diff --git a/DB.md b/DB.md
index 9292e64..7fd57da 100644
--- a/DB.md
+++ b/DB.md
@@ -264,7 +264,7 @@ SHOW PROCESSLIST:查看当前 MySQL 在进行的线程,可以实时地查看
1. 客户端发送一条查询给服务器
2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果(一般是 K-V 键值对),否则进入下一阶段
3. 分析器进行 SQL 分析,再由优化器生成对应的执行计划
-4. MySQL 根据优化器生成的执行计划,调用存储引擎的 API 来执行查询
+4. 执行器根据优化器生成的执行计划,调用存储引擎的 API 来执行查询
5. 将结果返回给客户端
大多数情况下不建议使用查询缓存,因为查询缓存往往弊大于利
@@ -434,7 +434,7 @@ MySQL 中保存着两种统计数据:
* innodb_table_stats 存储了表的统计数据,每一条记录对应着一个表的统计数据
* innodb_index_stats 存储了索引的统计数据,每一条记录对应着一个索引的一个统计项的数据
-MySQL 在真正执行语句之前,并不能精确地知道满足条件的记录有多少条,只能根据统计信息来估算记录,统计信息就是索引的区分度,一个索引上不同的值的个数(比如性别只能是男女,就是 2 ),称之为基数(cardinality),**基数越大说明区分度越好**
+MySQL 在真正执行语句之前,并不能精确地知道满足条件的记录有多少条,只能根据统计信息来估算记录,统计信息就是索引的区分度,一个索引上不同的值的个数(比如性别只能是男女,就是 2 ),称之为基数(cardinality),**基数越大说明区分度越好**
通过**采样统计**来获取基数,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数
@@ -3577,7 +3577,7 @@ MySQL 支持的存储引擎:
MyISAM 存储引擎:
* 特点:不支持事务和外键,读取速度快,节约资源
-* 应用场景:查询和插入操作为主,只有很少更新和删除操作,并对事务的完整性、并发性要求不高
+* 应用场景:**适用于读多写少的场景**,对事务的完整性要求不高,比如一些数仓、离线数据、支付宝的年度总结之类的场景,业务进行只读操作,查询起来会更快
* 存储方式:
* 每个 MyISAM 在磁盘上存储成 3 个文件,其文件名都和表名相同,拓展名不同
* 表的定义保存在 .frm 文件,表数据保存在 .MYD (MYData) 文件中,索引保存在 .MYI (MYIndex) 文件中
@@ -3593,7 +3593,7 @@ InnoDB 存储引擎:(MySQL5.5 版本后默认的存储引擎)
MEMORY 存储引擎:
- 特点:每个 MEMORY 表实际对应一个磁盘文件 ,该文件中只存储表的结构,表数据保存在内存中,且默认**使用 HASH 索引**,所以数据默认就是无序的,但是在需要快速定位记录可以提供更快的访问,**服务一旦关闭,表中的数据就会丢失**,存储不安全
-- 应用场景:通常用于更新不太频繁的小表,用以快速得到访问结果,类似缓存
+- 应用场景:**缓存型存储引擎**,通常用于更新不太频繁的小表,用以快速得到访问结果
- 存储方式:表结构保存在 .frm 中
MERGE 存储引擎:
@@ -3642,14 +3642,10 @@ MERGE 存储引擎:
| 批量插入速度 | 高 | 低 | 高 |
| **外键** | **不支持** | **支持** | **不支持** |
-MyISAM 和 InnoDB 的区别?
+只读场景 MyISAM 比 InnoDB 更快:
-* 事务:InnoDB 支持事务,MyISAM 不支持事务
-* 外键:InnoDB 支持外键,MyISAM 不支持外键
-* 索引:InnoDB 是聚集(聚簇)索引,MyISAM 是非聚集(非聚簇)索引
-
-* 锁粒度:InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁
-* 存储结构:参考本节上半部分
+* 底层存储结构有差别,MyISAM 是非聚簇索引,叶子节点保存的是数据的具体地址,不用回表查询
+* InnoDB 每次查询需要维护 MVCC 版本状态,保证并发状态下的读写冲突问题
@@ -5041,7 +5037,7 @@ CREATE INDEX idx_seller_name_sta_addr ON tb_seller(name, status, address); # 联
* **字符串不加单引号**,造成索引失效:隐式类型转换,当字符串和数字比较时会**把字符串转化为数字**
- 在查询时,没有对字符串加单引号,查询优化器会调用 CAST 函数将 status 转换为 int 进行比较,造成索引失效
+ 没有对字符串加单引号,查询优化器会调用 CAST 函数将 status 转换为 int 进行比较,造成索引失效
```mysql
EXPLAIN SELECT * FROM tb_seller WHERE name='小米科技' AND status = 1;
@@ -5134,7 +5130,7 @@ CREATE INDEX idx_seller_name_sta_addr ON tb_seller(name, status, address); # 联
EXPLAIN SELECT * FROM tb_seller WHERE sellerId NOT IN ('alibaba','huawei');
```
-* [MySQL 实战 45 讲](https://time.geekbang.org/column/article/74687)该章节最后提出了一种场景,获取到数据以后 Server 层还会做判断
+* [MySQL 实战 45 讲](https://time.geekbang.org/column/article/74687)该章节最后提出了一种慢查询场景,获取到数据以后 Server 层还会做判断
@@ -5202,7 +5198,7 @@ SHOW GLOBAL STATUS LIKE 'Handler_read%';
##### 自增机制
-自增主键可以让主键索引尽量地保持递增顺序插入,避免了页分裂,因此索引更紧凑
+自增主键可以让主键索引尽量地保持在数据页中递增顺序插入,不自增需要寻找其他页插入,导致随机 IO 和页分裂的情况
表的结构定义存放在后缀名为.frm 的文件中,但是并不会保存自增值,不同的引擎对于自增值的保存策略不同:
@@ -5770,9 +5766,9 @@ Flush 链表是一个用来**存储脏页**的链表,对于已经修改过的
##### LRU 链表
-当 Buffer Pool 中没有空闲缓冲页时就需要淘汰掉最近最少使用的部分缓冲页,为了实现这个功能,MySQL 创建了一个 LRU 链表,当访问某个页时:
+Buffer Pool 需要保证缓存的命中率,所以 MySQL 创建了一个 LRU 链表,当访问某个页时:
-* 如果该页不在 Buffer Pool 中,把该页从磁盘加载进来后会将该缓冲页对应的控制块作为节点放入 **LRU 链表的头部**
+* 如果该页不在 Buffer Pool 中,把该页从磁盘加载进来后会将该缓冲页对应的控制块作为节点放入 **LRU 链表的头部**,保证热点数据在链表头
* 如果该页在 Buffer Pool 中,则直接把该页对应的控制块移动到 LRU 链表的头部,所以 LRU 链表尾部就是最近最少使用的缓冲页
MySQL 基于局部性原理提供了预读功能:
@@ -5780,7 +5776,7 @@ MySQL 基于局部性原理提供了预读功能:
* 线性预读:系统变量 `innodb_read_ahead_threshold`,如果顺序访问某个区(extent:16 KB 的页,连续 64 个形成一个区,一个区默认 1MB 大小)的页面数超过了该系统变量值,就会触发一次**异步读取**下一个区中全部的页面到 Buffer Pool 中
* 随机预读:如果某个区 13 个连续的页面都被加载到 Buffer Pool,无论这些页面是否是顺序读取,都会触发一次**异步读取**本区所有的其他页面到 Buffer Pool 中
-预读会造成加载太多用不到的数据页,造成那些使用**频率很高的数据页被挤到 LRU 链表尾部**,所以 InnoDB 将 LRU 链表分成两段:
+预读会造成加载太多用不到的数据页,造成那些使用频率很高的数据页被挤到 LRU 链表尾部,所以 InnoDB 将 LRU 链表分成两段,**冷热数据隔离**:
* 一部分存储使用频率很高的数据页,这部分链表也叫热数据,young 区,靠近链表头部的区域
* 一部分存储使用频率不高的冷数据,old 区,靠近链表尾部,默认占 37%,可以通过系统变量 `innodb_old_blocks_pct` 指定
@@ -5847,7 +5843,7 @@ MySQL 5.7.5 之前 `innodb_buffer_pool_size` 只支持在系统启动时修改
#### Change
-InnoDB 管理的 Buffer Pool 中有一块内存叫 Change Buffer 用来对**增删改操作**提供缓存,参数 `innodb_change_buffer_max_size ` 来动态设置,设置为 50 时表示 Change Buffer 的大小最多只能占用 Buffer Pool 的 50%
+InnoDB 管理的 Buffer Pool 中有一块内存叫 Change Buffer 用来对**增删改操作**提供缓存,可以通过参数来动态设置,设置为 50 时表示 Change Buffer 的大小最多占用 Buffer Pool 的 50%
* 唯一索引的更新不能使用 Change Buffer,需要将数据页读入内存,判断没有冲突在写入
* 普通索引可以使用 Change Buffer,**直接写入 Buffer 就结束**,不用校验唯一性
@@ -5904,7 +5900,7 @@ SHOW PROCESSLIST 获取线程信息后,处于 Sending to client 状态代表
read_rnd_buffer 是 MySQL 的随机读缓冲区,当按任意顺序读取记录行时将分配一个随机读取缓冲区,进行排序查询时,MySQL 会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,大小是由 read_rnd_buffer_size 参数控制的
-**Multi-Range Read 优化**,将随机 IO 转化为顺序 IO 以降低查询过程中 IO 开销,因为大多数的数据都是按照主键递增顺序插入得到,所以按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能
+Multi-Range Read 优化,**将随机 IO 转化为顺序 IO** 以降低查询过程中 IO 开销,因为大多数的数据都是按照主键递增顺序插入得到,所以按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能
二级索引为 a,聚簇索引为 id,优化回表流程:
@@ -6346,7 +6342,7 @@ InnoDB 存储引擎提供了两种事务日志:redo log(重做日志)和 u
* redo log 用于保证事务持久性
* undo log 用于保证事务原子性和隔离性
-undo log 属于逻辑日志,根据每行操作进行记录,记录了 SQL 执行相关的信息,用来回滚行记录到某个版本
+undo log 属于**逻辑日志**,根据每行操作进行记录,记录了 SQL 执行相关的信息,用来回滚行记录到某个版本
当事务对数据库进行修改时,InnoDB 会先记录对应的 undo log,如果事务执行失败或调用了 rollback 导致事务回滚,InnoDB 会根据 undo log 的内容**做与之前相反的操作**:
@@ -6430,7 +6426,7 @@ roll_pointer 是一个指针,**指向记录对应的 undo log 日志**,一
* 将旧纪录进行 delete mark,在更新语句提交后由 purge 线程移入垃圾链表
* 根据更新的各列的值创建一条新纪录,插入到聚簇索引中
-在对一条记录修改前会**将记录的隐藏列 trx_id 和 roll_pointer 的旧值记录到 undo log 对应的属性中**,这样当前记录的 roll_pointer 指向当前 undo log 记录,当前 undo log 记录的 roll_pointer 指向旧的 undo log 记录,**形成一个版本链**
+在对一条记录修改前会**将记录的隐藏列 trx_id 和 roll_pointer 的旧值记录到当前 undo log 对应的属性中**,这样当前记录的 roll_pointer 指向当前 undo log 记录,当前 undo log 记录的 roll_pointer 指向旧的 undo log 记录,**形成一个版本链**
UPDATE、DELETE 操作产生的 undo 日志会用于其他事务的 MVCC 操作,所以不能立即删除,INSERT 可以删除的原因是 MVCC 是对现有数据的快照
@@ -6553,7 +6549,7 @@ undo log 是逻辑日志,记录的是每个事务对数据执行的操作,
undo log 的作用:
* 保证事务进行 rollback 时的原子性和一致性,当事务进行回滚的时候可以用 undo log 的数据进行恢复
-* 用于 MVCC 快照读,通过读取 undo log 的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据版本
+* 用于 MVCC 快照读,通过读取 undo log 的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据
undo log 主要分为两种:
@@ -6741,15 +6737,15 @@ Buffer Pool 的使用提高了读写数据的效率,但是如果 MySQL 宕机
log buffer 被划分为若干 redo log block(块,类似数据页的概念),每个默认大小 512 字节,每个 block 由 12 字节的 log block head、496 字节的 log block body、4 字节的 log block trailer 组成
* 当数据修改时,先修改 Change Buffer 中的数据,然后在 redo log buffer 记录这次操作,写入 log buffer 的过程是**顺序写入**的(先写入前面的 block,写满后继续写下一个)
-* log buffer 中有一个指针 buf_free,来标识该位置之前都是填满的 block,该位置之后都是空闲区域(**碰撞指针**)
+* log buffer 中有一个指针 buf_free,来标识该位置之前都是填满的 block,该位置之后都是空闲区域
MySQL 规定对底层页面的一次原子访问称为一个 Mini-Transaction(MTR),比如在 B+ 树上插入一条数据就算一个 MTR
* 一个事务包含若干个 MTR,一个 MTR 对应一组若干条 redo log,一组 redo log 是不可分割的,在进行数据恢复时也把一组 redo log 当作一个不可分割的整体处理
-* 所以不是每生成一条 redo 日志就将其插入到 log buffer 中,而是一个 MTR 结束后**将一组 redo 日志写入 log buffer**
+* 不是每生成一条 redo 日志就将其插入到 log buffer 中,而是一个 MTR 结束后**将一组 redo 日志写入**
-InnoDB 的 redo log 是**固定大小**的,redo 日志在磁盘中以文件组的形式存储,同一组中的每个文件大小一样格式一样,
+InnoDB 的 redo log 是**固定大小**的,redo 日志在磁盘中以文件组的形式存储,同一组中的每个文件大小一样格式一样
* `innodb_log_group_home_dir` 代表磁盘存储 redo log 的文件目录,默认是当前数据目录
* `innodb_log_file_size` 代表文件大小,默认 48M,`innodb_log_files_in_group` 代表文件个数,默认 2 最大 100,所以日志的文件大小为 `innodb_log_file_size * innodb_log_files_in_group`
@@ -6766,10 +6762,10 @@ redo 日志文件也是由若干个 512 字节的 block 组成,日志文件的
##### 日志刷盘
-redo log 需要在事务提交时将日志写入磁盘,但是比将内存中的 Buffer Pool 修改的数据写入磁盘的速度快,原因:
+redo log 需要在事务提交时将日志写入磁盘,但是比 Buffer Pool 修改的数据写入磁盘的速度快,原因:
* 刷脏是随机 IO,因为每次修改的数据位置随机;redo log 和 binlog 都是**顺序写**,磁盘的顺序 IO 比随机 IO 速度要快
-* 刷脏是以数据页(Page)为单位的,一个页上的一个小修改都要整页写入;redo log 中只包含真正需要写入的部分,减少无效 IO
+* 刷脏是以数据页(Page)为单位的,一个页上的一个小修改都要整页写入;redo log 中只包含真正需要写入的部分,好几页的数据修改可能只记录在一个 redo log 页中,减少无效 IO
* **组提交机制**,可以大幅度降低磁盘的 IO 消耗
InnoDB 引擎会在适当的时候,把内存中 redo log buffer 持久化(fsync)到磁盘,具体的**刷盘策略**:
@@ -6780,7 +6776,6 @@ InnoDB 引擎会在适当的时候,把内存中 redo log buffer 持久化(fs
* 2:在事务提交时将缓冲区的 redo 日志异步写入到磁盘,不能保证提交时肯定会写入,只是有这个动作。日志已经在操作系统的缓存,如果操作系统没有宕机而 MySQL 宕机,也是可以恢复数据的
* 写入 redo log buffer 的日志超过了总容量的一半,就会将日志刷入到磁盘文件,这会影响执行效率,所以开发中应**避免大事务**
* 服务器关闭时
-* checkpoint 时(下小节详解)
* 并行的事务提交(组提交)时,会将将其他事务的 redo log 持久化到磁盘。假设事务 A 已经写入 redo log buffer 中,这时另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么事务 B 要把 redo log buffer 里的日志全部持久化到磁盘,**因为多个事务共用一个 redo log buffer**,所以一次 fsync 可以刷盘多个事务的 redo log,提升了并发量
服务器启动后 redo 磁盘空间不变,所以 redo 磁盘中的日志文件是被**循环使用**的,采用循环写数据的方式,写完尾部重新写头部,所以要确保头部 log 对应的修改已经持久化到磁盘
@@ -6876,9 +6871,9 @@ binlog 为什么不支持崩溃恢复?
* 首先更新该记录对应的聚簇索引,更新聚簇索引记录时:
* 更新记录前向 undo 页面写 undo 日志,由于这是更改页面,所以需要记录一下相应的 redo 日志
- 注意:修改 undo页面也是在**修改页面**,事务凡是修改页面就需要先记录相应的 redo 日志
+ 注意:修改 undo 页面也是在**修改页面**,事务只要修改页面就需要先记录相应的 redo 日志
- * 然后**先记录对应的的 redo 日志**(等待 MTR 提交后写入 redo log buffer),**最后进行真正的更新记录**
+ * 然后**记录对应的 redo 日志**(等待 MTR 提交后写入 redo log buffer),**最后进行真正的更新记录**
* 更新其他的二级索引记录,不会再记录 undo log,只记录 redo log 到 buffer 中
@@ -6919,7 +6914,7 @@ update T set c=c+1 where ID=2;
* Prepare 阶段:存储引擎将该事务的 **redo 日志刷盘**,并且将本事务的状态设置为 PREPARE,代表执行完成随时可以提交事务
* Commit 阶段:先将事务执行过程中产生的 binlog 刷新到硬盘,再执行存储引擎的提交工作,引擎把 redo log 改成提交状态
-redo log 和 binlog 都可以用于表示事务的提交状态,而**两阶段提交就是让这两个状态保持逻辑上的一致**,也有利于主从复制,更好的保持主从数据的一致性
+存储引擎层的 redo log 和 server 层的 binlog 可以认为是一个分布式事务, 都可以用于表示事务的提交状态,而**两阶段提交就是让这两个状态保持逻辑上的一致**,也有利于主从复制,更好的保持主从数据的一致性
@@ -6931,7 +6926,7 @@ redo log 和 binlog 都可以用于表示事务的提交状态,而**两阶段
系统崩溃前没有提交的事务的 redo log 可能已经刷盘(定时线程或者 checkpoint),怎么处理崩溃恢复?
-工作流程:获取 undo 链表首节点页面的 undo segement header 中的 TRX_UNDO_STATE 属性,表示当前链表的事务属性,事务状态是活跃(未提交)的就全部回滚,如果是 PREPARE 状态,就需要根据 binlog 的状态进行判断:
+工作流程:获取 undo 链表首节点页面的 undo segement header 中的 TRX_UNDO_STATE 属性,表示当前链表的事务属性,**事务状态是活跃(未提交)的就全部回滚**,如果是 PREPARE 状态,就需要根据 binlog 的状态进行判断:
* 如果在时刻 A 发生了崩溃(crash),由于此时 binlog 还没完成,所以需要进行回滚
* 如果在时刻 B 发生了崩溃,redo log 和 binlog 有一个共**同的数据字段叫 XID**,崩溃恢复的时候,会按顺序扫描 redo log:
@@ -7406,7 +7401,7 @@ InnoDB 会对间隙(GAP)进行加锁,就是间隙锁 (RR 隔离级别下
InnoDB 加锁的基本单位是 next-key lock,该锁是行锁和 gap lock 的组合(X or S 锁),但是加锁过程是分为间隙锁和行锁两段执行
-* 可以**保护当前记录和前面的间隙**,遵循左开右闭原则,单纯的是间隙锁左开右开
+* 可以**保护当前记录和前面的间隙**,遵循左开右闭原则,单纯的间隙锁是左开右开
* 假设有 10、11、13,那么可能的间隙锁包括:(负无穷,10]、(10,11]、(11,13]、(13,正无穷)
几种索引的加锁情况:
@@ -7416,7 +7411,7 @@ InnoDB 加锁的基本单位是 next-key lock,该锁是行锁和 gap lock 的
* 范围查询无论是否是唯一索引,都需要访问到不满足条件的第一个值为止
* 对于联合索引且是唯一索引,如果 where 条件只包括联合索引的一部分,那么会加间隙锁
-间隙锁优点:RR 级别下间隙锁可以解决事务的一部分的**幻读问题**,通过对间隙加锁,可以防止读取过程中数据条目发生变化。一部分的意思是不会对全部间隙加锁,只能加锁一部分的间隙
+间隙锁优点:RR 级别下间隙锁可以**解决事务的一部分的幻读问题**,通过对间隙加锁,可以防止读取过程中数据条目发生变化。一部分的意思是不会对全部间隙加锁,只能加锁一部分的间隙
间隙锁危害:
@@ -7752,7 +7747,7 @@ MySQL 的主从之间维持了一个**长连接**。主库内部有一个线程
主从复制主要依赖的是 binlog,MySQL 默认是异步复制,需要三个线程:
-- binlog thread:在主库事务提交时,负责把数据变更记录在二进制日志文件 binlog 中,并通知 slave 有数据更新
+- binlog thread:在主库事务提交时,把数据变更记录在日志文件 binlog 中,并通知 slave 有数据更新
- I/O thread:负责从主服务器上**拉取二进制日志**,并将 binlog 日志内容依次写到 relay log 中转日志的最末端,并将新的 binlog 文件名和 offset 记录到 master-info 文件中,以便下一次读取日志时从指定 binlog 日志文件及位置开始读取新的 binlog 日志内容
- SQL thread:监测本地 relay log 中新增了日志内容,读取中继日志并重做其中的 SQL 语句,从库在 relay-log.info 中记录当前应用中继日志的文件名和位点以便下一次执行
@@ -7836,7 +7831,7 @@ coordinator 就是原来的 SQL Thread,并行复制中它不再直接更新数
* 线程分配完成并不是立即执行,为了防止造成更新覆盖,更新同一 DB 的两个事务必须被分发到同一个工作线程
* 同一个事务不能被拆开,必须放到同一个工作线程
-MySQL 5.6 版本的策略:每个线程对应一个 hash 表,用于保存当前这个线程的执行队列里的事务所涉及的表,hash 表的 key 是数据库 名,value 是一个数字,表示队列中有多少个事务修改这个库,适用于主库上有多个 DB 的情况
+MySQL 5.6 版本的策略:每个线程对应一个 hash 表,用于保存当前这个线程的执行队列里的事务所涉及的表,hash 表的 key 是数据库名,value 是一个数字,表示队列中有多少个事务修改这个库,适用于主库上有多个 DB 的情况
每个事务在分发的时候,跟线程的**冲突**(事务操作的是同一个库)关系包括以下三种情况:
@@ -7846,7 +7841,7 @@ MySQL 5.6 版本的策略:每个线程对应一个 hash 表,用于保存当
优缺点:
-* 构造 hash 值的时候很快,只需要库名,而且一个实例上 DB 数也不会很多,不会出现需要构造很多个项的情况
+* 构造 hash 值的时候很快,只需要库名,而且一个实例上 DB 数也不会很多,不会出现需要构造很多项的情况
* 不要求 binlog 的格式,statement 格式的 binlog 也可以很容易拿到库名(日志章节详解了 binlog)
* 主库上的表都放在同一个 DB 里面,这个策略就没有效果了;或者不同 DB 的热点不同,比如一个是业务逻辑库,一个是系统配置库,那也起不到并行的效果,需要**把相同热度的表均匀分到这些不同的 DB 中**,才可以使用这个策略
@@ -7977,7 +7972,7 @@ SELECT master_pos_wait(file, pos[, timeout]);
* 选定一个从库执行判断位点语句,如果返回值是 >=0 的正整数,说明从库已经同步完事务,可以在这个从库执行查询语句
* 如果出现其他情况,需要到主库执行查询语句
-注意:如果所有的从库都延迟超过 timeout 秒,查询压力就都跑到主库上,所以需要进行权衡
+注意:如果所有的从库都延迟超过 timeout 秒,查询压力就都跑到主库上,所以需要进行权衡
@@ -9721,7 +9716,7 @@ Redis 基于 Reactor 模式开发了网络事件处理器,这个处理器被
-尽管多个文件事件可能会并发出现,但是 I/O 多路复用程序将所有产生事件的套接字处理请求放入一个**单线程的执行队列**中,通过队列有序、同步的向文件事件分派器传送套接字,上一个套接字产生的事件处理完后,才会继续向分派器传送下一个
+I/O 多路复用程序将所有产生事件的套接字处理请求放入一个**单线程的执行队列**中,通过队列有序、同步的向文件事件分派器传送套接字,上一个套接字产生的事件处理完后,才会继续向分派器传送下一个
@@ -10799,7 +10794,7 @@ SDS 通过未使用空间解除了字符串长度和底层数组长度之间的
内存重分配涉及复杂的算法,需要执行**系统调用**,是一个比较耗时的操作,SDS 的两种优化策略:
-* 空间预分配:当 SDS需要进行空间扩展时,程序不仅会为 SDS 分配修改所必需的空间, 还会为 SDS 分配额外的未使用空间
+* 空间预分配:当 SDS 需要进行空间扩展时,程序不仅会为 SDS 分配修改所必需的空间, 还会为 SDS 分配额外的未使用空间
* 对 SDS 修改之后,SDS 的长度(len 属性)小于 1MB,程序分配和 len 属性同样大小的未使用空间,此时 len 和 free 相等
@@ -11010,7 +11005,7 @@ load_factor = ht[0].used / ht[0].size
原因:执行该命令的过程中,Redis 需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)技术来优化子进程的使用效率,通过提高执行扩展操作的负载因子,尽可能地避免在子进程存在期间进行哈希表扩展操作,可以避免不必要的内存写入操作,最大限度地节约内存
-哈希表执行收缩的条件:负载因子小于 0.1(自动执行,servreCron 中检测),缩小为字典中数据个数的 50% 左右
+哈希表执行收缩的条件:负载因子小于 0.1(自动执行,servreCron 中检测)
@@ -11553,7 +11548,7 @@ Redis 所有操作都是**原子性**的,采用**单线程**机制,命令是
-#### 对象
+#### 实现
字符串对象的编码可以是 int、raw、embstr 三种
@@ -12100,6 +12095,11 @@ set 类型:与 hash 存储结构哈希表完全相同,只是仅存储键不
当元素比较多时,此时 ziplist 的读写效率会下降,时间复杂度是 O(n),跳表的时间复杂度是 O(logn)
+为什么用跳表而不用平衡树?
+
+* 在做范围查找的时候,跳表操作简单(前进指针或后退指针),平衡树需要回旋查找
+* 跳表比平衡树实现简单,平衡树的插入和删除操作可能引发子树的旋转调整,而跳表的插入和删除只需要修改相邻节点的指针
+
***
@@ -12354,7 +12354,7 @@ AOF:将数据的操作过程进行保存,日志形式,存储操作过程
#### 文件创建
-RDB 持久化功能所生成的 RDB 文件 是一个经过压缩的紧凑二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态,有两个 Redis 命令可以生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE
+RDB 持久化功能所生成的 RDB 文件是一个经过压缩的紧凑二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态,有两个 Redis 命令可以生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE
@@ -12655,12 +12655,12 @@ appendfsync always|everysec|no #AOF写数据策略:默认为everysec
特点:安全性最高,数据零误差,但是性能较低,不建议使用
-- everysec:先将 aof_buf 缓冲区中的内容写入到 AOF 文件,判断上次同步 AOF 文件的时间距离现在超过一秒钟,再次对 AOF 文件进行同步 fsync,这个同步操作是由一个(子)线程专门负责执行的
+- everysec:先将 aof_buf 缓冲区中的内容写入到操作系统缓存,判断上次同步 AOF 文件的时间距离现在超过一秒钟,再次进行同步 fsync,这个同步操作是由一个(子)线程专门负责执行的
特点:在系统突然宕机的情况下丢失 1 秒内的数据,准确性较高,性能较高,建议使用,也是默认配置
-- no:将 aof_buf 缓冲区中的内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统来决定
+- no:将 aof_buf 缓冲区中的内容写入到操作系统缓存,但并不进行同步,何时同步由操作系统来决定
特点:**整体不可控**,服务器宕机会丢失上次同步 AOF 后的所有写指令
@@ -12758,7 +12758,7 @@ bgrewriteaof
* 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理命令请求
-* 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下, 保证数据的安全性
+* 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下, 保证数据安全性

@@ -13495,7 +13495,7 @@ Redis 分布式锁的基本使用,悲观锁
`NX`:只在键不存在时,才对键进行设置操作,`SET key value NX` 效果等同于 `SETNX key value`
- `XX` :只在键已经存在时,才对键进行设置操作
+ `XX`:只在键已经存在时,才对键进行设置操作
`EX`:设置键 key 的过期时间,单位时秒
@@ -13633,7 +13633,7 @@ end
主从一致性:集群模式下,主从同步存在延迟,当加锁后主服务器宕机时,从服务器还没同步主服务器中的锁数据,此时从服务器升级为主服务器,其他线程又可以获取到锁
-将服务器升级为多主多从,:
+将服务器升级为多主多从:
* 获取锁需要从所有主服务器 SET 成功才算获取成功
* 某个 master 宕机,slave 还没有同步锁数据就升级为 master,其他线程尝试加锁会加锁失败,因为其他 master 上已经存在该锁
@@ -14007,7 +14007,7 @@ PSYNC 命令的调用方法有两种
#### 心跳机制
-心跳机制:进入命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:`REPLCONF ACK `,re_offset 是从服务器当前的复制偏移量
+心跳机制:进入命令传播阶段,**从服务器**默认会以每秒一次的频率,**向主服务器发送命令**:`REPLCONF ACK `,replication_offset 是从服务器当前的复制偏移量
心跳的作用:
@@ -14046,7 +14046,7 @@ slavel: ip=127.0.0.1,port=22222,state=online,offset=456,lag=3 # 3秒之前发送
#### 配置选项
-Redis 的 min-slaves-to-write 和 min-slaves-max-lag 两个选项可以防止主服务器在**不安全的情况下**执行写命令
+Redis 的 min-slaves-to-write 和 min-slaves-max-lag 两个选项可以防止主服务器在**不安全的情况下**拒绝执行写命令
比如向主服务器设置:
@@ -14113,7 +14113,7 @@ master 的 CPU 占用过高或 slave 频繁断开连接
* 出现的原因:
* slave 每 1 秒发送 REPLCONF ACK 命令到 master
- * 当 slave 接到了慢查询时(keys * ,hgetall等),会大量占用 CPU 性能
+ * 当 slave 接到了慢查询时(keys * ,hgetall 等),会大量占用 CPU 性能
* master 每 1 秒调用复制定时函数 replicationCron(),比对 slave 发现长时间没有进行响应
最终导致 master 各种资源(输出缓冲区、带宽、连接等)被严重占用
@@ -14277,7 +14277,7 @@ Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器,当
#### 代码替换
-将一部分普通 Redis服务器使用的代码替换成 Sentinel 专用代码
+将一部分普通 Redis 服务器使用的代码替换成 Sentinel 专用代码
Redis 服务器端口:
@@ -14321,7 +14321,7 @@ struct sentinelState {
// 当前纪元,用于实现故障转移
uint64_t current_epoch;
- // 保存了所有被这个sentinel监视的主服务器
+ // 【保存了所有被这个sentinel监视的主服务器】
dict *masters;
// 是否进入了 TILT 模式
@@ -14438,10 +14438,10 @@ typedef struct sentinelAddr {
##### 主服务器
-Sentinel 默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO 命令,来获取主服务器的当前信息
+Sentinel 默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO 命令,来获取主服务器的信息
* 一部分是主服务器本身的信息,包括 runid 域记录的服务器运行 ID,以及 role 域记录的服务器角色
-* 另一部分是服务器属下所有从服务器的信息,每个从服务器都由一个 slave 字符串开头的行记录,根据这些 IP 地址和端口号,Sentinel 无须用户提供从服务器的地址信息,就可以自动发现从服务器
+* 另一部分是服务器属下所有从服务器的信息,每个从服务器都由一个 slave 字符串开头的行记录,根据这些 IP 地址和端口号,Sentinel 无须用户提供从服务器的地址信息,就可以**自动发现从服务器**
```sh
# Server
@@ -14470,7 +14470,7 @@ slave1: ip=l27.0.0.1, port=22222, state=online, offset=22, lag=0
##### 从服务器
-当 Sentinel 发现主服务器有新的从服务器出现时,会为这个新的从服务器创建相应的实例结构,还会创建到从服务器的命令连接和订阅连接,所以 Sentinel 对所有的从服务器之间都可以进行命令操作
+当 Sentinel 发现主服务器有新的从服务器出现时,会为这个新的从服务器创建相应的实例结构,还会**创建到从服务器的命令连接和订阅连接**,所以 Sentinel 对所有的从服务器之间都可以进行命令操作
Sentinel 默认会以每十秒一次的频率,向从服务器发送 INFO 命令:
@@ -14614,7 +14614,7 @@ SENTINEL is-master-down-by-addr
源 Sentinel 将统计其他 Sentinel 同意主服务器已下线的数量,当这一数量达到配置指定的判断客观下线所需的数量(quorum)时,Sentinel 会将主服务器对应实例结构 flags 属性的 SRI_O_DOWN 标识打开,代表客观下线,并对主服务器执行故障转移操作
-注意:**不同 Sentinel 判断客观下线的条件可能不同**,因为载入的配置文件中的属性(quorum)可能不同
+注意:**不同 Sentinel 判断客观下线的条件可能不同**,因为载入的配置文件中的属性 quorum 可能不同
@@ -14624,7 +14624,7 @@ SENTINEL is-master-down-by-addr
### 领头选举
-主服务器被判断为客观下线时,监视这个主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel 对下线服务器执行故障转移
+主服务器被判断为客观下线时,**监视该主服务器的各个 Sentinel 会进行协商**,选举出一个领头 Sentinel 对下线服务器执行故障转移
Redis 选举领头 Sentinel 的规则:
@@ -14633,7 +14633,7 @@ Redis 选举领头 Sentinel 的规则:
* 在一个配置纪元里,所有 Sentinel 都只有一次将某个 Sentinel 设置为局部领头 Sentinel 的机会,并且局部领头一旦设置,在这个配置纪元里就不能再更改
* Sentinel 设置局部领头 Sentinel 的规则是先到先得,最先向目标 Sentinel 发送设置要求的源 Sentinel 将成为目标 Sentinel 的局部领头 Sentinel,之后接收到的所有设置要求都会被目标 Sentinel 拒绝
-* 领头 Sentinel 的产生需要半数以上 Sentinel 的支持,并且每个 Sentinel 只有一票,所以一个配置纪元只会出现一个领头 Sentinel,比如 10 个 Sentinel 的系统中,至少需要 `10/2 + 1 = 6` 票
+* 领头 Sentinel 的产生**需要半数以上 Sentinel 的支持**,并且每个 Sentinel 只有一票,所以一个配置纪元只会出现一个领头 Sentinel,比如 10 个 Sentinel 的系统中,至少需要 `10/2 + 1 = 6` 票
选举过程:
@@ -14641,7 +14641,7 @@ Redis 选举领头 Sentinel 的规则:
* 目标 Sentinel 接受命令处理完成后,将返回一条命令回复,回复中的 leader_runid 和 leader_epoch 参数分别记录了目标 Sentinel 的局部领头 Sentinel 的运行 ID 和配置纪元
* 源 Sentinel 接收目标 Sentinel 命令回复之后,会判断 leader_epoch 是否和自己的相同,相同就继续判断 leader_runid 是否和自己的运行 ID 一致,成立表示目标 Sentinel 将源 Sentinel 设置成了局部领头 Sentinel,即获得一票
* 如果某个 Sentinel 被半数以上的 Sentinel 设置成了局部领头 Sentinel,那么这个 Sentinel 成为领头 Sentinel
-* 如果在给定时限内,没有一个 Sentinel 被选举为领头 Sentinel,那么各个 Sentinel 将在一段时间后再次选举,直到选出领头
+* 如果在给定时限内,没有一个 Sentinel 被选举为领头 Sentinel,那么各个 Sentinel 将在一段时间后**再次选举**,直到选出领头
* 每次进行领头 Sentinel 选举之后,不论选举是否成功,所有 Sentinel 的配置纪元(configuration epoch)都要自增一次
Sentinel 集群至少 3 个节点的原因:
@@ -14649,7 +14649,10 @@ Sentinel 集群至少 3 个节点的原因:
* 如果 Sentinel 集群只有 2 个 Sentinel 节点,则领头选举需要 `2/2 + 1 = 2` 票,如果一个节点挂了,那就永远选不出领头
* Sentinel 集群允许 1 个 Sentinel 节点故障则需要 3 个节点的集群,允许 2 个节点故障则需要 5 个节点集群
+**如何获取哨兵节点的半数数量**?
+* 客观下线是通过配置文件获取的数量,达到 quorum 就客观下线
+* 哨兵数量是通过主节点是实例结构中,保存着监视该主节点的所有哨兵信息,从而获取得到
@@ -14748,7 +14751,8 @@ typedef struct clusterState {
// 集群当前的状态,是在线还是下线
int state;
- // 集群中至少处理着一个槽的节点的数量,为0表示集群目前没有任何节点在处理槽
+ // 集群中至少处理着一个槽的(主)节点的数量,为0表示集群目前没有任何节点在处理槽
+ // 【选举时投票数量超过半数,从这里获取的】
int size;
// 集群节点名单(包括 myself 节点),字典的键为节点的名字,字典的值为节点对应的clusterNode结构
@@ -14844,7 +14848,7 @@ CLUSTER MEET
#### 基本操作
-Redis 集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为16384 个槽(slot),数据库中的每个键都属于 16384 个槽中的一个,集群中的每个节点可以处理 0 个或最多 16384 个槽(**每个主节点存储的数据并不一样**)
+Redis 集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分为 16384 个槽(slot),数据库中的每个键都属于 16384 个槽中的一个,集群中的每个节点可以处理 0 个或最多 16384 个槽(**每个主节点存储的数据并不一样**)
* 当数据库中的 16384 个槽都有节点在处理时,集群处于上线状态(ok)
* 如果数据库中有任何一个槽得到处理,那么集群处于下线状态(fail)
@@ -14929,7 +14933,7 @@ typedef struct clusterState {
#### 集群数据
-集群节点保存键值对以及键值对过期时间的方式,与单机 Redis 服务器保存键值对以及键值对过期时间的方式完全相同,但是集群节点只能使用 0 号数据库,单机服务器可以任意使用
+集群节点保存键值对以及键值对过期时间的方式,与单机 Redis 服务器保存键值对以及键值对过期时间的方式完全相同,但是**集群节点只能使用 0 号数据库**,单机服务器可以任意使用
除了将键值对保存在数据库里面之外,节点还会用 clusterState 结构中的 slots_to_keys 跳跃表来**保存槽和键之间的关系**
@@ -15189,7 +15193,7 @@ struct clusterNode {
#### 故障检测
-集群中的每个节点都会定期地向集群中的其他节点发送 PING 消息,来检测对方是否在线,如果接收 PING 的节点没有在规定的时间内返回 PONG 消息,那么发送消息节点就会将接收节点标记为**疑似下线**(probable fail, PFAIL)
+集群中的每个节点都会定期地向集群中的其他节点发送 PING 消息,来检测对方是否在线,如果接收 PING 的节点没有在规定的时间内返回 PONG 消息,那么发送消息节点就会将接收节点标记为**疑似下线**(probable fail)
集群中的节点会互相发送消息,来**交换集群中各个节点的状态信息**,当一个主节点 A 通过消息得知主节点 B 认为主节点 C 进入了疑似下线状态时,主节点 A 会在 clusterState.nodes 字典中找到主节点 C 所对应的节点,并将主节点 B 的下线报告(failure report)添加到 clusterNode.fail_reports 链表里面
@@ -15253,7 +15257,7 @@ struct clusterNodeFailReport {
-选举新主节点的方法和选举领头 Sentinel 的方法非常相似,两者都是基于 Raft 算法的领头选举(eader election)方法实现的
+选举新主节点的方法和选举领头 Sentinel 的方法非常相似,两者都是基于 Raft 算法的领头选举(leader election)方法实现的
@@ -15456,7 +15460,7 @@ typedef struct clusterMsgDataPublish {
### 脑裂问题
-脑裂指在主从集群中,同时有两个相同的主节点能接收写请求,导致客户端不知道应该往哪个主节点写入数据,导致不同客户端往不同的主节点上写入数据
+脑裂指在主从集群中,同时有两个相同的主节点能接收写请求,导致客户端不知道应该往哪个主节点写入数据,最后 不同客户端往不同的主节点上写入数据
* 原主节点并没有真的发生故障,由于某些原因无法处理请求(CPU 利用率很高、自身阻塞),无法按时响应心跳请求,被哨兵/集群主节点错误的判断为下线
* 在被判断下线之后,原主库又重新开始处理请求了,哨兵/集群主节点还没有完成主从切换,客户端仍然可以和原主库通信,客户端发送的写操作就会在原主库上写入数据,造成脑裂问题
@@ -15825,7 +15829,7 @@ OK
Redis 的管道 Pipeline 机制可以一次处理多条指令
* Pipeline 中的多条命令非原子性,因为在向管道内添加命令时,其他客户端的发送的命令仍然在执行
-* 原生批命令(mset 等)是服务端实现,而 pipeline 需要服务端与客户端共同完成
+* 原生批命令(MSET 等)是服务端实现,而 Pipeline 需要服务端与客户端共同完成
使用 Pipeline 封装的命令数量不能太多,数据量过大会增加客户端的等待时间,造成网络阻塞,Jedis 中的 Pipeline 使用方式:
@@ -16135,6 +16139,13 @@ Read-Through Pattern 也存在首次不命中的问题,采用缓存预热解
- 在抢购或秒杀场景下,可能因商品对应库存 Key 的请求量过大,超出 Redis 处理能力造成超卖
- 热 Key 的请求压力数量超出 Redis 的承受能力易造成缓存击穿,即大量请求将被直接指向后端的存储层,导致存储访问量激增甚至宕机,从而影响其他业务
+热 Key 分类两种,治理方式如下:
+
+* 一种是单一数据,比如秒杀场景,假设总量 10000 可以拆为多个 Key 进行访问,每次对请求进行路由到不同的 Key 访问,保证最终一致性,但是会出现访问不同 Key 产生的剩余量是不同的,这时可以通过前端进行 Mock 假数据
+* 一种是多数据集合,比如进行 ID 过滤,这时可以添加本地 LRU 缓存,减少对热 Key 的访问
+
+
+
参考文档:https://help.aliyun.com/document_detail/353223.html
diff --git a/Frame.md b/Frame.md
index 9c7fdaa..f00ae48 100644
--- a/Frame.md
+++ b/Frame.md
@@ -739,9 +739,13 @@ Maven 的插件用来执行生命周期中的相关事件
### 继承
-作用:通过继承可以实现在子工程中沿用父工程中的配置
+Maven 中的继承与 Java 中的继承相似,可以实现在子工程中沿用父工程中的配置
-- Maven 中的继承与 Java 中的继承相似,在子工程中配置继承关系
+dependencyManagement 里只是声明依赖,并不实现引入,所以子工程需要显式声明需要用的依赖
+
+- 如果子工程中未声明依赖,则不会从父项目继承下来
+- 在子工程中声明该依赖项,并且不指定具体版本,才会从父项目中继承该项,version 和 scope 都继承取自父工程 pom 文件
+- 如果子工程中指定了版本号,那么使用子工程中指定的 jar 版本
制作方式:
@@ -3485,7 +3489,7 @@ Kafka 比 RocketMQ 吞吐量高:
Topic 的 partition 数量过多时,Kafka 的性能不如 RocketMQ:
-* 两者都使用文件存储,但是 Kafka 是一个分区一个文件,Topic 过多时分区的总量也会增加,过多的文件导致对消息刷盘时出现文件竞争磁盘,造成性能的下降。一个分区只能被一个消费组中的一个消费线程进行消费,因此可以同时消费的消费端也比较少
+* 两者都使用文件存储,但是 Kafka 是一个分区一个文件,Topic 过多时分区的总量也会增加,过多的文件导致对消息刷盘时出现文件竞争磁盘,造成性能的下降。**一个分区只能被一个消费组中的一个消费线程进行消费**,因此可以同时消费的消费端也比较少
* RocketMQ 所有队列都存储在一个文件中,每个队列存储的消息量也比较小,因此多 Topic 的对 RocketMQ 的性能的影响较小
@@ -4856,7 +4860,7 @@ Consumer 端实现负载均衡的核心类 **RebalanceImpl**
对比下 RebalancePushImpl 和 RebalancePullImpl 两个实现类的 dispatchPullRequest() 方法,RebalancePullImpl 类里面的该方法为空
-消息消费队列在同一消费组不同消费者之间的负载均衡,其核心设计理念是在**一个消息消费队列在同一时间只允许被同一消费组内的一个消费者消费,一个消息消费者能同时消费多个消息队列**
+消息消费队列在**同一消费组不同消费者之间的负载均衡**,其核心设计理念是在**一个消息消费队列在同一时间只允许被同一消费组内的一个消费者消费,一个消息消费者能同时消费多个消息队列**
@@ -10268,13 +10272,14 @@ ConsumeRequest 是 ConsumeMessageOrderlyService 的内部类,是一个 Runnabl
生产流程:
-* 首先获取当前消息主题的发布信息,获取不到去 Namesrv 获取(默认有 TBW102),并将获取的到的路由数据转化为发布数据,**创建 MQ 队列**,客户端实例同样更新订阅数据,创建 MQ 队列,放入负载均衡服务 topicSubscribeInfoTable 中
+* 首先获取当前消息主题的发布信息,获取不到去 Namesrv 获取(默认有 TBW102),并将获取的到的路由数据转化为发布数据,**创建 MQ 队列在多个 Broker 组**(一组代表一主多从的 Broker 架构),客户端实例同样更新订阅数据,创建 MQ 队列,放入负载均衡服务 topicSubscribeInfoTable 中
* 然后从发布数据中选择一个 MQ 队列发送消息
* Broker 端通过 SendMessageProcessor 对发送的消息进行持久化处理,存储到 CommitLog。将重试次数过多的消息加入**死信队列**,将延迟消息的主题和队列修改为调度主题和调度队列 ID
* Broker 启动 ScheduleMessageService 服务会为每个延迟级别创建一个延迟任务,让延迟消息得到有效的处理,将到达交付时间的消息修改为原始主题的原始 ID 存入 CommitLog,消费者就可以进行消费了
消费流程:
+* 消息消费队列 ConsumerQueue 存储消息在 CommitLog 的索引,消费者通过该队列来读取消息实体内容,一个 MQ 就对应一个 CQ
* 首先通过负载均衡服务,将分配到当前消费者实例的 MQ 创建 PullRequest,并放入 PullMessageService 的本地阻塞队列内
* PullMessageService 循环从阻塞队列获取请求对象,发起拉消息请求,并创建 PullCallback 回调对象,将正常拉取的消息**提交到消费任务线程池**,并设置请求的下一次拉取位点,重新放入阻塞队列,形成闭环
* 消费任务服务对消费失败的消息进行回退,通过内部生产者实例发送回退消息,回退失败的消息会再次提交消费任务重新消费
@@ -10771,7 +10776,7 @@ CAP 理论指的是在一个分布式系统中,Consistency(一致性)、Av
CAP 三个基本需求,因为 P 是必须的,因此分布式系统选择就在 CP 或者 AP 中:
* 一致性:指数据在多个副本之间是否能够保持数据一致的特性,当一个系统在数据一致的状态下执行更新操作后,也能保证系统的数据仍然处于一致的状态
-* 可用性:指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果
+* 可用性:指系统提供的服务必须一直处于可用的状态,即使集群中一部分节点故障,对于用户的每一个操作请求总是能够在有限的时间内返回结果
* 分区容错性:分布式系统在遇到任何网络分区故障时,仍然能够保证对外提供服务,不会宕机,除非是整个网络环境都发生了故障
diff --git a/Java.md b/Java.md
index ad43393..23a9e8b 100644
--- a/Java.md
+++ b/Java.md
@@ -330,7 +330,7 @@ valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中
- Integer values between -128 and 127
- Character in the range \u0000 to \u007F (0 and 127)
-在 jdk 1.8 所有的数值类缓冲池中,**Integer 的缓存池 IntegerCache 很特殊,这个缓冲池的下界是 -128,上界默认是 127**,但是上界是可调的,在启动 JVM 时通过 `AutoBoxCacheMax=` 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.IntegerCache.high 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界
+在 jdk 1.8 所有的数值类缓冲池中,**Integer 的缓存池 IntegerCache 很特殊,这个缓冲池的下界是 -128,上界默认是 127**,但是上界是可调的,在启动 JVM 时通过 `AutoBoxCacheMax=` 来指定这个缓冲池的大小,该选项在 JVM 初始化的时候会设定一个名为 java.lang.Integer.IntegerCache 系统属性,然后 IntegerCache 初始化的时候就会读取该系统属性来决定上界
```java
Integer x = 100; // 自动装箱,底层调用 Integer.valueOf(1)
@@ -430,7 +430,7 @@ public static void main(String[] args) {
| 堆内存 | 存储对象或者数组,new 来创建的,都存储在堆内存 |
| 方法栈 | 方法运行时使用的内存,比如 main 方法运行,进入方法栈中执行 |
-**内存分配图**:Java 内存分配
+内存分配图:**Java 数组分配在堆内存**
* 一个数组内存图
@@ -2865,7 +2865,7 @@ public class MyArraysDemo {
1. 导入包:`import java.util.Random`
2. 创建对象:`Random r = new Random()`
3. 随机整数:`int num = r.nextInt(10)`
- * 解释:10 代表的是一个范围,如果括号写 10,产生的随机数就是 0 - 9,括号写 20 的随机数则是 0 - 19
+ * 解释:10 代表的是一个范围,如果括号写 10,产生的随机数就是 0 - 9,括号写 20 的随机数则是 0 - 19
* 获取 0 - 10:`int num = r.nextInt(10 + 1)`
4. 随机小数:`public double nextDouble()` 从范围 `0.0d` 至 `1.0d` (左闭右开),伪随机地生成并返回
@@ -6089,35 +6089,6 @@ public class ExceptionDemo{
5. 算术异常(数学操作异常):ArithmeticException
6. 数字转换异常:NumberFormatException
-```java
-public class ExceptionDemo {
- public static void main(String[] args) {
- System.out.println("程序开始。。。。。。");
- // 1.数组索引越界异常: ArrayIndexOutOfBoundsException。
- int[] arrs = {10 ,20 ,30};
- System.out.println(arrs[3]); //出现了数组索引越界异常。代码在此处直接执行死亡!
-
- // 2.空指针异常 : NullPointerException。
- String name = null ;
- System.out.println(name); // 直接输出没有问题
- System.out.println(name.length());//出现了空指针异常。代码直接执行死亡!
-
- /** 3.类型转换异常:ClassCastException。 */
- Object o = "齐天大圣";
- Integer s = (Integer) o; // 此处出现了类型转换异常。代码在此处直接执行死亡!
-
- /** 5.数学操作异常:ArithmeticException。 */
- int c = 10 / 0 ; // 此处出现了数学操作异常。代码在此处直接执行死亡!
-
- /** 6.数字转换异常: NumberFormatException。 */
- String num = "23aa";
- Integer it = Integer.valueOf(num); //出现了数字转换异常。代码在此处执行死亡!
-
- System.out.println("程序结束。。。。。。");
- }
-}
-```
-
****
@@ -8625,9 +8596,9 @@ public class AnnotationDemo{
}
}
-@Book(value = "《Java基础到精通》", price = 99.5, authors = {"波仔","波妞"})
+@Book(value = "《Java基础到精通》", price = 99.5, authors = {"张三","李四"})
class BookStore{
- @Book(value = "《Mybatis持久层框架》", price = 199.5, authors = {"dlei","播客"})
+ @Book(value = "《Mybatis持久层框架》", price = 199.5, authors = {"王五","小六"})
public void run(){
}
}
@@ -8642,47 +8613,6 @@ class BookStore{
-***
-
-
-
-### 注解模拟
-
-注解模拟写一个 Junit 框架的基本使用
-
-1. 定义一个自定义注解 MyTest,只能注解方法,存活范围一直都在。
-2. 定义若干个方法,只要有 @MyTest 注解的方法就能被触发执行,没有这个注解的方法不能执行!!
-
-```java
-public class TestDemo{
- @MyTest
- public void test01(){System.out.println("===test01===");}
- public void test02(){System.out.println("===test02===");}
- @MyTest
- public void test03(){System.out.println("===test03===");}
- @MyTest
- public void test04(){System.out.println("===test04===");}
-
- public static void main(String[] args) throws Exception {
- TestDemo t = new TestDemo();
- Class c = TestDemo.class;
- Method[] methods = c.getDeclaredMethods();
- for (Method method : methods) {
- if(method.isAnnotationPresent(MyTest.class)){
- method.invoke(t);
- }
- }
- }
-}
-
-@Target(ElementType.METHOD) // 只能注解方法!
-@Retention(RetentionPolicy.RUNTIME) // 一直都活着
-@interface MyTest{
-}
-```
-
-
-
****
@@ -8837,8 +8767,6 @@ XML 文件中常见的组成元素有:文档声明、元素、属性、注释、
#### DTD
-##### DTD 定义
-
DTD 是文档类型定义(Document Type Definition)。DTD 可以定义在 XML 文档中出现的元素、这些元素出现的次序、它们如何相互嵌套以及 XML 文档结构的其它详细信息。
DTD 规则:
@@ -8922,135 +8850,24 @@ DTD 规则:
* 代码
```dtd
-
- id ID #REQUIRED
- 编号 CDATA #IMPLIED
- 出版社 (清华|北大|传智播客) "传智播客"
- type CDATA #FIXED "IT"
+
+ id ID #REQUIRED
+ 编号 CDATA #IMPLIED
+ 出版社 (清华|北大) "清华"
+ type CDATA #FIXED "IT"
>
```
-****
-
-
-
-##### DTD 引入
-
-* 引入本地 dtd
-
- ```dtd
-
- ```
-
-* 在 xml 文件内部引入
-
- ```dtd
-
- ```
-
-* 引入网络 dtd
-
- ```dtd
-
- ```
-
-```dtd
-
-
-
-
-
-```
-
-```xml
-
-
-
-
- 张三
- 23
-
-
-
-```
-
-```xml-dtd
-
-
-
-
-
-
- ]>
-
-
-
- 张三
- 23
-
-
-```
-
-```dtd
-
-
-
-
-
- 张三
- 23
-
-
-```
-
-
-
-***
-
-
-
-##### DTD 实现
-
-persondtd.dtd 文件
-
-```dtd
-
-
-
-
-
-```
-
-```xml-dtd
-
-
-
-
-
- 张三
- 23
-
-
-
- 张三
- 23
-
-
-```
-
-
-
***
#### Schema
-##### XSD 定义
+XSD 定义:
1. Schema 语言也可作为 XSD(XML Schema Definition)
2. Schema 约束文件本身也是一个 XML 文件,符合 XML 的语法,这个文件的后缀名 .xsd
@@ -9058,13 +8875,7 @@ persondtd.dtd 文件
4. dtd 里面元素类型的取值比较单一常见的是 PCDATA 类型,但是在 Schema 里面可以支持很多个数据类型
5. **Schema 文件约束 XML 文件的同时也被别的文件约束着**
-
-
-***
-
-
-
-##### XSD 规则
+XSD 规则:
1. 创建一个文件,这个文件的后缀名为 .xsd
2. 定义文档声明
@@ -9111,88 +8922,6 @@ person.xsd
-****
-
-
-
-##### XSD 引入
-
-1. 在根标签上定义属性 xmlns="http://www.w3.org/2001/XMLSchema-instance"
-2. **通过 xmlns 引入约束文件的名称空间**
-3. 给某一个 xmlns 属性添加一个标识,用于区分不同的名称空间,格式为 `xmlns:标识="名称空间url"` ,标识可以是任意的,但是一般取值都是 xsi
-4. 通过 xsi:schemaLocation 指定名称空间所对应的约束文件路径,格式为 `xsi:schemaLocation = "名称空间url 文件路径`
-
-```scheme
-
-
- xmlns="http://www.seazean.cn/javase"
- xsi:schemaLocation="http://www.seazean.cn/javase person.xsd"
->
-
-
- 张三
- 23
-
-
-
-```
-
-
-
-****
-
-
-
-##### XSD 属性
-
-```scheme
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 张三
- 23
-
-
-
-```
-
***
@@ -9256,7 +8985,7 @@ public class Dom4JDemo {
JavaWeb开发教程
- 张孝祥
+ 张三
100.00元
@@ -9390,8 +9119,6 @@ public class Dom4JDemo {
System.out.println(bookEle.elementTextTrim("name")); // 去前后空格
System.out.println(bookEle.elementText("author"));
System.out.println(bookEle.elementTextTrim("author")); // 去前后空格
- System.out.println(bookEle.elementText("sale"));
- System.out.println(bookEle.elementTextTrim("sale")); // 去前后空格
// 6.先获取到子元素对象,再获取该文本值
Element bookNameEle = bookEle.element("name");
@@ -10596,23 +10323,26 @@ Return Address:存放调用该方法的 PC 寄存器的值
本地方法栈是为虚拟机执行本地方法时提供服务的
-JNI:Java Native Interface,通过使用 Java 本地接口书写程序,可以确保代码在不同的平台上方便移植
+JNI:Java Native Interface,通过使用 Java 本地接口程序,可以确保代码在不同的平台上方便移植
* 不需要进行 GC,与虚拟机栈类似,也是线程私有的,有 StackOverFlowError 和 OutOfMemoryError 异常
-
* 虚拟机栈执行的是 Java 方法,在 HotSpot JVM 中,直接将本地方法栈和虚拟机栈合二为一
-
* 本地方法一般是由其他语言编写,并且被编译为基于本机硬件和操作系统的程序
-
* 当某个线程调用一个本地方法时,就进入了不再受虚拟机限制的世界,和虚拟机拥有同样的权限
* 本地方法可以通过本地方法接口来**访问虚拟机内部的运行时数据区**
* 直接从本地内存的堆中分配任意数量的内存
* 可以直接使用本地处理器中的寄存器
-
-
-
+
+原理:将本地的 C 函数(如 foo)编译到一个共享库(foo.so)中,当正在运行的 Java 程序调用 foo 时,Java 解释器利用 dlopen 接口动态链接和加载 foo.so 后再调用该函数
+
+* dlopen 函数:Linux 系统加载和链接共享库
+* dlclose 函数:卸载共享库
+
+
+
+
图片来源:https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20%E8%99%9A%E6%8B%9F%E6%9C%BA.md
@@ -11494,7 +11224,7 @@ Java 语言提供了对象终止(finalization)机制来允许开发人员提
- 主要不足是**只使用了内存的一半**
- 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销都不小
-现在的商业虚拟机都采用这种收集算法**回收新生代**,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间
+现在的商业虚拟机都采用这种收集算法**回收新生代**,因为新生代 GC 频繁并且对象的存活率不高,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间
@@ -11710,7 +11440,7 @@ CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿
- 初始标记:使用 STW 出现短暂停顿,仅标记一下 GC Roots 能直接关联到的对象,速度很快
- 并发标记:进行 GC Roots 开始遍历整个对象图,在整个回收过程中耗时最长,不需要 STW,可以与用户线程并发运行
-- 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,比初始标记时间长但远比并发标记时间短,需要 STW(不停顿就会一直变化,采用写屏障 + 增量更新来避免漏标情况)
+- 重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象,比初始标记时间长但远比并发标记时间短,需要 STW(不停顿就会一直变化,采用写屏障 + 增量更新来避免漏标情况)
- 并发清除:清除标记为可以回收对象,**不需要移动存活对象**,所以这个阶段可以与用户线程同时并发的
Mark Sweep 会造成内存碎片,不把算法换成 Mark Compact 的原因:Mark Compact 算法会整理内存,导致用户线程使用的**对象的地址改变**,影响用户线程继续执行
@@ -11782,7 +11512,7 @@ G1 对比其他处理器的优点:
- 空间整合:
- CMS:标记-清除算法、内存碎片、若干次 GC 后进行一次碎片整理
- - G1:整体来看是基于标记 - 整理算法实现的收集器,从局部(Region 之间)上来看是基于复制算法实现的,两种算法都可以避免内存碎片
+ - G1:整体来看是**基于标记 - 整理算法实现**的收集器,从局部(Region 之间)上来看是基于复制算法实现的,两种算法都可以避免内存碎片
- **可预测的停顿时间模型(软实时 soft real-time)**:可以指定在 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒
@@ -12541,7 +12271,7 @@ Java 对象创建时机:
- 通过类的完全限定名称获取定义该类的二进制字节流(二进制字节码)
- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构(Java 类模型)
-- **在内存中生成一个代表该类的 Class 对象,作为该类在方法区中的各种数据的访问入口**
+- **将字节码文件加载至方法区后,在堆中生成一个代表该类的 Class 对象,作为该类在方法区中的各种数据的访问入口**
其中二进制字节流可以从以下方式中获取:
@@ -12550,8 +12280,6 @@ Java 对象创建时机:
- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类
- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 生成字节码
-将字节码文件加载至方法区后,会**在堆中**创建一个 java.lang.Class 对象,用来引用位于方法区内的数据结构,该 Class 对象是在加载类的过程中创建的,每个类都对应有一个 Class 类型的对象
-
方法区内部采用 C++ 的 instanceKlass 描述 Java 类的数据结构:
* `_java_mirror` 即 Java 的类镜像,例如对 String 来说就是 String.class,作用是把 class 暴露给 Java 使用
@@ -12638,7 +12366,7 @@ Java 对象创建时机:
public static final int value = 123;
```
-* Java 并不支持 boolean 类型,对于 boolean 类型,内部实现是 int,由于 int 的默认值是0,故 boolean 的默认值就是 false
+* Java 并不支持 boolean 类型,对于 boolean 类型,内部实现是 int,由于 int 的默认值是 0,故 boolean 的默认值就是 false
@@ -12791,7 +12519,7 @@ new 关键字会创建对象并复制 dup 一个对象引用,一个调用 loadClass(String name, boolean resolve)
* 如果不想破坏双亲委派模型,只需要重写 findClass 方法
* 如果想要去破坏双亲委派模型,需要去**重写 loadClass **方法
-* 引入线程**上下文类加载器**
+* 引入**线程上下文类加载器**
- Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:
+ Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的有 JDBC、JCE、JNDI 等。这些 SPI 接口由 Java 核心库来提供,而 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径 classpath 里,SPI 接口中的代码需要加载具体的实现类:
* SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的
* SPI 的实现类是由系统类加载器加载,引导类加载器是无法找到 SPI 的实现类,因为双亲委派模型中 BootstrapClassloader 无法委派 AppClassLoader 来加载类
@@ -14468,14 +14196,9 @@ public static int invoke(Object... args) {
在 JVM 中,将符号引用转换为直接引用有两种机制:
- 静态链接:当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变,将调用方法的符号引用转换为直接引用的过程称之为静态链接(类加载的解析阶段)
-- 动态链接:如果被调用的方法在编译期无法被确定下来,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此被称为动态链接(初始化后的解析阶段)
-
-对应方法的绑定(分配)机制:静态绑定和动态绑定。绑定是一个字段、方法或者类从符号引用被替换为直接引用的过程,仅发生一次:
-
-- 静态绑定:被调用的目标方法在编译期可知,且运行期保持不变,将这个方法与所属的类型进行绑定
-- 动态绑定:被调用的目标方法在编译期无法确定,只能在程序运行期根据实际的类型绑定相关的方法
+- 动态链接:被调用的方法在编译期无法被确定下来,只能在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此被称为动态链接(初始化后的解析阶段)
-* Java 编译器已经区分了重载的方法(静态绑定和动态绑定),因此可以认为虚拟机中不存在重载
+* 对应方法的绑定(分配)机制:静态绑定和动态绑定,编译器已经区分了重载的方法(静态绑定和动态绑定),因此可以认为虚拟机中不存在重载
非虚方法:
@@ -14509,7 +14232,7 @@ public static int invoke(Object... args) {
普通调用指令:
- invokestatic:调用静态方法
-- invokespecial:调用私有实例方法、构造器,和父类的实例方法或构造器,以及所实现接口的默认方法
+- invokespecial:调用私有方法、构造器,和父类的实例方法或构造器,以及所实现接口的默认方法
- invokevirtual:调用所有虚方法(虚方法分派)
- invokeinterface:调用接口方法
@@ -14541,9 +14264,6 @@ public static int invoke(Object... args) {
在编译过程中,虚拟机并不知道目标方法的具体内存地址,Java 编译器会暂时用符号引用来表示该目标方法,这一符号引用包括目标方法所在的类或接口的名字,以及目标方法的方法名和方法描述符
-* 对于静态绑定的方法调用而言,实际引用是一个指向方法的指针
-* 对于需要动态绑定的方法调用而言,实际引用则是一个方法表的索引
-
符号引用存储在方法区常量池中,根据目标方法是否为接口方法,分为接口符号引用和非接口符号引用:
```java
@@ -14645,8 +14365,6 @@ Java 虚拟机中关于方法重写的判定基于方法描述符,如果子类
2. 如果在类型 C 中找到与描述符和名称都相符的方法,则进行访问**权限校验**(私有的),如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回 java.lang.IllegalAccessError 异常
- IllegalAccessError:表示程序试图访问或修改一个属性或调用一个方法,这个属性或方法没有权限访问,一般会引起编译器异常。如果这个错误发生在运行时,就说明一个类发生了不兼容的改变
-
3. 找不到,就会按照继承关系从下往上依次对 C 的各个父类进行第二步的搜索和验证过程
4. 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError 异常
@@ -14670,15 +14388,15 @@ Java 虚拟机中关于方法重写的判定基于方法描述符,如果子类
虚方法表的执行过程:
-* 对于静态绑定的方法调用而言,实际引用将指向具体的目标方法
-* 对于动态绑定的方法调用而言,实际引用则是方法表的索引值,也就是方法的间接地址。Java 虚拟机获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法内存偏移量(指针)
+* 对于静态绑定的方法调用而言,实际引用是一个指向方法的指针
+* 对于动态绑定的方法调用而言,实际引用是方法表的索引值,也就是方法的间接地址。Java 虚拟机获取调用者的实际类型,并在该实际类型的虚方法表中,根据索引值获得目标方法内存偏移量(指针)
为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向一个记录该类方法的方法表。每个类中都有一个虚方法表,本质上是一个数组,每个数组元素指向一个当前类及其祖先类中非私有的实例方法
方法表满足以下的特质:
* 其一,子类方法表中包含父类方法表中的**所有方法**,并且在方法表中的索引值与父类方法表种的索引值相同
-* 其二,**非重写的方法指向父类的方法表项,与父类共享一个方法表项,重写的方法指向本身自己的实现**。所以这就是为什么多态情况下可以访问父类的方法。
+* 其二,**非重写的方法指向父类的方法表项,与父类共享一个方法表项,重写的方法指向本身自己的实现**,这就是为什么多态情况下可以访问父类的方法。
diff --git a/Prog.md b/Prog.md
index c0a54d0..2aee6d0 100644
--- a/Prog.md
+++ b/Prog.md
@@ -1505,7 +1505,7 @@ Object 类 API:
```java
public final void notify():唤醒正在等待对象监视器的单个线程。
public final void notifyAll():唤醒正在等待对象监视器的所有线程。
-public final void wait():导致当前线程等待,直到另一个线程调用该对象的notify()方法或 notifyAll()方法。
+public final void wait():导致当前线程等待,直到另一个线程调用该对象的 notify() 方法或 notifyAll()方法。
public final native void wait(long timeout):有时限的等待, 到n毫秒后结束等待,或是被唤醒
```
@@ -2539,9 +2539,7 @@ volatile 修饰的变量,可以禁用指令重排
##### 缓存一致
-使用 volatile 修饰的共享变量,总线会开启 **CPU 总线嗅探机制**来解决 JMM 缓存一致性问题,也就是共享变量在多线程中可见性的问题,实现 MESI 缓存一致性协议
-
-底层是通过汇编 lock 前缀指令,共享变量加了 lock 前缀指令就会进行缓存锁定,在线程修改完共享变量后写回主存,其他的 CPU 核心上运行的线程根据总线嗅探机制会修改其共享变量为失效状态,读取时会重新从主内存中读取最新的数据
+使用 volatile 修饰的共享变量,底层通过汇编 lock 前缀指令进行缓存锁定,在线程修改完共享变量后写回主存,其他的 CPU 核心上运行的线程通过 CPU 总线嗅探机制会修改其共享变量为失效状态,读取时会重新从主内存中读取最新的数据
lock 前缀指令就相当于内存屏障,Memory Barrier(Memory Fence)
@@ -2921,7 +2919,7 @@ CAS 特点:
CAS 缺点:
-- 循环时间长,开销大,因为执行的是循环操作,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,**使用 CAS 线程数不要超过 CPU 的核心数**
+- 执行的是循环操作,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,**使用 CAS 线程数不要超过 CPU 的核心数**,采用分段 CAS 和自动迁移机制
- 只能保证一个共享变量的原子操作
- 对于一个共享变量执行操作时,可以通过循环 CAS 的方式来保证原子操作
- 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候**只能用锁来保证原子性**
@@ -3470,7 +3468,7 @@ ABA 问题:当进行获取主内存值时,该内存值在写入主内存时
* `public AtomicStampedReference(V initialRef, int initialStamp)`:初始值和初始版本号
* 常用API:
- * ` public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)`:期望引用和期望版本号都一致才进行 CAS 修改数据
+ * ` public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)`:**期望引用和期望版本号都一致**才进行 CAS 修改数据
* `public void set(V newReference, int newStamp)`:设置值和版本号
* `public V getReference()`:返回引用的值
* `public int getStamp()`:返回当前版本号
@@ -3595,7 +3593,10 @@ public class TestFinal {
final 变量的赋值通过 putfield 指令来完成,在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0 的情况
-其他线程访问 final 修饰的变量**会复制一份放入栈中**,效率更高
+其他线程访问 final 修饰的变量
+
+* **复制一份放入栈中**直接访问,效率高
+* 大于 short 最大值会将其复制到类的常量池,访问时从常量池获取
@@ -4424,9 +4425,9 @@ ThreadLocal 内部解决方法:在 ThreadLocalMap 中的 set/getEntry 方法
##### 基本使用
-父子线程:**创建子线程的线程是父线程**,比如实例中的 main 线程就是父线程
+父子线程:创建子线程的线程是父线程,比如实例中的 main 线程就是父线程
-ThreadLocal 中存储的是线程的局部变量,如果想实现线程间局部变量传递可以使用 InheritableThreadLocal 类
+ThreadLocal 中存储的是线程的局部变量,如果想**实现线程间局部变量传递**可以使用 InheritableThreadLocal 类
```java
public static void main(String[] args) {
@@ -5650,7 +5651,7 @@ ExecutorService 类 API:
| 方法 | 说明 |
| ----------------------------------------------------- | ------------------------------------------------------------ |
-| void shutdown() | 线程池状态变为 SHUTDOWN,等待任务执行完后关闭线程池,不会接收新任务,但已提交任务会执行完,而且也可以添加线程(不绑定ren'wu) |
+| void shutdown() | 线程池状态变为 SHUTDOWN,等待任务执行完后关闭线程池,不会接收新任务,但已提交任务会执行完,而且也可以添加线程(不绑定任务) |
| List shutdownNow() | 线程池状态变为 STOP,用 interrupt 中断正在执行的任务,直接关闭线程池,不会接收新任务,会将队列中的任务返回 |
| boolean isShutdown() | 不在 RUNNING 状态的线程池,此执行者已被关闭,方法返回 true |
| boolean isTerminated() | 线程池状态是否是 TERMINATED,如果所有任务在关闭后完成,返回 true |
@@ -8251,7 +8252,7 @@ public void lock() {
}
```
-* 接下来进入 addWaiter 逻辑,构造 Node 队列,前置条件是当前线程获取锁失败,说明有线程占用了锁
+* 接下来进入 addWaiter 逻辑,构造 Node 队列(不是阻塞队列),前置条件是当前线程获取锁失败,说明有线程占用了锁
* 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认**正常状态**
* Node 的创建是懒惰的,其中第一个 Node 称为 **Dummy(哑元)或哨兵**,用来占位,并不关联线程
@@ -8262,7 +8263,7 @@ public void lock() {
// 将当前线程关联到一个 Node 对象上, 模式为独占模式
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
- // 快速入队,如果 tail 不为 null,说明存在阻塞队列
+ // 快速入队,如果 tail 不为 null,说明存在队列
if (pred != null) {
// 将当前节点的前驱节点指向 尾节点
node.prev = pred;
@@ -8305,7 +8306,7 @@ public void lock() {
-* 线程节点加入阻塞队列成功,进入 AbstractQueuedSynchronizer#acquireQueued 逻辑阻塞线程
+* 线程节点加入队列成功,进入 AbstractQueuedSynchronizer#acquireQueued 逻辑阻塞线程
* acquireQueued 会在一个自旋中不断尝试获得锁,失败后进入 park 阻塞
@@ -10758,7 +10759,7 @@ public class ExchangerDemo {
// 创建交换对象(信使)
Exchanger exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
- new ThreadA(exchanger).start();
+ new ThreadB(exchanger).start();
}
}
class ThreadA extends Thread{
diff --git a/README.md b/README.md
index 6083f8e..a013bf5 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,15 @@
+## 组内直招
+
+阿里巴巴 AliExpress 营销团队:https://aidc-jobs.alibaba.com/off-campus/position-detail?lang=zh&positionId=1040520
+
+联系邮箱:xizan.zhy@alibaba-inc.com
+
+
+
+
+
+## 仓库介绍
+
**Java** 学习笔记,记录作者从编程入门到深入学习的所有知识,每次学有所获都会更新笔记,排版布局**美观整洁**,希望对各位读者朋友有所帮助。
个人邮箱:imseazean@gmail.com
@@ -8,7 +20,7 @@
* Frame:Maven、Netty、RocketMQ、Zookeeper
* Java:JavaSE、JVM、Algorithm
* Prog:Concurrent、Network Programming
-* SSM:MyBatis、Spring、SpringMVC、SpringBoot
+* SSM:MyBatis、Spring、SpringMVC、SpringBoot、SpringCloud
* Tool:Git、Linux、Docker
* Web:HTML、CSS、HTTP、Servlet、JavaScript
@@ -17,7 +29,3 @@
* 推荐使用 Typora 阅读笔记,打开目录栏效果更佳。
* 所有的知识不保证权威性,如果各位朋友发现错误,欢迎与我讨论。
* 笔记的编写基于 Windows 平台,可能会因为平台的不同而造成空格、制表符的显示效果不同。
-
-字节跳动校园招聘:https://jobs.bytedance.com/campus/invite?referral_code=1VQUWCD
-
-
diff --git a/SSM.md b/SSM.md
index 6939b15..60035dc 100644
--- a/SSM.md
+++ b/SSM.md
@@ -2878,39 +2878,6 @@ PageInfo相关API:
## 概述
-### 框架
-
-框架源自于建筑学,隶属土木工程,后发展到软件工程领域
-
-软件工程框架:经过验证的,具有一定功能的,半成品软件
-
-- 经过验证
-
-- 具有一定功能
-
-- 半成品
-
-框架作用:
-
-* 提高开发效率
-* 增强可重用性
-
-* 提供编写规范
-* 节约维护成本
-* 解耦底层实现原理
-
-
-
-参考视频:https://space.bilibili.com/37974444
-
-
-
-****
-
-
-
-### Spring
-
Spring 是分层的 JavaSE/EE 应用 full-stack 轻量级开源框架

@@ -2928,6 +2895,10 @@ Spring 优点:

+参考视频:https://space.bilibili.com/37974444
+
+
+
***
@@ -13890,7 +13861,7 @@ SpringBoot 功能:
1. 使用 @ComponentScan 扫描 com.example.config 包
-2. 使用 @Import 注解,加载类,这些类都会被 Spring 创建并放入 ioc 容器,默认组件的名字就是**全类名**
+2. 使用 @Import 注解加载类,这些类都会被 Spring 创建并放入 ioc 容器,默认组件的名字就是**全类名**
3. 对 @Import 注解进行封装
@@ -13982,14 +13953,14 @@ Condition 是 Spring4.0 后引入的条件化配置接口,通过实现 Conditi
ConditionContext 类API:
-| 方法 | 说明 |
-| --------------------------------------------------- | ----------------------------- |
-| ConfigurableListableBeanFactory getBeanFactory() | 获取到 IOC 使用的 beanfactory |
-| ClassLoader getClassLoader() | 获取类加载器 |
-| Environment getEnvironment() | 获取当前环境信息 |
-| BeanDefinitionRegistry getRegistry() | 获取到 bean 定义的注册类 |
+| 方法 | 说明 |
+| ------------------------------------------------- | ----------------------------- |
+| ConfigurableListableBeanFactory getBeanFactory() | 获取到 IOC 使用的 beanfactory |
+| ClassLoader getClassLoader() | 获取类加载器 |
+| Environment getEnvironment() | 获取当前环境信息 |
+| BeanDefinitionRegistry getRegistry() | 获取到 bean 定义的注册类 |
-* ClassCondition
+* ClassCondition:
```java
public class ClassCondition implements Condition {
@@ -14013,7 +13984,7 @@ ConditionContext 类API:
}
```
-* UserConfig
+* UserConfig:
```java
@Configuration
@@ -14202,7 +14173,7 @@ public class Car {
-### 源码解析
+### 装配原理
#### 启动流程
@@ -14632,7 +14603,7 @@ ApplicationContextInitializer、SpringApplicationRunListener、CommandLineRunner
#### 文件类型
-SpringBoot 是基于约定的,很多配置都有默认值,如果想使用自己的配置替换默认配置,可以使用 application.properties 或者application.yml(application.yaml)进行配置
+SpringBoot 是基于约定的,很多配置都有默认值,如果想使用自己的配置替换默认配置,可以使用 application.properties 或者 application.yml(application.yaml)进行配置
* 默认配置文件名称:application
* 在同一级目录下优先级为:properties > yml > yaml
@@ -16192,7 +16163,3343 @@ SpringBoot 项目开发完毕后,支持两种方式部署到服务器:
```
+
+
+
+
+
+
+
+***
+
+
+
+
+
+# Cloud
+
+## 基本介绍
+
+SpringCloud 是分布式微服务的一站式解决方案,是多种微服务落地技术的集合体,俗称微服务全家桶
+
+
+
+
+
+参考文档:https://www.yuque.com/mrlinxi/pxvr4g/wcwd39
+
+
+
+
+
+***
+
+
+
+
+
+## 服务注册
+
+### Eureka
+
+#### 基本介绍
+
+Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理。Eureka 采用了 CS(Client-Server) 的设计架构,Eureka Server 是服务注册中心,系统中的其他微服务使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接
+
+
+
+* Eureka Server 提供服务注册服务:各个微服务节点通过配置启动后,会在 EurekaServer 中进行注册,EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,并且具有可视化界面
+
+* Eureka Client 通过注册中心进行访问:用于简化 Eureka Server的交互,客户端也具备一个内置的、使用轮询 (round-robin) 负载算法的负载均衡器。在应用启动后将会向 Eureka Server 发送心跳(默认周期为30秒),如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,将会从服务注册表中把这个服务节点移除(默认 90 秒)
+
+
+
+
+
+****
+
+
+
+#### 服务端
+
+服务器端主启动类增加 @EnableEurekaServer 注解,指定该模块作为 Eureka 注册中心的服务器
+
+构建流程如下:
+
+* 主启动类
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaServer // 表示当前是Eureka的服务注册中心
+ public class EurekaMain7001 {
+ public static void main(String[] args) {
+ SpringApplication.run(EurekaMain7001.class, args);
+ }
+ }
+ ```
+
+* 修改 pom 文件
+
+ ```xml
+ 1.x: server跟client合在一起
+
+ org.springframework.cloud
+ spring-cloud-starter-eureka
+
+ 2.x: server跟client分开
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-server
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+ ```
+
+* 修改 application.yml 文件
+
+ ```yaml
+ server:
+ port: 7001
+
+ eureka:
+ instance:
+ hostname: localhost # eureka服务端的实例名称
+ client:
+ # false表示不向注册中心注册自己。
+ register-with-eureka: false
+ # false表示自己端就是注册中心,职责就是维护服务实例,并不需要去检索服务
+ fetch-registry: false
+ service-url:
+ # 设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址。
+ defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
+ ```
+
+* 游览器访问 http://localhost:7001
+
+
+
+***
+
+
+
+#### 客户端
+
+##### 生产者
+
+服务器端主启动类需要增加 @EnableEurekaClient 注解,表示这是一个 Eureka 客户端,要注册进 EurekaServer 中
+
+* 主启动类:PaymentMain8001
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ public class PaymentMain8001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain8001.class, args);
+ }
+ }
+ ```
+
+* 修改 pom 文件:添加一个 Eureka-Client 依赖
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+ ```
+
+* 写 yml 文件
+
+ ```yaml
+ server:
+ port: 8001
+
+ eureka:
+ client:
+ # 表示将自己注册进EurekaServer默认为true
+ register-with-eureka: true
+ # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
+ fetch-registry: true
+ service-url:
+ defaultZone: http://localhost:7001/eureka
+ instance:
+ instance-id: payment8001 # 只暴露服务名,不带有主机名
+ prefer-ip-address: true # 访问信息有 IP 信息提示(鼠标停留在服务名称上时)
+ ```
+
+* 游览器访问 http://localhost:7001
+
+
+
+***
+
+
+
+##### 消费者
+
+* 主启动类:PaymentMain8001
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ @EnableDiscoveryClient
+ public class PaymentMain8001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain8001.class, args);
+ }
+ }
+ ```
+
+* pom 文件同生产者
+
+* 写 yml 文件
+
+ ```yaml
+ server:
+ port: 80
+
+ # 微服务名称
+ spring:
+ application:
+ name: cloud-order-service
+ eureka:
+ client:
+ register-with-eureka: true
+ fetch-registry: true
+ service-url:
+ defaultZone: http://localhost:7001/eureka
+ ```
+
+* 浏览器访问 http://localhost:7001
+
+ 
+
+
+
+***
+
+
+
+#### 集群构建
+
+##### 服务端
+
+Server 端高可用集群原理:实现负载均衡和故障容错,互相注册,相互守望
+
+
+
+多台 Eureka 服务器,每一台 Eureka 服务器需要有自己的主机名,同时各服务器需要相互注册
+
+* Eureka1:
+
+ ```yaml
+ server:
+ port: 7001
+
+ eureka:
+ instance:
+ hostname: eureka7001.com
+ client:
+ register-with-eureka: false
+ fetch-registry: false
+ service-url:
+ # 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
+ # 单机就是自己
+ # defaultZone: http://eureka7001.com:7001/eureka/
+ # 集群指向其他eureka
+ #defaultZone: http://eureka7002.com:7002/eureka/
+ # 写成这样可以直接通过可视化页面跳转到7002
+ defaultZone: http://eureka7002.com:7002/
+ ```
+
+* Eureka2:
+
+ ```yaml
+ server:
+ port: 7002
+
+ eureka:
+ instance:
+ hostname: eureka7002.com
+ client:
+ register-with-eureka: false
+ fetch-registry: false
+ service-url:
+ #写成这样可以直接通过可视化页面跳转到7001
+ defaultZone: http://eureka7001.com:7001/
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaServer
+ public class EurekaMain7002 {
+ public static void main(String[] args) {
+ SpringApplication.run(EurekaMain7002.class, args);
+ }
+ }
+ ```
+
+* 访问 http://eureka7001.com:7001 和 http://eureka7002.com:7002:
+
+ 
+
+* RPC 调用:controller.OrderController
+
+ ```java
+ @RestController
+ @Slf4j
+ public class OrderController {
+ public static final String PAYMENT_URL = "http://localhost:8001";
+
+ @Autowired
+ private RestTemplate restTemplate;
+
+ // CommonResult 是一个公共的返回类型
+ @GetMapping("/consumer/payment/get/{id}")
+ public CommonResult getPayment(@PathVariable("id") long id) {
+ // 返回对象为响应体中数据转化成的对象,基本上可以理解为JSON
+ return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
+ }
+ }
+ ```
+
+
+
+
+
+***
+
+
+
+##### 生产者
+
+构建 PaymentMain8001 的服务集群
+
+* 主启动类
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ @EnableDiscoveryClient
+ public class PaymentMain8002 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain8002.class, args);
+ }
+ }
+ ```
+
+* 写 yml 文件:端口修改,并且 spring.application.name 均为 cloud-payment-service
+
+ ```yaml
+ server:
+ port: 8002
+ spring:
+ application:
+ name: cloud-payment-service
+
+ eureka:
+ client:
+ # 表示将自己注册进EurekaServer默认为true
+ register-with-eureka: true
+ # 表示可以从Eureka抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
+ fetch-registry: true
+ service-url:
+ defaultZone: http://localhost:7001/eureka
+ ```
+
+
+
+***
+
+
+
+##### 负载均衡
+
+消费者端的 Controller
+
+```java
+// public static final String PAYMENT_URL = "http://localhost:8001";
+public static final String PAYMENT_URL = "http://localhost:8002";
+```
+
+由于已经建立了生产者集群,所以可以进行负载均衡的操作:
+
+* Controller:只修改 PAYMENT_URL 会报错,因为 CLOUD-PAYMENT-SERVICE 对应多个微服务,需要规则来判断调用哪个端口
+
+ ```java
+ public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
+ ```
+
+* 使用 @LoadBlanced 注解赋予 RestTemplate 负载均衡的能力,增加 config.ApplicationContextConfig 文件:
+
+ ```java
+ @Configuration
+ public class ApplicationContextConfig {
+ @Bean
+ @LoadBalanced
+ public RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+ }
+ ```
+
+
+
+****
+
+
+
+#### 服务发现
+
+服务发现:对于注册进 Eureka 里面的微服务,可以通过服务发现来获得该服务的信息
+
+* 主启动类增加注解 @EnableDiscoveryClient:
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ @EnableDiscoveryClient
+ public class PaymentMain8001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain8001.class, args);
+ }
+ }
+ ```
+
+* 修改生产者的 Controller
+
+ ```java
+ @RestController
+ @Slf4j
+ public class PaymentController {
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ @GetMapping(value = "/payment/discovery")
+ public Object discovery() {
+ List services = discoveryClient.getServices();
+ for (String service : services) {
+ log.info("**** element:" + service);
+ }
+
+ List instances = discoveryClient.getInstances("PAYMENT-SERVICE");
+ for (ServiceInstance instance : instances) {
+ log.info(instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort());
+ }
+ return this.discoveryClient;
+ }
+ }
+ ```
+
+
+
+***
+
+
+
+#### 自我保护
+
+保护模式用于客户端和 EurekaServer 之间存在网络分区场景下的保护,一旦进入保护模式 EurekaServer 将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,属于 CAP 里面的 AP 思想(可用性和分区容错性)
+
+
+
+如果一定时间内丢失大量该微服务的实例,这时 Eureka 就会开启自我保护机制,不会剔除该服务。 因为这个现象可能是因为网络暂时不通,出现了 Eureka 的假死、拥堵、卡顿,客户端恢复后还能正常发送心跳
+
+禁止自我保护:
+
+* Server:
+
+ ```yaml
+ eureka:
+ server:
+ # 关闭自我保护机制,不可用的服务直接删除
+ enable-self-preservation: false
+ eviction-interval-timer-in-ms: 2000
+ ```
+
+* Client:
+
+ ```yaml
+ eureka:
+ instance:
+ # Eureka客户端向服务端发送心跳的时间间隔默认30秒
+ lease-renewal-interval-in-seconds: 1
+ # Eureka服务端在收到最后一次心跳后,90s没有收到心跳,剔除服务
+ lease-expiration-duration-in-seconds: 2
+ ```
+
+
+
+
+
+****
+
+
+
+
+
+### Consul
+
+#### 基本介绍
+
+Consul 是开源的分布式服务发现和配置管理系统,采用 Go 语言开发,官网:https://developer.hashicorp.com/consul
+
+* 提供了微服务系统中心的服务治理,配置中心,控制总线等功能
+* 基于 Raft 协议,支持健康检查,同时支持 HTTP 和 DNS 协议支持跨数据中心的 WAN 集群
+* 提供图形界面
+
+下载 Consul 后,运行指令:`consul -version`
+
+```bash
+D:\Program Files\Java>consul -version
+Consul v1.15.1
+Revision 7c04b6a0
+Build Date 2023-03-07T20:35:33Z
+Protocol 2 spoken by default, understands 2 to 3 (.....)
+```
+
+启动命令:
+
+```bash
+consul agent -dev
+```
+
+访问浏览器:http://localhost:8500/
+
+
+
+中文文档:https://www.springcloud.cc/spring-cloud-consul.html
+
+
+
+***
+
+
+
+#### 基本使用
+
+无需 Server 端代码的编写
+
+生产者:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-consul-discovery
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ ###consul 服务端口号
+ server:
+ port: 8006
+
+ spring:
+ application:
+ name: consul-provider-payment
+ ####consul注册中心地址
+ cloud:
+ consul:
+ host: localhost
+ port: 8500
+ discovery:
+ service-name: ${spring.application.name}
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class PaymentMain8006 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain8006.class, args);
+ }
+ }
+ ```
+
+消费者:
+
+* application.yml:
+
+ ```yaml
+ ###consul服务端口号
+ server:
+ port: 80
+
+ spring:
+ application:
+ name: cloud-consumer-order
+ ####consul注册中心地址
+ cloud:
+ consul:
+ host: localhost
+ port: 8500
+ discovery:
+ #hostname: 127.0.0.1
+ service-name: ${spring.application.name}
+ ```
+
+* 主启动类:同生产者
+
+* 配置类:
+
+ ```java
+ @Configuration
+ public class ApplicationContextConfig {
+ @Bean
+ @LoadBalanced
+ public RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+ }
+ ```
+
+* 业务类 Controller:
+
+ ```java
+ @RestController
+ @Slf4j
+ public class OrderConsulController {
+ public static final String INVOKE_URL = "http://cloud-provider-pament";
+
+ @Resource
+ private RestTemplate restTemplate;
+
+ @GetMapping("/consumer/payment/consul")
+ public String paymentInfo() {
+ return restTemplate.getForObject(INVOKE_URL, String.class);
+ }
+ }
+ ```
+
+
+
+
+
+****
+
+
+
+
+
+## 服务调用
+
+### Ribbon
+
+#### 基本介绍
+
+SpringCloud Ribbon 是基于 Netflix Ribbon 实现的一套负载均衡工具,提供客户端的软件负载均衡算法和服务调用,Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等
+
+官网: https://github.com/Netflix/ribbon/wiki/Getting-Started (已进入维护模式,未来替换为 Load Banlancer)
+
+负载均衡 Load Balance (LB) 就是将用户的请求平摊的分配到多个服务上,从而达到系统的 HA(高可用)
+
+**常见的负载均衡算法:**
+
+- 轮询:为请求选择健康池中的第一个后端服务器,然后按顺序往后依次选择
+
+- 最小连接:优先选择连接数最少,即压力最小的后端服务器,在会话较长的情况下可以采取这种方式
+
+- 散列:根据请求源的 IP 的散列(hash)来选择要转发的服务器,可以一定程度上保证特定用户能连接到相同的服务器,如果应用需要处理状态而要求用户能连接到和之前相同的服务器,可以采取这种方式
+
+Ribbon 本地负载均衡客户端与 Nginx 服务端负载均衡区别:
+
+- Nginx 是服务器负载均衡,客户端所有请求都会交给 Nginx,然后由 Nginx 实现转发请求,即负载均衡是由服务端实现的
+- Ribbon 本地负载均衡,在调用微服务接口时会在注册中心上获取注册信息服务列表,然后缓存到 JVM 本地,从而在本地实现 RPC 远程服务调用技术
+
+集中式 LB 和进程内 LB 的对比:
+
+* 集中式 LB:在服务的消费方和提供方之间使用独立的 LB 设施(如 Nginx),由该设施把访问请求通过某种策略转发至服务的提供方
+* 进程内 LB:将 LB 逻辑集成到消费方,消费方从服务注册中心获知有哪些服务可用,然后从中选择出一个服务器,Ribbon 属于该类
+
+
+
+***
+
+
+
+#### 工作流程
+
+Ribbon 是一个软负载均衡的客户端组件
+
+
+
+- 第一步先选择 EurekaServer,优先选择在同一个区域内负载较少的 Server
+- 第二步根据用户指定的策略,再从 Server 取到的服务注册列表中选择一个地址
+
+
+
+***
+
+
+
+#### 核心组件
+
+Ribbon 核心组件 IRule 接口,主要实现类:
+
+- RoundRobinRule:轮询
+- RandomRule:随机
+- RetryRule:先按照 RoundRobinRule 的策略获取服务,如果获取服务失败则在指定时间内会进行重试
+- WeightedResponseTimeRule:对 RoundRobinRule 的扩展,响应速度越快的实例选择权重越大,越容易被选择
+- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
+- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
+- ZoneAvoidanceRule:默认规则,复合判断 Server 所在区域的性能和 Server 的可用性选择服务器
+
+
+
+注意:官方文档明确给出了警告,自定义负载均衡配置类不能放在 @ComponentScan 所扫描的当前包下以及子包下
+
+更换负载均衡算法方式:
+
+* 自定义负载均衡配置类 MySelfRule:
+
+ ```java
+ @Configuration
+ public class MySelfRule {
+ @Bean
+ public IRule myRule() {
+ return new RandomRule();//定义为随机负载均衡算法
+ }
+ }
+ ```
+
+* 主启动类添加 @RibbonCilent 注解
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ // 指明访问的服务CLOUD-PAYMENT-SERVICE,以及指定负载均衡策略
+ @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration= MySelfRule.class)
+ public class OrderMain80 {
+ public static void main(String[] args) {
+ SpringApplication.run(OrderMain80.class, args);
+ }
+ }
+ ```
+
+
+
+
+
+****
+
+
+
+
+
+### OpenFeign
+
+#### 基本介绍
+
+Feign 是一个声明式 WebService 客户端,能让编写 Web 客户端更加简单,只要创建一个接口并添加注解 @Feign 即可,可以与 Eureka 和 Ribbon 组合使用支持负载均衡,所以一般**用在消费者端**
+
+OpenFeign 在 Feign 的基础上支持了 SpringMVC 注解,并且 @FeignClient 注解可以解析 @RequestMapping 注解下的接口,并通过动态代理的方式产生实现类,在实现类中做负载均衡和服务调用
+
+优点:利用 RestTemplate 对 HTTP 请求的封装处理,形成了一套模版化的调用方法。但是对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以一个微服务接口上面标注一个 @Feign 注解,就可以完成包装依赖服务的调用
+
+
+
+
+
+****
+
+
+
+#### 基本使用
+
+@FeignClient("provider name") 注解使用规则:
+
+* 声明的方法签名必须和 provider 微服务中的 controller 中的方法签名一致
+* 如果需要传递参数,那么 `@RequestParam` 、`@RequestBody` 、`@PathVariable` 也需要加上
+
+改造消费者服务
+
+* 引入 pom 依赖:OpenFeign 整合了 Ribbon,具有负载均衡的功能
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+ ```
+
+* application.yml:不将其注册到 Eureka 作为微服务
+
+ ```yaml
+ server:
+ port: 80
+
+ eureka:
+ client:
+ # 表示不将其注入Eureka作为微服务,不作为Eureak客户端了,而是作为Feign客户端
+ register-with-eureka: false
+ service-url:
+ # 集群版
+ defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
+ ```
+
+* 主启动类:开启 Feign
+
+ ```java
+ @SpringBootApplication
+ @EnableFeignClients //不作为Eureak客户端了,而是作为Feign客户端
+ public class OrderOpenFeignMain80 {
+ public static void main(String[] args) {
+ SpringApplication.run(OrderOpenFeignMain80.class, args);
+ }
+
+ }
+ ```
+
+* 新建 Service 接口:PaymentFeignService 接口和 @FeignClient 注解,完成 Feign 的包装调用
+
+ ```java
+ @Component
+ @FeignClient(value = "CLOUD-PAYMENT-SERVICE") // 作为一个Feign功能绑定的的接口
+ public interface PaymentFeignService {
+ @GetMapping(value = "/payment/get/{id}")
+ public CommonResult getPaymentById(@PathVariable("id") long id);
+
+ @GetMapping("/payment/feign/timeout")
+ public String paymentFeignTimeout();
+ }
+ ```
+
+* Controller:
+
+ ```java
+ @RestController
+ @Slf4j
+ public class OrderFeignController {
+ @Autowired
+ private PaymentFeignService paymentFeignService;
+
+ @GetMapping("/consumer/payment/get/{id}")
+ public CommonResult getPayment(@PathVariable("id") long id) {
+ // 返回对象为响应体中数据转化成的对象,基本上可以理解为JSON
+ return paymentFeignService.getPaymentById(id);
+ }
+
+ @GetMapping("/consumer/payment/feign/timeout")
+ public String paymentFeignTimeout() {
+ // openfeign-ribbon,客户端一般默认等待1s
+ return paymentFeignService.paymentFeignTimeout();
+ }
+ }
+
+ ```
+
+
+
+***
+
+
+
+#### 超时问题
+
+Feign 默认是支持 Ribbon,Feign 客户端的负载均衡和超时控制都由 Ribbon 控制
+
+设置 Feign 客户端的超时等待时间:
+
+```yaml
+ribbon:
+ #指的是建立连接后从服务器读取到可用资源所用的时间
+ ReadTimeout: 5000
+ #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
+ ConnectTimeout: 5000
+```
+
+演示超时现象:OpenFeign 默认等待时间为 1 秒钟,超过后会报错
+
+* 服务提供方 Controller:
+
+ ```java
+ @GetMapping("/payment/feign/timeout")
+ public String paymentFeignTimeout() {
+ try {
+ TimeUnit.SECONDS.sleep(3);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return serverPort;
+ }
+ ```
+
+* 消费者 PaymentFeignService 和 OrderFeignController 参考上一小节代码
+
+* 测试报错:
+
+ !](C:\Users\Seazean\Desktop\123\Cloud-OpenFeign超时错误.png)
+
+
+
+***
+
+
+
+#### 日志级别
+
+Feign 提供了日志打印功能,可以通过配置来调整日志级别,从而了解 Feign 中 HTTP 请求的细节
+
+| NONE | 默认的,不显示任何日志 |
+| ------- | --------------------------------------------------------- |
+| BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
+| HEADERS | 除了 BASIC 中定义的信息之外,还有请求和响应的头信息 |
+| FULL | 除了 HEADERS 中定义的信息外,还有请求和响应的正文及元数据 |
+
+配置在消费者端
+
+* 新建 config.FeignConfig 文件:配置日志 Bean
+
+ ```java
+ @Configuration
+ public class FeignConfig {
+ @Bean
+ Logger.Level feignLoggerLevel() {
+ return Logger.Level.FULL;
+ }
+ }
+ ```
+
+* application.yml:
+
+ ```yaml
+ logging:
+ level:
+ # feign 日志以什么级别监控哪个接口
+ com.atguigu.springcloud.service.PaymentFeignService: debug
+ ```
+
+* Debug 后查看后台日志
+
+
+
+
+
+****
+
+
+
+
+
+## 服务熔断
+
+### Hystrix
+
+#### 基本介绍
+
+Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖会出现调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
+
+断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间地占用,避免了故障在分布式系统中的蔓延,乃至雪崩
+
+* 服务降级 Fallback:系统不可用时需要一个兜底的解决方案或备选响应,向调用方返回一个可处理的响应
+* 服务熔断 Break:达到最大服务访问后,直接拒绝访问
+* 服务限流 Flowlimit:高并发操作时严禁所有请求一次性过来拥挤,一秒钟 N 个,有序排队进行
+
+
+
+官方文档:https://github.com/Netflix/Hystrix/wiki/How-To-Use
+
+
+
+
+
+****
+
+
+
+#### 服务降级
+
+##### 案例构建
+
+生产者模块:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-hystrix
+
+ ```
+
+* 主启动类:开启 Feign
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ @EnableCircuitBreaker // 降级使用
+ public class PaymentHystrixMain8001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentHystrixMain8001.class, args);
+ }
+ }
+ ```
+
+* Controller:
+
+ ```java
+ @RestController
+ @Slf4j
+ public class PaymentController {
+ @Resource
+ private PaymentService paymentService;
+ @Value("${server.port}")
+ private String serverPort;
+
+ // 正常访问
+ @GetMapping("/payment/hystrix/ok/{id}")
+ private String paymentInfo_Ok(@PathVariable("id") Integer id) {
+ return paymentService.paymentInfo_Ok(id);
+ }
+ // 超时
+ @GetMapping("/payment/hystrix/timeout/{id}")
+ private String paymentInfo_Timeout(@PathVariable("id") Integer id) {
+ // service 层有 Thread.sleep() 操作,保证超时
+ return paymentService.paymentInfo_Timeout(id);
+ }
+ }
+ ```
+
+* Service:
+
+ ```java
+ @Service
+ public class PaymentService {
+ public String paymentInfo_Ok(Integer id) {
+ return "线程池: " + Thread.currentThread().getName() + "paymentInfo_OK, id: " + id";
+ }
+
+ public String paymentInfo_Timeout(Integer id) {
+ int timeNumber = 3;
+ try {
+ TimeUnit.SECONDS.sleep(timeNumber);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return "线程池: " + Thread.currentThread().getName() + " payment_Timeout, id: " + id;
+ }
+ }
+ ```
+
+* jmeter 压测两个接口,发现接口 paymentInfo_Ok 也变的卡顿
+
+消费者模块:
+
+* Service 接口:
+
+ ```java
+ @Component
+ @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
+ public interface PaymentHystrixService {
+ @GetMapping("/payment/hystrix/ok/{id}")
+ public String paymentInfo_Ok(@PathVariable("id") Integer id);
+
+ @GetMapping("/payment/hystrix/timeout/{id}")
+ public String paymentInfo_Timeout(@PathVariable("id") Integer id);
+ }
+ ```
+
+* Controller:
+
+ ```java
+ @RestController
+ @Slf4j
+ public class OrderHystirxController {
+ @Resource
+ PaymentHystrixService paymentHystrixService;
+
+ @GetMapping("/consumer/payment/hystrix/ok/{id}")
+ public String paymentInfo_Ok(@PathVariable("id") Integer id) {
+ return paymentHystrixService.paymentInfo_Ok(id);
+ }
+
+ @GetMapping("/consumer/payment/hystrix/timeout/{id}")
+ public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
+ return paymentHystrixService.paymentInfo_Timeout(id);
+ }
+ }
+ ```
+
+* 测试:使用的是 Feign 作为客户端,默认 1s 没有得到响应就会报超时错误,进行并发压测
+
+* 解决:
+
+ * 超时导致服务器变慢(转圈):超时不再等待
+ * 出错(宕机或程序运行出错):出错要有兜底
+
+
+
+****
+
+
+
+##### 降级操作
+
+生产者端和消费者端都可以进行服务降级,使用 @HystrixCommand 注解指定降级后的方法
+
+生产者端:主启动类添加新注解 @EnableCircuitBreaker,业务类(Service)方法进行如下修改,
+
+```java
+// 模拟拥堵的情况
+@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
+ //规定这个线程的超时时间是3s,3s后就由fallbackMethod指定的方法“兜底”(服务降级)
+ @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value = "3000")
+})
+public String paymentInfo_Timeout(Integer id) {
+ // 超时或者出错
+}
+
+public String paymentInfo_TimeoutHandler(Integer id) {
+ return "线程池:" + Thread.currentThread().getName() + " paymentInfo_TimeoutHandler, id: " + id";
+}
+```
+
+服务降级的方法和业务处理的方法混杂在了一块,耦合度很高,并且每个方法配置一个服务降级方法
+
+- 在业务类Controller上加 @DefaultProperties(defaultFallback = "method_name") 注解
+- 在需要服务降级的方法上标注 @HystrixCommand 注解,如果 @HystrixCommand 里没有指明 fallbackMethod,就默认使用 @DefaultProperties 中指明的降级服务
+
+```java
+@RestController
+@Slf4j
+@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
+public class OrderHystrixController {
+ @Resource
+ PaymentHystrixService paymentHystrixService;
+
+ @GetMapping("/consumer/payment/hystrix/ok/{id}")
+ public String paymentInfo_Ok(@PathVariable("id") Integer id) {
+ return paymentHystrixService.paymentInfo_OK(id);
+ }
+
+ @HystrixCommand
+ public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
+ return paymentHystrixService.paymentInfo_Timeout(id);
+ }
+
+ public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
+ return "fallback";
+ }
+
+ // 下面是全局fallback方法
+ public String payment_Global_FallbackMethod() {
+ return "Global fallback";
+ }
+}
+```
+
+客户端调用服务端,遇到服务端宕机或关闭等极端情况,为 Feign 客户端定义的接口添加一个服务降级实现类即可实现解耦
+
+* application.yml:配置文件中开启了 Hystrix
+
+ ```yaml
+ # 用于服务降级 在注解 @FeignClient中添加fallbackFactory属性值
+ feign:
+ hystrix:
+ enabled: true #在Feign中开启Hystrix
+ ```
+
+* Service:统一为接口里面的方法进行异常处理,服务异常找 PaymentFallbackService,来统一进行服务降级的处理
+
+ ```java
+ @Component
+ @FeignClient(value = "PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
+ public interface PaymentHystrixService {
+
+ @GetMapping("/payment/hystrix/ok/{id}")
+ public String paymentInfo_OK(@PathVariable("id") Integer id);
+
+ @GetMapping("/payment/hystrix/timeout/{id}")
+ public String paymentInfo_Timeout(@PathVariable("id") Integer id);
+ }
+ ```
+
+* PaymentFallbackService:
+
+ ```java
+ @Component
+ public class PaymentFallbackService implements PaymentHystrixService {
+ @Override
+ public String paymentInfo_OK(Integer id) {
+ return "------PaymentFallbackService-paymentInfo_Ok, fallback";
+ }
+
+ @Override
+ public String paymentInfo_Timeout(Integer id) {
+ return "------PaymentFallbackService-paymentInfo_Timeout, fallback";
+ }
+ }
+ ```
+
+
+
+***
+
+
+
+#### 服务熔断
+
+##### 熔断类型
+
+熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息
+
+Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省时 5 秒内 20 次调用失败,就会启动熔断机制;当检测到该节点微服务调用响应正常后(检测方式是尝试性放开请求),自动恢复调用链路
+
+- 熔断打开:请求不再进行调用当前服务,再有请求调用时将不会调用主逻辑,而是直接调用降级 fallback。实现了自动的发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果。内部设置时钟一般为 MTTR(Mean time to repair,平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
+- 熔断关闭:熔断关闭不会对服务进行熔断,服务正常调用
+- 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断,反之继续熔断
+
+
+
+
+
+****
+
+
+
+##### 熔断操作
+
+涉及到断路器的四个重要参数:**快照时间窗、请求总数阀值、窗口睡眠时间、错误百分比阀值**
+
+- circuitBreaker.enabled:是否开启断路器
+- metrics.rollingStats.timeInMilliseconds:快照时间窗口,断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒
+- circuitBreaker.requestVolumeThreshold:请求总数阀值,该属性设置在快照时间窗内(默认 10s)使断路器跳闸的最小请求数量(默认是 20),如果 10s 内请求数小于设定值,就算请求全部失败也不会触发断路器
+- circuitBreaker.sleepWindowInMilliseconds:窗口睡眠时间,短路多久以后开始尝试是否恢复进入半开状态,默认 5s
+- circuitBreaker.errorThresholdPercentage:错误百分比阀值,失败率达到多少后将断路器打开
+
+```java
+ //总的意思就是在n(10)毫秒内的时间窗口期内,m次请求中有p% (60%)的请求失败了,那么断路器启动
+@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
+ @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
+ @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
+ @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
+ @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
+})
+public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
+ if (id < 0) {
+ throw new RuntimeException("******id 不能负数");
+ }
+ String serialNumber = IdUtil.simpleUUID(); // 等价于UUID.randomUUID().toString()
+
+ return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
+}
+```
+
+* 开启:满足一定的阈值(默认 10 秒内超过 20 个请求次数)、失败率达到阈值(默认 10 秒内超过 50% 的请求失败)
+* 关闭:一段时间之后(默认是 5 秒),断路器是半开状态,会让其中一个请求进行转发,如果成功断路器会关闭,反之继续开启
+
+
+
+
+
+***
+
+
+
+#### 工作流程
+
+具体工作流程:
+
+1. 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象
+
+2. 命令执行,其中 HystrixComand 实现了下面前两种执行方式,而 HystrixObservableCommand 实现了后两种执行方式
+
+ * execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常
+
+ * queue():异步执行, 直接返回 一个 Future 对象, 其中包含了服务执行结束时要返回的单一结果对象
+
+ * observe():返回 Observable 对象,代表了操作的多个结果,它是一个 Hot Obserable(不论事件源是否有订阅者,都会在创建后对事件进行发布,所以对于 Hot Observable 的每个订阅者都有可能是从事件源的中途开始的,并可能只是看到了整个操作的局部过程)
+
+ * toObservable():同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个 Cold Observable(没有订阅者的时候并不会发布事件,而是进行等待,直到有订阅者之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)
+
+3. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以 Observable 对象的形式返回
+4. 检查断路器是否为打开状态,如果断路器是打开的,那么 Hystrix 不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)
+5. 线程池/请求队列/信号量是否占满,如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池时)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第 8 步)
+6. Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务
+ * HystrixCommand.run():返回一个单一的结果,或者抛出异常
+ * HystrixObservableCommand.construct():返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知
+7. Hystrix会将"成功"、"失败"、"拒绝"、"超时"等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"
+8. 当命令执行失败的时候,Hystrix 会进入 fallback 尝试回退处理,通常也称该操作为"服务降级",而能够引起服务降级情况:
+ * 第 4 步:当前命令处于"熔断/短路"状态,断路器是打开的时候
+ * 第 5 步:当前命令的线程池、请求队列或 者信号量被占满的时候
+ * 第 6 步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候
+9. 当 Hystrix 命令执行成功之后, 它会将处理结果直接返回或是以 Observable 的形式返回
+
+注意:如果、没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据,而是通过 onError 方法通知命令立即中断请求,并通过 onError() 方法将引起命令失败的异常发送给调用者
+
+
+
+
+
+官方文档:https://github.com/Netflix/Hystrix/wiki/How-it-Works
+
+
+
+
+
+****
+
+
+
+#### 服务监控
+
+Hystrix 提供了准实时的调用监控(Hystrix Dashboard),Hystrix 会持续的记录所有通过 Hystrix 发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等,Netflix 通过 `hystrix-metrics-event-stream` 项目实现了对以上指标的监控,Spring Cloud 提供了 Hystrix Dashboard 的整合,对监控内容转化成可视化页面
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-hystrix-dashboard
+
+ ```
+
+* application.yml:只需要端口即可
+
+ ```yaml
+ server:
+ port: 9001
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableHystrixDashboard // 开启Hystrix仪表盘
+ public class HystrixDashboardMain9001 {
+ public static void main(String[] args) {
+ SpringApplication.run(HystrixDashboardMain9001.class, args);
+ }
+ }
+ ```
+
+* 所有微服务(生产者)提供类 8001/8002/8003 都需要监控依赖配置
+
+ ```xml
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+ ```
+
+* 启动测试:http://localhost:9001/hystrix
+
+
+
+* 新版本 Hystrix 需要在需要监控的微服务端的主启动类中指定监控路径,不然会报错
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient // 本服务启动后会自动注册进eureka服务中
+ @EnableCircuitBreaker // 对hystrixR熔断机制的支持
+ public class PaymentHystrixMain8001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentHystrixMain8001.class, args);
+ }
+
+ /** ======================================需要添加的代码==================
+ *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
+ *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
+ *只要在自己的项目里配置上下面的servlet就可以了
+ */
+ @Bean
+ public ServletRegistrationBean getServlet() {
+ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
+ ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
+ registrationBean.setLoadOnStartup(1);
+ registrationBean.addUrlMappings("/hystrix.stream");
+ registrationBean.setName("HystrixMetricsStreamServlet");
+ return registrationBean;
+ }
+ }
+ ```
+
+* 指标说明:
+
+ 
+
+
+
+
+
+****
+
+
+
+
+
+## 服务网关
+
+### Zuul
+
+SpringCloud 中所集成的 Zuul 版本,采用的是 Tomcat 容器,基于 Servlet 之上的一个阻塞式处理模型,不支持任何长连接,用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得 Zuul 的性能相对较差
+
+官网: https://github.com/Netflix/zuul/wiki
+
+
+
+
+
+****
+
+
+
+
+
+### Gateway
+
+#### 基本介绍
+
+SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
+
+* 基于 WebFlux 框架实现,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty(异步非阻塞响应式的框架)
+* 基于 Filter 链的方式提供了网关基本的功能,例如:安全、监控/指标、限流等
+
+Gateway 的三个核心组件:
+
+* Route:路由是构建网关的基本模块,由 ID、目标 URI、一系列的断言和过滤器组成,如果断言为 true 则匹配该路由
+* Predicate:断言,可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求参数与断言相匹配则进行路由
+* Filter:指 Spring 框架中的 GatewayFilter实例,使用过滤器可以在请求被路由前或之后(拦截)对请求进行修改
+
+
+
+核心逻辑:路由转发 + 执行过滤器链
+
+- 客户端向 Spring Cloud Gateway 发出请求,然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler
+- Handler 通过指定的过滤器链来将请求发送到际的服务执行业务逻辑,然后返回
+- 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑
+- Filter 在 pre 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在 post 类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等
+
+
+
+
+
+***
+
+
+
+#### 网关使用
+
+##### 配置方式
+
+Gateway 网关路由有两种配置方式,分别为通过 yml 配置和注入 Bean
+
+* 引入 pom 依赖:Gateway 不需要 spring-boot-starter-web 依赖,否在会报错,原因是底层使用的是 WebFlux 与 Web 冲突
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-gateway
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 9527
+
+ spring:
+ application:
+ name: cloud-gateway
+
+ eureka:
+ instance:
+ hostname: cloud-gateway-service
+ client: #服务提供者provider注册进eureka服务列表内
+ service-url:
+ register-with-eureka: true
+ fetch-registry: true
+ defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
+ ```
+
+* 主启动类(网关不需要业务类):
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ public class GateWayMain9527 {
+ public static void main(String[] args) {
+ SpringApplication.run(GateWayMain9527.class, args);
+ }
+ }
+ ```
+
+* 以前访问 provider-payment8001 中的 Controller 方法,通过 localhost:8001/payment/get/id 和 localhost:8001/payment/lb,项目不想暴露 8001 端口号,希望在 8001 外面套一层 9527 端口:
+
+ ```yaml
+ server:
+ port: 9527
+
+ spring:
+ application:
+ name: cloud-gateway
+ ## =====================新增====================
+ cloud:
+ gateway:
+ routes:
+ - id: payment_routh # payment_route #路由的ID,没有固定规则但要求【唯一】,建议配合服务名
+ uri: http://localhost:8001 #匹配后提供服务的路由地址
+ predicates:
+ - Path=/payment/get/** # 断言,路径相匹配的进行路由
+
+ - id: payment_routh2 # payment_route#路由的ID,没有固定规则但要求【唯一】,建议配合服务名
+ uri: http://localhost:8001 #匹配后提供服务的路由地址
+ predicates:
+ - Path=/payment/lb/** # 断言,路径相匹配的进行路由
+ ```
+
+ * uri + predicate 拼接就是具体的接口请求路径,通过 localhost:9527 映射的地址
+ * predicate 断言 http://localhost:8001下面有一个 /payment/get/** 的地址,如果找到了该地址就返回 true,可以用 9527 端口访问,进行端口的适配
+ * `**` 表示通配符,因为这是一个不确定的参数
+
+
+
+****
+
+
+
+##### 注入Bean
+
+通过 9527 网关访问到百度的网址 https://www.baidu.com/,在 config 包下创建一个配置类,路由规则是访问 /baidu 跳转到百度
+
+```java
+@Configuration
+public class GatewayConfig {
+ // 配置了一个 id 为 path_route_cloud 的路由规则
+ @Bean
+ public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
+ // 构建一个路由器,这个routes相当于yml配置文件中的routes
+ RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
+ // 路由器的id是:path_route_cloud,规则是访问/baidu ,将会转发到 https://www.baidu.com/
+ routes.route("path_route_cloud",
+ r -> r.path("/baidu").uri(" https://www.baidu.com")).build();
+ return routes.build();
+ }
+}
+```
+
+
+
+***
+
+
+
+##### 动态路由
+
+Gateway 会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由和负载均衡,避免出现一个路由规则仅对应一个接口方法,当请求地址很多时需要很大的配置文件
+
+application.yml 开启动态路由功能
+
+```yaml
+spring:
+ application:
+ name: cloud-gateway
+ cloud:
+ gateway:
+ discovery:
+ locator:
+ enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
+ routes:
+ - id: payment_routh1 # 路由的ID,没有固定规则但要求唯一,建议配合服务名
+ uri: lb://cloud-payment-service # 匹配后提供服务的路由地址
+ predicates:
+ - Path=/payment/get/** # 断言,路径相匹配的进行路由
+
+ - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
+ uri: lb://cloud-payment-service #匹配后提供服务的路由地址
+ predicates:
+ - Path=/payment/lb/** # 断言,路径相匹配的进行路由
+ - After=2021-09-28T19:14:51.514+08:00[Asia/Shanghai]
+```
+
+lb:// 开头代表从注册中心中获取服务,后面是需要转发到的服务名称
+
+
+
+
+
+*****
+
+
+
+#### 断言类型
+
+After Route Predicate:匹配该断言时间之后的 URI 请求
+
+* 获取时间:
+
+ ```java
+ public class TimeTest {
+ public static void main(String[] args) {
+ ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
+ System.out.println(zbj); //2023-01-10T16:31:44.106+08:00[Asia/Shanghai]
+ }
+ }
+ ```
+
+* 配置 yml:动态路由小结有配置
+
+* 测试:正常访问成功,将时间修改到 2023-01-10T16:31:44.106+08:00[Asia/Shanghai] 之后访问失败
+
+ 
+
+常见断言类型:
+
+* Before Route Predicate:匹配该断言时间之前的 URI 请求
+
+* Between Route Predicate:匹配该断言时间之间的 URI 请求
+
+ ```yaml
+ - Between=2022-02-02T17:45:06.206+08:00[Asia/Shanghai],2022-03-25T18:59:06.206+08:00[Asia/Shanghai]
+ ```
+
+* Cookie Route Predicate:Cookie 断言,两个参数分别是 Cookie name 和正则表达式,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由
+
+ ```yaml
+ - Cookie=username, seazean # 只有发送的请求有 cookie,而且有username=seazean这个数据才能访问,反之404
+ ```
+
+* Header Route Predicate:请求头断言
+
+ ```yaml
+ - Header=X-Request-Id, \d+ # 请求头要有 X-Request-Id 属性,并且值为整数的正则表达式
+ ```
+
+* Host Route Predicate:指定主机可以访问,可以指定多个用 `,` 分隔开
+
+ ```yaml
+ - Host=**.seazean.com
+ ```
+
+* Method Route Predicate:请求类型断言
+
+ ```yaml
+ - Method=GET # 只有 Get 请求才能访问
+ ```
+
+* Path Route Predicate:路径匹配断言
+
+* Query Route Predicate:请求参数断言
+
+ ```yaml
+ - Query=username, \d+ # 要有参数名 username 并且值还要是整数才能路由
+ ```
+
+
+
+
+
+****
+
+
+
+#### Filter使用
+
+Filter 链是同时满足一系列的过滤链,路由过滤器可用于修改进入的 HTTP 请求和返回的 HTTP 响应,路由过滤器只能指定路由进行使用,Spring Cloud Gateway 内置了多种路由过滤器,都由 GatewayFilter 的工厂类来产生
+
+配置文件:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
+
+自定义全局过滤器:实现两个主要接口 GlobalFilter, Ordered
+
+```java
+@Component
+@Slf4j
+public class MyLogGateWayFilter implements GlobalFilter, Ordered {
+
+ @Override
+ public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+ log.info("*********************come in MyLogGateWayFilter: "+ new Date());
+ // 取出请求参数的uname对应的值
+ String uname = exchange.getRequest().getQueryParams().getFirst("uname");
+ // 如果 uname 为空,就直接过滤掉,不走路由
+ if(uname == null){
+ log.info("************* 用户名为 NULL 非法用户 o(╥﹏╥)o");
+
+ // 判断该请求不通过时:给一个回应,返回
+ exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
+ return exchange.getResponse().setComplete();
+ }
+
+ // 反之,调用下一个过滤器,也就是放行:在该环节判断通过的 exchange 放行,交给下一个 filter 判断
+ return chain.filter(exchange);
+ }
+
+ // 设置这个过滤器在Filter链中的加载顺序,数字越小,优先级越高
+ @Override
+ public int getOrder() {
+ return 0;
+ }
+}
+
+```
+
+
+
+
+
+***
+
+
+
+
+
+## 服务配置
+
+### config
+
+#### 基本介绍
+
+SpringCloud Config 为微服务架构中的微服务提供集中化的外部配置支持(Git/GitHub),为各个不同微服务应用的所有环境提供了一个中心化的外部配置(Config Server)
+
+
+
+SpringCloud Config 分为服务端和客户端两部分
+
+* 服务端也称为分布式配置中心,是一个独立的微服务应用,连接配置服务器并为客户端提供获取配置信息,加密/解密等信息访问接口
+* 客户端通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动时从配置中心获取和加载配置信息,配置服务器默认采用 Git 来存储配置信息,这样既有助于对环境配置进行版本管理,也可以通过 Git 客户端来方便的管理和访问配置内容
+
+优点:
+
+* 集中管理配置文件
+* 不同环境不同配置,动态化的配置更新,分环境部署比如 dev/test/prod/beta/release
+* 运行期间动态调整配置,服务向配置中心统一拉取配置的信息,**服务不需要重启即可感知到配置的变化并应用新的配置**
+* 将配置信息以 Rest 接口的形式暴露
+
+
+
+官网: https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/
+
+
+
+
+
+
+****
+
+
+
+#### 服务端
+
+构建 Config Server 模块
+
+* 引入 pom 依赖:
+
+ ```xml
+
+
+ org.springframework.cloud
+ spring-cloud-config-server
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 3344
+
+ spring:
+ application:
+ name: cloud-config-center #注册进Eureka服务器的微服务名
+ cloud:
+ config:
+ server:
+ git:
+ # GitHub上面的git仓库名字 这里可以写https地址跟ssh地址,https地址需要配置username和 password
+ uri: git@github.com:seazean/springcloud-config.git
+ default-label: main
+ search-paths:
+ - springcloud-config # 搜索目录
+ # username:
+ # password:
+ label: main # 读取分支,以前是master
+
+ #服务注册到eureka地址
+ eureka:
+ client:
+ service-url:
+ defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka #集群版
+ ```
+
+ search-paths 表示远程仓库下有一个叫做 springcloud-config 的,label 则表示读取 main分支里面的内容
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ @EnableConfigServer //开启SpringCloud的
+ public class ConfigCenterMain3344 {
+ public static void main(String[] args) {
+ SpringApplication.run(ConfigCenterMain3344.class, args);
+ }
+ }
+ ```
+
+配置读取规则:
+
+```yaml
+/{application}/{profile}[/{label}]
+/{application}-{profile}.yml
+/{label}/{application}-{profile}.yml
+/{application}-{profile}.properties
+/{label}/{application}-{profile}.properties
+```
+
+* label:分支
+* name:服务名
+* profile:环境(dev/test/prod)
+
+比如:http://localhost:3344/master/config-dev.yaml
+
+
+
+
+
+***
+
+
+
+#### 客户端
+
+##### 基本配置
+
+配置客户端 Config Client,客户端从配置中心(Config Server)获取配置信息
+
+* 引入 pom 依赖:
+
+ ```xml
+
+
+ org.springframework.cloud
+ spring-cloud-starter-config
+
+ ```
+
+* bootstrap.yml:系统级配置,优先级更高,application.yml 是用户级的资源配置项
+
+ Spring Cloud 会创建一个 Bootstrap Context 作为 Spring 应用的 Application Context 的父上下文,初始化的时候 Bootstrap Context 负责从外部源加载配置属性并解析配置,这两个上下文共享一个从外部获取的 Environment,为了配置文件的加载顺序和分级管理,这里使用 bootstrap.yml
+
+ ```yaml
+ server:
+ port: 3355 # 构建多个微服务,3366 3377 等
+
+ spring:
+ application:
+ name: config-client
+ cloud:
+ #Config客户端配置
+ config:
+ label: main #分支名称 以前是master
+ name: config #配置文件名称
+ profile: dev #读取后缀名称
+ # main分支上config-dev.yml的配置文件被读取 http://config-3344.com:3344/master/config-dev.yml
+ uri: http://localhost:3344 # 配置中心地址k
+
+ #服务注册到eureka地址
+ eureka:
+ client:
+ service-url:
+ defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ public class ConfigClientMain3355 {
+ public static void main(String[] args) {
+ SpringApplication.run(ConfigClientMain3355.class, args);
+ }
+ }
+ ```
+
+* 业务类:将配置信息以 REST 窗口的形式暴露
+
+ ```java
+ @RestController
+ public class ConfigClientController {
+ @Value("${config.info}")
+ private String configInfo;
+
+ @GetMapping("/configInfo")
+ public String getConfigInfo() {
+ return configInfo;
+ }
+ }
+ ```
+
+
+
+***
+
+
+
+##### 动态刷新
+
+分布式配置的动态刷新问题,修改 GitHub 上的配置文件,Config Server 配置中心立刻响应,但是 Config Client 客户端没有任何响应,需要重启客户端
+
+* 引入 pom 依赖:
+
+ ```xml
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+ ```
+
+* 修改 yml,暴露监控端口:SpringBoot 的 actuator 启动端点监控 Web 端默认加载默认只有两个 info,health 可见的页面节点
+
+ ```yaml
+ management:
+ endpoints:
+ web:
+ exposure:
+ include: "*" # 表示包含所有节点页面
+ exclude: env,beans # 表示排除env、beans
+ ```
+
+* 业务类:加 @RefreshScope 注解
+
+ ```java
+ @RestController
+ @RefreshScope
+ public class ConfigClientController {
+ // 从配置文件中取前缀为server.port的值
+ @Value("${config.info}")
+ private String configInfo;
+ // config-{profile}.yml
+ @GetMapping("/configInfo")
+ public String getConfigInfo() {
+ return configInfo;
+ }
+ }
+ ```
+
+此时客户端还是没有刷新,需要发送 POST 请求刷新 3355:`curl -X POST "http://localhost:3355/actuator/refresh`
+
+引出问题:
+
+* 在微服务多的情况下,每个微服务都需要执行一个 POST 请求,手动刷新成本太大
+* 可否广播,一次通知,处处生效,大范围的实现自动刷新
+
+解决方法:Bus 总线
+
+
+
+
+
+***
+
+
+
+
+
+## 服务消息
+
+### Bus
+
+#### 基本介绍
+
+Spring Cloud Bus 能管理和传播分布式系统间的消息,就像分布式执行器,可用于广播状态更改、事件推送、微服务间的通信通道等
+
+消息总线:在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称为消息总线
+
+基本原理:ConfigClient 实例都监听 MQ 中同一个 Topic(默认 springCloudBus),当一个服务刷新数据时,会把信息放入 Topic 中,这样其它监听同一 Topic 的服务就能得到通知,然后去更新自身的配置
+
+
+
+****
+
+
+
+#### 全局广播
+
+利用消息总线接触一个服务端 ConfigServer 的 `/bus/refresh` 断点,从而刷新所有客户端的配置
+
+
+
+改造 ConfigClient:
+
+* 引入 MQ 的依赖:
+
+ ```xml
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bus-amqp
+
+ ```
+
+* yml 文件添加 MQ 信息:
+
+ ```yaml
+ server:
+ port: 3344
+
+ spring:
+ application:
+ name: config-client #注册进Eureka服务器的微服务名
+ cloud:
+ # rabbitmq相关配置
+ rabbitmq:
+ host: localhost
+ port: 5672
+ username: guest
+ password: guest
+
+ # rabbitmq相关配置,暴露bus刷新配置的端点
+ management:
+ endpoints: # 暴露bus刷新配置的端点
+ web:
+ exposure:
+ include: 'bus-refresh'
+ ```
+
+* 只需要调用一次 `curl -X POST "http://localhost:3344/actuator/bus-refresh`,可以实现全局广播
+
+
+
+***
+
+
+
+#### 定点通知
+
+动态刷新情况下,只通知指定的微服务,比如只通知 3355 服务,不通知 3366,指定具体某一个实例生效,而不是全部
+
+公式:`http://localhost:port/actuator/bus-refresh/{destination}`
+
+/bus/refresh 请求不再发送到具体的服务实例上,而是发给 Config Server 并通过 destination 参数类指定需要更新配置的服务或实例
+
+
+
+
+
+
+
+****
+
+
+
+
+
+### Stream
+
+#### 基本介绍
+
+Spring Cloud Stream 是一个构建消息驱动微服务的框架,通过定义绑定器 Binder 作为中间层,实现了应用程序与消息中间件细节之间的隔离,屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
+
+Stream 中的消息通信方式遵循了发布订阅模式,Binder 可以生成 Binding 用来绑定消息容器的生产者和消费者,Binding 有两种类型 Input 和 Output,Input 对应于消费者(消费者从 Stream 接收消息),Output 对应于生产者(生产者从 Stream 发布消息)
+
+
+
+- Binder:连接中间件
+- Channel:通道,是队列 Queue 的一种抽象,在消息通讯系统中实现存储和转发的媒介,通过 Channel 对队列进行配置
+- Source、Sink:生产者和消费者
+
+
+
+中文手册:https://m.wang1314.com/doc/webapp/topic/20971999.html
+
+
+
+****
+
+
+
+#### 基本使用
+
+Binder 是应用与消息中间件之间的封装,目前实现了 Kafka 和 RabbitMQ 的 Binder,可以动态的改变消息类型(Kafka 的 Topic 和 RabbitMQ 的 Exchange),可以通过配置文件实现,常用注解如下:
+
+* @Input:标识输入通道,接收的消息通过该通道进入应用程序
+* @Output:标识输出通道,发布的消息通过该通道离开应用程序
+* @StreamListener:监听队列,用于消费者队列的消息接收
+* @EnableBinding:信道 Channel 和 Exchange 绑定
+
+生产者发消息模块:
+
+* 引入 pom 依赖:RabbitMQ
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-rabbit
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 8801
+
+ spring:
+ application:
+ name: cloud-stream-provider
+ cloud:
+ stream:
+ binders: # 在此处配置要绑定的rabbitmq的服务信息;
+ defaultRabbit: # 表示定义的名称,用于于binding整合
+ type: rabbit # 消息组件类型
+ environment: # 设置rabbitmq的相关的环境配置
+ spring:
+ rabbitmq:
+ host: localhost
+ port: 5672
+ username: guest
+ password: guest
+ bindings: # 服务的整合处理
+ output: # 这个名字是一个通道的名称
+ destination: studyExchange # 表示要使用的Exchange名称定义
+ content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
+ binder: defaultRabbit # 设置要绑定的消息服务的具体设置
+
+ eureka:
+ client: # 客户端进行Eureka注册的配置
+ service-url:
+ defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka
+ instance:
+ lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
+ lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
+ instance-id: send-8801.com # 在信息列表时显示主机名称
+ prefer-ip-address: true # 访问的路径变为IP地址
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableEurekaClient
+ public class StreamMQMain8801 {
+ public static void main(String[] args) {
+ SpringApplication.run(StreamMQMain8801.class, args);
+ }
+ }
+ ```
+
+* 业务类:MessageChannel 的实例名必须是 output,否则无法启动
+
+ ```java
+ // 可以理解为定义消息的发送管道Source对应output(生产者),Sink对应input(消费者)
+ @EnableBinding(Source.class)
+ // @Service:这里不需要,不是传统的controller调用service。这个service是和rabbitMQ打交道的
+ // IMessageProvider 只有一个 send 方法的接口
+ public class MessageProviderImpl implements IMessageProvider {
+ @Resource
+ private MessageChannel output; // 消息的发送管道
+
+ @Override
+ public String send() {
+ String serial = UUID.randomUUID().toString();
+
+ //创建消息,通过output这个管道向消息中间件发消息
+ this.output.send(MessageBuilder.withPayload(serial).build());
+ System.out.println("***serial: " + serial);
+ return serial;
+ }
+ }
+ ```
+
+* Controller:
+
+ ```java
+ @RestController
+ public class SendMessageController {
+ @Resource
+ private IMessageProvider messageProvider;
+
+ @GetMapping(value = "/sendMessage")
+ public String sendMessage() {
+ return messageProvider.send();
+ }
+ }
+ ```
+
+消费者模块:8802 和 8803 两个消费者
+
+* application.yml:只标注出与生产者不同的地方
+
+ ```yaml
+ server:
+ port: 8802
+
+ spring:
+ application:
+ name: cloud-stream-consumer
+ cloud:
+ stream:
+ # ...
+ bindings: # 服务的整合处理
+ input: # 这个名字是一个通道的名称
+ # ...
+ binder: { defaultRabbit } # 设置要绑定的消息服务的具体设置
+
+ eureka:
+ # ...
+ instance:
+ # ...
+ instance-id: receive-8802.com # 在信息列表时显示主机名称
+ ```
+
+* Controller:
+
+ ```java
+ @Component
+ @EnableBinding(Sink.class) // 理解为定义一个消息消费者的接收管道
+ public class ReceiveMessageListener {
+ @Value("${server.port}")
+ private String serverPort;
+
+ @StreamListener(Sink.INPUT) //输入源:作为一个消息监听者
+ public void input(Message message) {
+ // 获取到消息
+ String messageStr = message.getPayload();
+ System.out.println("消费者1号,------->接收到的消息:" + messageStr + "\t port: " + serverPort);
+ }
+ }
+ ```
+
+
+
+****
+
+
+
+#### 高级特性
+
+重复消费问题:生产者 8801 发送一条消息后,8802 和 8803 会同时收到 8801 的消息
+
+解决方法:微服务应用放置于同一个 group 中,能够保证消息只会被其中一个应用消费一次。不同的组是可以全面消费的(重复消费),同一个组内的多个消费者会发生竞争关系,只有其中一个可以消费
+
+```yaml
+bindings:
+ input:
+ destination: studyExchange
+ content-type: application/json
+ binder: { defaultRabbit }
+ group: seazean # 设置分组
+```
+
+消息持久化问题:
+
+* 停止 8802/8803 并去除掉 8802 的分组 group: seazean,8801 先发送 4 条消息到 MQ
+* 先启动 8802,无分组属性配置,后台没有打出来消息,消息丢失
+* 再启动 8803,有分组属性配置,后台打印出来了 MQ 上的消息
+
+
+
+
+
+*****
+
+
+
+
+
+### Sleuth
+
+#### 基本介绍
+
+Spring Cloud Sleuth 提供了一套完整的分布式请求链路跟踪的解决方案,并且兼容支持了 zipkin
+
+在微服务框架中,一个客户端发起的请求在后端系统中会经过多次不同的服务节点调用来协同产生最后的请求结果,形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败,所以需要链路追踪
+
+
+
+Sleuth 官网:https://github.com/spring-cloud/spring-cloud-sleuth
+
+zipkin 下载地址:https://repo1.maven.org/maven2/io/zipkin/java/zipkin-server/
+
+
+
+***
+
+
+
+#### 链路监控
+
+Sleuth 负责跟踪整理,zipkin 负责可视化展示
+
+```bash
+java -jar zipkin-server-2.12.9-exec.jar # 启动 zipkin
+```
+
+访问 http://localhost:9411/zipkin/ 展示交互界面
+
+一条请求链路通过 Trace ID 唯一标识,Span 标识发起的请求信息
+
+* Trace:类似于树结构的 Span 集合,表示一条调用链路,存在唯一 ID 标识
+
+* Span:表示调用链路来源,通俗的理解 Span 就是一次请求信息,各个 Span 通过 ParentID 关联起来
+
+服务生产者模块:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+
+ org.springframework.cloud
+ spring-cloud-starter-zipkin
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 8001
+
+ spring:
+ application:
+ name: cloud-payment-service
+ zipkin:
+ base-url: http://localhost:9411
+ sleuth:
+ sampler:
+ #采样率值介于 0 到 1 之间,1 则表示全部采集
+ probability: 1
+ ```
+
+* 业务类:
+
+ ```java
+ @GetMapping("/payment/zipkin")
+ public String paymentZipkin() {
+ return "hi ,i'am paymentzipkin server fall back,welcome to seazean";
+ }
+ ```
+
+服务消费者模块:
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 80
+
+ # 微服务名称
+ spring:
+ application:
+ name: cloud-order-service
+ zipkin:
+ base-url: http://localhost:9411
+ sleuth:
+ sampler:
+ probability: 1
+ ```
+
+* 业务类:
+
+ ```java
+ @GetMapping("/comsumer/payment/zipkin")
+ public String paymentZipKin() {
+ String result = restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/", String.class);
+ return result;
+ }
+ ```
+
+
+
+
+
+
+****
+
+
+
+
+
+## Alibaba
+
+Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案,此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务
+
+- 服务限流降级:默认支持 WebServlet、WebFlux、OpenFeign、RestTemplate、Spring Cloud Gateway、Zuul、Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
+- 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持
+- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新
+- 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力
+- 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题
+- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务
+- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行
+
+官方文档:https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
+
+官方手册:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
+
+
+
+
+
+### Nacos
+
+#### 基本介绍
+
+Nacos 全称 Dynamic Naming and Configuration Service,一个更易于构建云原生应用的动态服务发现、配置管理和服务的管理平台,Nacos = Eureka + Config + Bus
+
+下载地址:https://github.com/alibaba/nacos/releases
+
+启动命令:命令运行成功后直接访问 http://localhost:8848/nacos,默认账号密码都是 nacos
+
+```bash
+startup.cmd -m standalone # standalone 代表着单机模式运行,非集群模式
+```
+
+关闭命令:
+
+```bash
+shutdown.cmd
+```
+
+注册中心对比:C 一致性,A 可用性,P 分区容错性
+
+| 注册中心 | CAP 模型 | 控制台管理 |
+| :-------: | :------: | :--------: |
+| Eureka | AP | 支持 |
+| Zookeeper | CP | 不支持 |
+| Consul | CP | 支持 |
+| Nacos | AP | 支持 |
+
+切换模式:`curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP`
+
+
+
+官网:https://nacos.io
+
+
+
+****
+
+
+
+#### 注册中心
+
+Nacos 作为服务注册中心
+
+服务提供者:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 9001
+
+ spring:
+ application:
+ name: nacos-payment-provider
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848 #配置Nacos地址,注册到Nacos
+ # 做监控需要把这个全部暴露出来
+ management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ ```
+
+* 主启动类:注解是 EnableDiscoveryClient
+
+ ```java
+ @EnableDiscoveryClient
+ @SpringBootApplication
+ public class PaymentMain9001 {
+ public static void main(String[] args) {
+ SpringApplication.run(PaymentMain9001.class, args);
+ }
+ }
+ ```
+
+* Controller:
+
+ ```java
+ @RestController
+ public class PaymentController {
+ @Value("${server.port}")
+ private String serverPort;
+
+ @GetMapping(value = "/payment/nacos/{id}")
+ public String getPayment(@PathVariable("id") Integer id) {
+ return "nacos registry, serverPort: " + serverPort + "\t id" + id;
+ }
+ }
+ ```
+
+* 管理后台服务:
+
+ 
+
+* 新建一个模块端口是 9002,其他与 9001 服务一样,nacos-payment-provider 的实例数就变为 2
+
+服务消费者:
+
+* application.yml:
+
+ ```yaml
+ server:
+ port: 83
+
+ spring:
+ application:
+ name: nacos-order-consumer
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848
+
+ # 消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
+ service-url:
+ nacos-user-service: http://nacos-payment-provider
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class OrderNacosMain83 {
+ public static void main(String[] args) {
+ SpringApplication.run(OrderNacosMain83.class, args);
+ }
+ }
+ ```
+
+* 业务类:
+
+ ```java
+ @Configuration
+ public class ApplicationContextBean {
+ @Bean
+ @LoadBalanced // 生产者集群状态下,必须添加,防止找不到实例
+ public RestTemplate getRestTemplate() {
+ return new RestTemplate();
+ }
+ }
+ ```
+
+ ```java
+ @RestController
+ @Slf4j
+ public class OrderNacosController {
+ @Resource
+ private RestTemplate restTemplate;
+ // 从配置文件中读取 URL
+ @Value("${service-url.nacos-user-service}")
+ private String serverURL;
+
+ @GetMapping("/consumer/payment/nacos/{id}")
+ public String paymentInfo(@PathVariable("id") Long id) {
+ String result = restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
+ return result;
+ }
+ }
+ ```
+
+
+
+***
+
+
+
+#### 配置中心
+
+##### 基础配置
+
+把配置文件写进 Nacos,然后再用 Nacos 做 config 这样的功能,直接从 Nacos 上抓取服务的配置信息
+
+在 Nacos 中,dataId 的完整格式如下 `${prefix}-${spring.profiles.active}.${file-extension}`
+
+* `prefix`:默认为 `spring.application.name` 的值,也可以通过配置项 `spring.cloud.nacos.config.prefix` 来配置
+* `spring.profiles.active`:当前环境对应的 profile,当该值为空时,dataId 的拼接格式变成 `${prefix}.${file-extension}`
+* `file-exetension`:配置内容的数据格式,可以通过配置项 `spring.cloud.nacos.config.file-extension` 来配置,目前只支持 properties 和 yaml 类型(不是 yml)
+
+构建流程:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+
+ ```
+
+* 配置两个 yml 文件:配置文件的加载是存在优先级顺序的,bootstrap 优先级高于 application
+
+ bootstrap.yml:全局配置
+
+ ```yaml
+ # nacos配置
+ server:
+ port: 3377
+
+ spring:
+ application:
+ name: nacos-config-client
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848 #Nacos服务注册中心地址
+ config:
+ server-addr: localhost:8848 #Nacos作为配置中心地址
+ file-extension: yaml #指定yaml格式的配置
+
+ # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
+ ```
+
+ application.yml:服务独立配置,表示服务要去配置中心找名为 nacos-config-client-dev.yaml 的文件
+
+ ```yaml
+ spring:
+ profiles:
+ active: dev # 表示开发环境
+ ```
+
+* 主启动类:
+
+ ```java
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class NacosConfigClientMain3377 {
+ public static void main(String[] args) {
+ SpringApplication.run(NacosConfigClientMain3377.class, args);
+ }
+ }
+ ```
+
+* 业务类:@RefreshScope 注解使当前类下的配置支持 Nacos 的动态刷新功能
+
+ ```java
+ @RestController
+ @RefreshScope
+ public class ConfigClientController {
+ @Value("${config.info}")
+ private String configInfo;
+
+ @GetMapping("/config/info")
+ public String getConfigInfo() {
+ return configInfo;
+ }
+ }
+ ```
+
+* 新增配置,然后访问 http://localhost:3377/config/info
+
+ 
+
+
+
+***
+
+
+
+##### 分类配置
+
+分布式开发中的多环境多项目管理问题,Namespace 用于区分部署环境,Group 和 DataID 逻辑上区分两个目标对象
+
+
+
+Namespace 默认 public,主要用来实现隔离,图示三个开发环境
+
+Group 默认是 DEFAULT_GROUP,Group 可以把不同的微服务划分到同一个分组里面去
+
+
+
+***
+
+
+
+##### 加载配置
+
+DataID 方案:指定 `spring.profile.active` 和配置文件的 DataID 来使不同环境下读取不同的配置
+
+Group 方案:通过 Group 实现环境分区,在 config 下增加一条 Group 的配置即可
+
+Namespace 方案:
+
+```yaml
+server:
+ port: 3377
+
+spring:
+ application:
+ name: nacos-config-client
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848 #Nacos服务注册中心地址
+ config:
+ server-addr: localhost:8848 #Nacos作为配置中心地址
+ file-extension: yaml #指定yaml格式的配置
+ group: DEV_GROUP
+ namespace: 95d44530-a4a6-4ead-98c6-23d192cee298
+```
+
+
+
+
+
+****
+
+
+
+#### 集群架构
+
+集群部署参考官方文档,Nacos 支持的三种部署模式:
+
+- 单机模式:用于测试和单机使用
+- 集群模式:用于生产环境,确保高可用
+- 多集群模式:用于多数据中心场景
+
+集群部署文档:https://nacos.io/zh-cn/docs/v2/guide/admin/cluster-mode-quick-start.html
+
+默认 Nacos 使用嵌入式数据库 derby 实现数据的存储,重启 Nacos 后配置文件不会消失,但是多个 Nacos 节点数据存储存在一致性问题,每个 Nacos 都有独立的嵌入式数据库,所以 Nacos 采用了集中式存储的方式来支持集群化部署,目前只支持 MySQL 的存储
+
+Windows 下 Nacos 切换 MySQL 存储:
+
+* 在 Nacos 安装目录的 conf 目录下找到一个名为 `nacos-mysql.sql` 的脚本并执行
+
+* 在 conf 目录下找到 `application.properties`,增加如下数据
+
+ ```properties
+ spring.datasource.platform=mysql
+
+ db.num=1
+ db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
+ db.user=username
+ db.password=password
+ ```
+
+* 重新启动 Nacos,可以看到是个全新的空记录界面
+
+Linux 参考:https://www.yuque.com/mrlinxi/pxvr4g/rnahsn#dPvMy
+
+
+
+
+
+****
+
+
+
+
+
+### Sentinel
+
+#### 基本介绍
+
+Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件
+
+Sentinel 分为两个部分:
+
+- 核心库(Java 客户端)不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境
+- 控制台(Dashboard)主要负责管理推送规则、监控、管理机器信息等
+
+下载到本地,运行命令:`java -jar sentinel-dashboard-1.8.2.jar` (要求 Java8,且 8080 端口不能被占用),访问 http://localhost:8080/,账号密码均为 sentinel
+
+
+
+官网:https://sentinelguard.io
+
+下载地址:https://github.com/alibaba/Sentinel/releases
+
+
+
+
+
+****
+
+
+
+#### 基本使用
+
+构建演示工程:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+ ```
+
+* application.yml:sentinel.transport.port 端口配置会在应用对应的机器上启动一个 HTTP Server,该 Server 与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了 1 个限流规则,会把规则数据 Push 给 Server 接收,Server 再将规则注册到 Sentinel 中
+
+ ```yaml
+ server:
+ port: 8401
+
+ spring:
+ application:
+ name: cloudalibaba-sentinel-service
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848 # Nacos 服务注册中心地址【需要启动Nacos8848】
+ sentinel:
+ transport:
+ # 配置Sentinel dashboard地址
+ dashboard: localhost:8080
+ # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
+ port: 8719
+
+ management:
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+ ```
+
+* 主启动类:
+
+ ```java
+ @EnableDiscoveryClient
+ @SpringBootApplication
+ public class SentinelMainApp8401 {
+ public static void main(String[] args) {
+ SpringApplication.run(SentinelMainApp8401.class, args);
+ }
+ }
+ ```
+
+* 流量控制 Controller:
+
+ ```java
+ @RestController
+ @Slf4j
+ public class FlowLimitController {
+ @GetMapping("/testA")
+ public String testA() {
+ return "------testA";
+ }
+
+ @GetMapping("/testB")
+ public String testB() {
+ return "------testB";
+ }
+ }
+ ```
+
+* Sentinel 采用懒加载机制,需要先访问 http://localhost:8401/testA,控制台才能看到
+
+
+
+***
+
+
+
+#### 流控规则
+
+流量控制规则 FlowRule:同一个资源可以同时有多个限流规则
+
+* 资源名 resource:限流规则的作用对象,Demo 中为 testA
+* 针对资源 limitApp:针对调用者进行限流,默认为 default 代表不区分调用来源
+* 阈值类型 grade:QPS 或线程数模式
+* 单机阈值 count:限流阈值
+* 流控模式 strategy:调用关系限流策略
+ * 直接:资源本身达到限流条件直接限流
+ * 关联:当关联的资源达到阈值时,限流自身
+ * 链路:只记录指定链路上的流量,从入口资源进来的流量
+* 流控效果 controlBehavior:
+ * 快速失败:直接失败,抛出异常
+ * Warm Up:冷启动,根据 codeFactory(冷加载因子,默认 3)的值,从 count/codeFactory 开始缓慢增加,给系统预热时间
+ * 排队等待:匀速排队,让请求以匀速的方式通过,阈值类型必须设置为 QPS,否则无效
+
+
+
+通过调用 `SystemRuleManager.loadRules()` 方法来用硬编码的方式定义流量控制规则:
+
+```java
+private void initSystemProtectionRule() {
+ List rules = new ArrayList<>();
+ SystemRule rule = new SystemRule();
+ rule.setHighestSystemLoad(10);
+ rules.add(rule);
+ SystemRuleManager.loadRules(rules);
+}
+```
+
+
+
+详细内容参考文档:https://sentinelguard.io/zh-cn/docs/flow-control.html
+
+
+
+****
+
+
+
+#### 降级熔断
+
+Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时,对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)
+
+Sentinel 提供以下几种熔断策略:
+
+* 资源名 resource:限流规则的作用对象,Demo 中为 testA
+* 熔断策略 grade:
+ * 慢调用比例(SLOW_REQUEST_RATIO):以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
+ * 异常比例(ERROR_RATIO):当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 `[0.0, 1.0]`,代表 0% - 100%
+ * 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
+* 单机阈值 count:慢调用比例模式下为慢调用临界 RT;异常比例/异常数模式下为对应的阈值
+* 熔断时长 timeWindow:单位为 s
+* 最小请求数 minRequestAmount:熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断,默认 5
+* 统计时长 statIntervalMs:单位统计时长
+* 慢调用比例阈值 slowRatioThreshold:仅慢调用比例模式有效
+
+
+
+注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常 BlockException 不生效,为了统计异常比例或异常数,需要通过 `Tracer.trace(ex)` 记录业务异常或者通过`@SentinelResource` 注解会自动统计业务异常
+
+```java
+Entry entry = null;
+try {
+ entry = SphU.entry(resource);
+
+ // Write your biz code here.
+ // <>
+} catch (Throwable t) {
+ if (!BlockException.isBlockException(t)) {
+ Tracer.trace(t);
+ }
+} finally {
+ if (entry != null) {
+ entry.exit();
+ }
+}
+```
+
+
+
+详细内容参考文档:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
+
+
+
+****
+
+
+
+#### 热点限流
+
+热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流,Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控
+
+
+
+引入 @SentinelResource 注解:https://sentinelguard.io/zh-cn/docs/annotation-support.html
+
+- value:Sentinel 资源名,默认为请求路径,这里 value 的值可以任意写,但是约定与 Restful 地址一致
+
+- blockHandler:表示触发了 Sentinel 中配置的流控规则,就会调用兜底方法 `del_testHotKey`
+
+- blockHandlerClass:如果设置了该值,就会去该类中寻找 blockHandler 方法
+
+- fallback:用于在抛出异常的时候提供 fallback 处理逻辑
+
+ 说明:fallback 对应服务降级(服务出错了需要有个兜底方法),blockHandler 对应服务熔断(服务不可用的兜底方法)
+
+```java
+@GetMapping("/testHotKey")
+@SentinelResource(value = "testHotKey", blockHandler = "del_testHotKey")
+public String testHotkey(@RequestParam(value = "p1", required = false) String p1,
+ @RequestParam(value = "p1", required = false) String p2) {
+ return "------testHotkey";
+}
+
+// 自定义的兜底方法,必须是 BlockException
+public String del_testHotKey(String p1, String p2, BlockException e) {
+ return "不用默认的兜底提示 Blocked by Sentinel(flow limiting),自定义提示:del_testHotKeyo.";
+}
+```
+
+图示设置 p1 参数限流,规则是 1s 访问 1 次,当 p1=5 时 QPS > 100,只访问 p2 不会出现限流 `http://localhost:8401/testHotKey?p2=b`
+
+
+
+* 参数索引 paramIdx:热点参数的索引,图中索引 0 对应方法中的 p1 参数
+* 参数例外项 paramFlowItemList:针对指定的参数值单独设置限流阈值,不受 count 阈值的限制,**仅支持基本类型和字符串类型**
+
+说明:@SentinelResource 只管控制台配置规则问题,出现运行时异常依然会报错
+
+
+
+详细内容参考文档:https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html
+
+
+
+***
+
+
+
+#### 系统规则
+
+Sentinel 系统自适应保护从整体维度对**应用入口流量**进行控制,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性
+
+系统规则支持以下的阈值类型:
+
+- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护,系统容量由系统的 `maxQps * minRt` 计算得出,设定参考值一般是 `CPU cores * 2.5`
+- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
+- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
+- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
+- CPU usage:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)
+
+
+
+
+
+详细内容参考文档:https://sentinelguard.io/zh-cn/docs/system-adaptive-protection.html
+
+
+
+****
+
+
+
+#### 服务调用
+
+消费者需要进行服务调用
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+ ```
+
+* application.yml:激活 Sentinel 对 Feign 的支持
+
+ ```yaml
+ feign:
+ sentinel:
+ enabled: true
+ ```
+
+* 主启动类:加上 @EnableFeignClient 注解开启 OpenFeign
+
+* 业务类:
+
+ ```java
+ // 指明调用失败的兜底方法在PaymentFallbackService,使用 fallback 方式是无法获取异常信息的,
+ // 如果想要获取异常信息,可以使用 fallbackFactory 参数
+ @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
+ public interface PaymentFeignService {
+ // 去生产则服务中找相应的接口,方法签名一定要和生产者中 controller 的一致
+ @GetMapping(value = "/paymentSQL/{id}")
+ public CommonResult paymentSQL(@PathVariable("id") Long id);
+ }
+
+ ```
+
+ ```java
+ @Component //不要忘记注解,降级方法
+ public class PaymentFallbackService implements PaymentFeignService {
+ @Override
+ public CommonResult paymentSQL(Long id) {
+ return new CommonResult<>(444,"服务降级返回,没有该流水信息-------PaymentFallbackSe
+ ```
+
+
+
+****
+
+
+
+#### 持久化
+
+配置持久化:
+
+* 引入 pom 依赖:
+
+ ```xml
+
+
+ com.alibaba.csp
+ sentinel-datasource-nacos
+
+ ```
+
+* 添加 Nacos 数据源配置:
+
+ ```yaml
+ server:
+ port: 8401
+
+ spring:
+ application:
+ name: cloudalibaba-sentinel-service
+ cloud:
+ nacos:
+ discovery:
+ server-addr: localhost:8848 #Nacos服务注册中心地址
+ sentinel:
+ transport:
+ dashboard: localhost:8080
+ port: 8719
+ # 关闭默认收敛所有URL的入口context,不然链路限流不生效
+ web-context-unify: false
+ # filter:
+ # enabled: false # 关闭自动收敛
+
+ #持久化配置
+ datasource:
+ ds1:
+ nacos:
+ server-addr: localhost:8848
+ dataId: cloudalibaba-sentinel-service
+ groupId: DEFAULT_GROUP
+ data-type: json
+ rule-type: flow
+ ```
+
+
+
+
+
+****
+
+
+
+
+
+### Seata
+
+#### 分布事物
+
+一个分布式事务过程,可以用分布式处理过程的一 ID + 三组件模型来描述:
+
+* XID (Transaction ID):全局唯一的事务 ID,在这个事务ID下的所有事务会被统一控制
+
+* TC (Transaction Coordinator):事务协调者,维护全局和分支事务的状态,驱动全局事务提交或回滚
+
+* TM (Transaction Manager):事务管理器,定义全局事务的范围,开始全局事务、提交或回滚全局事务
+
+* RM (Resource Manager):资源管理器,管理分支事务处理的资源,与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
+
+典型的分布式事务流程:
+
+1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
+2. XID 在微服务调用链路的上下文中传播(也就是在多个 TM,RM 中传播)
+3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖
+4. TM 向 TC 发起针对 XID 的全局提交或回滚决议
+5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求
+
+
+
+
+
+***
+
+
+
+#### 基本配置
+
+Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
+
+下载 seata-server 文件修改 conf 目录下的配置文件
+
+* file.conf:自定义事务组名称、事务日志存储模式为 db、数据库连接信息
+
+ **事务分组**:seata 的资源逻辑,可以按微服务的需要,在应用程序(客户端)对自行定义事务分组,每组取一个名字
+
+
+
+* 数据库新建库 seata,建表 db_store.sql 在 https://github.com/seata/seata/tree/2.x/script/server/db 目录里面
+
+* registry.conf:指明注册中心为 Nacos,及修改 Nacos 连接信息
+
+
+
+启动 Nacos 和 Seata,如果 DB 报错,需要把将 lib 文件夹下 mysql-connector-java-5.1.30.jar 删除,替换为自己 MySQL 连接器版本
+
+
+
+
+
+官网:https://seata.io
+
+下载地址:https://github.com/seata/seata/releases
+
+基本介绍:https://seata.io/zh-cn/docs/overview/what-is-seata.html
+
+
+
+***
+
+
+
+#### 基本使用
+
+两个注解:
+
+* Spring 提供的本地事务:@Transactional
+
+* Seata 提供的全局事务:**@GlobalTransactional**
+
+搭建简单 Demo:
+
+* 创建 UNDO_LOG 表:SEATA AT 模式需要 `UNDO_LOG` 表,如果一个模块的事务提交了,Seata 会把提交了哪些数据记录到 undo_log 表中,如果这时 TC 通知全局事务回滚,那么 RM 就从 undo_log 表中获取之前修改了哪些资源,并根据这个表回滚
+
+ ```sql
+ -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
+ CREATE TABLE `undo_log` (
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
+ `branch_id` bigint(20) NOT NULL,
+ `xid` varchar(100) NOT NULL,
+ `context` varchar(128) NOT NULL,
+ `rollback_info` longblob NOT NULL,
+ `log_status` int(11) NOT NULL,
+ `log_created` datetime NOT NULL,
+ `log_modified` datetime NOT NULL,
+ `ext` varchar(100) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
+ ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
+ ```
+
+* 引入 pom 依赖:
+
+ ```xml
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-seata
+ ${spring-cloud-alibaba.version}
+
+ ```
+
+* application.yml:
+
+ ```yaml
+ spring:
+ application:
+ name: seata-order-service
+ cloud:
+ alibaba:
+ seata:
+ # 自定义事务组名称需要与seata-server中file.conf中配置的事务组ID对应
+ # vgroup_mapping.my_test_tx_group = "my_group"
+ tx-service-group: my_group
+ nacos:
+ discovery:
+ server-addr: localhost:8848
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC
+ username: root
+ password: 123456
+ ```
+
+* 构建三个服务:
+
+ ```java
+ // 仓储服务
+ public interface StorageService {
+ // 扣除存储数量
+ void deduct(String commodityCode, int count);
+ }
+
+ // 订单服务
+ public interface OrderService {
+ // 创建订单
+ Order create(String userId, String commodityCode, int orderCount);
+ }
+
+ // 帐户服务
+ public interface AccountService {
+ // 从用户账户中借出
+ void debit(String userId, int money);
+ }
+ ```
+
+* 业务逻辑:增加 @GlobalTransactional 注解
+
+ ```java
+ public class OrderServiceImpl implements OrderService {
+ @Resource
+ private OrderDAO orderDAO;
+ @Resource
+ private AccountService accountService;
+
+ @Transactional(rollbackFor = Exception.class)
+ public Order create(String userId, String commodityCode, int orderCount) {
+ int orderMoney = calculate(commodityCode, orderCount);
+ // 账户扣钱
+ accountService.debit(userId, orderMoney);
+
+ Order order = new Order();
+ order.userId = userId;
+ order.commodityCode = commodityCode;
+ order.count = orderCount;
+ order.money = orderMoney;
+
+ return orderDAO.insert(order);
+ }
+ }
+ ```
+
+ ```java
+ public class BusinessServiceImpl implements BusinessService {
+ @Resource
+ private StorageService storageService;
+ @Resource
+ private OrderService orderService;
+
+ // 采购,涉及多服务的分布式事务问题
+ @GlobalTransactional
+ @Transactional(rollbackFor = Exception.class)
+ public void purchase(String userId, String commodityCode, int orderCount) {
+ storageService.deduct(commodityCode, orderCount);
+ orderService.create(userId, commodityCode, orderCount);
+ }
+ }
+ ```
+
+
+
+
+
+详细示例参考:https://github.com/seata/seata-samples/tree/master/springcloud-nacos-seata
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tool.md b/Tool.md
index efe91aa..2ed20c7 100644
--- a/Tool.md
+++ b/Tool.md
@@ -31,7 +31,7 @@ Git 是分布式版本控制系统(Distributed Version Control System,简称
4.提交到本地仓库。本地仓库中保存修改的各个历史版本
-5.修改完成后,需要和团队成员共享代码时,将代码push到远程仓库
+5.修改完成后,需要和团队成员共享代码时,将代码 push 到远程仓库
@@ -66,7 +66,7 @@ GitLab(地址: https://about.gitlab.com/ )是一个用于仓库管理系
设置用户信息:
* git config --global user.name “Seazean”
-* git config --global user.email “zhyzhyang@sina.com” //用户名和邮箱可以随意填写,不会校对
+* git config --global user.email "imseazean@gmail.com" //用户名和邮箱可以随意填写,不会校对
查看配置信息:
@@ -108,8 +108,8 @@ GitLab(地址: https://about.gitlab.com/ )是一个用于仓库管理系
* -t 指定密钥类型,默认是 rsa ,可以省略
* -C 设置注释文字,比如邮箱
* -f 指定密钥文件存储文件名
- * 查看命令: cat ~/.ssh/id_rsa.pub
- * 公钥测试命令: ssh -T git@github.com
+ * 查看命令:cat ~/.ssh/id_rsa.pub
+ * 公钥测试命令:ssh -T git@github.com
@@ -2245,7 +2245,7 @@ pid_t wait(int *status)
参数:status 用来保存被收集的子进程退出时的状态,如果不关心子进程**如何**销毁,可以设置这个参数为 NULL
-父进程调用 wait() 会一直阻塞,直到收到一个子进程退出的 SIGCHLD 信号,wait() 函数就会销毁子进程并返回
+父进程调用 wait() 会阻塞等待,直到收到一个子进程退出的 SIGCHLD 信号,wait() 函数就会销毁子进程并返回
* 成功,返回被收集的子进程的进程 ID
* 失败,返回 -1,同时 errno 被置为 ECHILD(如果调用进程没有子进程,调用就会失败)
diff --git a/Web.md b/Web.md
index 5dfad06..1aeab98 100644
--- a/Web.md
+++ b/Web.md
@@ -2063,8 +2063,6 @@ a{