Skip to content

Commit 572a59c

Browse files
committed
Update Java Note
1 parent 9a18249 commit 572a59c

File tree

5 files changed

+163
-73
lines changed

5 files changed

+163
-73
lines changed

DB.md

Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7259,7 +7259,7 @@ MySQL 复制的优点主要包含以下三个方面:
72597259

72607260

72617261

7262-
### 复制原理
7262+
### 主从复制
72637263

72647264
#### 主从结构
72657265

@@ -7420,7 +7420,9 @@ MySQL 5.7.22 的并行复制策略在通用性上是有保证的,但是对于
74207420

74217421

74227422

7423-
#### 读写分离
7423+
### 读写分离
7424+
7425+
#### 读写延迟
74247426

74257427
读写分离:可以降低主库的访问压力,提高系统的并发能力
74267428

@@ -7432,21 +7434,111 @@ MySQL 5.7.22 的并行复制策略在通用性上是有保证的,但是对于
74327434
解决方案:
74337435

74347436
* 强制将写之后**立刻读的操作转移到主库**,比如刚注册的用户,直接登录从库查询可能查询不到,先走主库登录
7435-
74367437
* **二次查询**,如果从库查不到数据,则再去主库查一遍,由 API 封装,比较简单,但导致主库压力大
7438+
* 更新主库后,读从库之前先 sleep 一下,类似于执行一条 `select sleep(1)` 命令,大多数情况下主备延迟在 1 秒之内
7439+
7440+
7441+
7442+
***
7443+
7444+
7445+
7446+
#### 确保机制
7447+
7448+
##### 无延迟
7449+
7450+
确保主备无延迟的方法:
7451+
7452+
* 每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0,如果不等于那就等到参数变为 0 执行查询请求
7453+
* 对比位点,Master_Log_File 和 Read_Master_Log_Pos 表示的是读到的主库的最新位点,Relay_Master_Log_File 和 Exec_Master_Log_Pos 表示的是备库执行的最新位点,这两组值完全相同就说明接收到的日志已经同步完成
7454+
* 对比 GTID 集合,Retrieved_Gtid_Set 是备库收到的所有日志的 GTID 集合,Executed_Gtid_Set 是备库所有已经执行完成的 GTID 集合,如果这两个集合相同也表示备库接收到的日志都已经同步完成
7455+
7456+
7457+
7458+
***
7459+
7460+
7461+
7462+
##### 半同步
7463+
7464+
半同步复制就是 semi-sync replication,适用于一主一备的场景,工作流程:
7465+
7466+
* 事务提交的时候,主库把 binlog 发给从库
7467+
* 从库收到 binlog 以后,发回给主库一个 ack,表示收到了
7468+
* 主库收到这个 ack 以后,才能给客户端返回事务完成的确认
7469+
7470+
在一主多从场景中,主库只要等到一个从库的 ack,就开始给客户端返回确认,这时在从库上执行查询请求,有两种情况:
7471+
7472+
* 如果查询是落在这个响应了 ack 的从库上,是能够确保读到最新数据
7473+
* 如果查询落到其他从库上,它们可能还没有收到最新的日志,就会产生过期读的问题
7474+
7475+
在业务更新的高峰期,主库的位点或者 GTID 集合更新很快,导致从库来不及处理,那么两个位点等值判断就会一直不成立,很可能出现从库上迟迟无法响应查询请求的情况
7476+
7477+
7478+
7479+
****
7480+
7481+
7482+
7483+
##### 等位点
7484+
7485+
**从库执行判断位点**的命令,参数 file 和 pos 指的是主库上的文件名和位置,timeout 可选,设置为正整数 N 表示最多等待 N 秒
7486+
7487+
```mysql
7488+
SELECT master_pos_wait(file, pos[, timeout]);
7489+
```
7490+
7491+
命令正常返回的结果是一个正整数 M,表示从命令开始执行,到应用完 file 和 pos 表示的 binlog 位置,执行了多少事务
7492+
7493+
* 如果执行期间,备库同步线程发生异常,则返回 NULL
7494+
* 如果等待超过 N 秒,就返回 -1
7495+
* 如果刚开始执行的时候,就发现已经执行过这个位置了,则返回 0
74377496

7438-
* 更新主库后,读从库之前先 sleep 一下,类似于执行一条 `select sleep(1)` 命令
7439-
* 确保主备无延迟的方法,每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0,如果不等于那就等到这个参数变为 0 才能执行查询请求
7497+
工作流程:先执行 trx1,再执行一个查询请求的逻辑,要**保证能够查到正确的数据**
74407498

7499+
* trx1 事务更新完成后,马上执行 `show master status` 得到当前主库执行到的 File 和 Position
7500+
* 选定一个从库执行判断位点语句,如果返回值是 >=0 的正整数,说明从库已经同步完事务,可以在这个从库执行查询语句
7501+
* 如果出现其他情况,需要到主库执行查询语句
74417502

7503+
注意:如果所有的从库都延迟超过 timeout 秒,查询压力就都跑到主库上,所以需要进行权衡
74427504

74437505

74447506

74457507
***
74467508

74477509

74487510

7449-
### 负载均衡
7511+
##### 等GTID
7512+
7513+
数据库开启了 GTID 模式,MySQL 提供了判断 GTID 的命令
7514+
7515+
```mysql
7516+
SELECT wait_for_executed_gtid_set(gtid_set [, timeout])
7517+
```
7518+
7519+
* 等待直到这个库执行的事务中包含传入的 gtid_set,返回 0
7520+
* 超时返回 1
7521+
7522+
工作流程:先执行 trx1,再执行一个查询请求的逻辑,要保证能够查到正确的数据
7523+
7524+
* trx1 事务更新完成后,从返回包直接获取这个事务的 GTID,记为 gtid1
7525+
* 选定一个从库执行查询语句,如果返回值是 0,则在这个从库执行查询语句,否则到主库执行查询语句
7526+
7527+
对比等待位点方法,减少了一次 `show master status` 的方法,将参数 session_track_gtids 设置为 OWN_GTID,然后通过 API 接口 mysql_session_track_get_first 从返回包解析出 GTID 的值即可
7528+
7529+
总结:所有的等待无延迟的方法,都需要根据具体的业务场景去判断实施
7530+
7531+
7532+
7533+
参考文章:https://time.geekbang.org/column/article/77636
7534+
7535+
7536+
7537+
***
7538+
7539+
7540+
7541+
#### 负载均衡
74507542

74517543
负载均衡是应用中使用非常普遍的一种优化方法,机制就是利用某种均衡算法,将固定的负载量分布到不同的服务器上,以此来降低单台服务器的负载,达到优化的效果
74527544

@@ -7641,9 +7733,33 @@ MySQL 5.7.22 的并行复制策略在通用性上是有保证的,但是对于
76417733

76427734

76437735

7736+
#### 健康检测
7737+
7738+
主库发生故障后从库会上位,**其他从库指向新的主库**,所以需要一个健康检测的机制来判断主库是否宕机
7739+
7740+
* select 1 判断,但是高并发下检测不出线程的锁等待的阻塞问题
7741+
7742+
* 查表判断,在系统库(mysql 库)里创建一个表,比如命名为 health_check,里面只放一行数据,然后定期执行。但是当 binlog 所在磁盘的空间占用率达到 100%,所有的更新和事务提交语句都被阻塞,查询语句可以继续运行
7743+
7744+
* 更新判断,在健康检测表中放一个 timestamp 字段,用来表示最后一次执行检测的时间
7745+
7746+
```mysql
7747+
UPDATE mysql.health_check SET t_modified=now();
7748+
```
7749+
7750+
节点可用性的检测都应该包含主库和备库,为了让主备之间的更新不产生冲突,可以在 mysql.health_check 表上存入多行数据,并用主备的 server_id 做主键,保证主、备库各自的检测命令不会发生冲突
7751+
7752+
7753+
7754+
***
7755+
7756+
7757+
7758+
7759+
76447760
#### 基于位点
76457761

7646-
主库发生故障后从库会上位,**其他从库指向新的主库**从库(B)执行 CHANGE MASTER TO 命令需要指定 MASTER_LOG_FILE、MASTER_LOG_POS 表示从新主库(A)的哪个文件的哪个位点开始同步,这个位置就是**同步位点**,对应主库的文件名和日志偏移量
7762+
主库上位后,从库(B)执行 CHANGE MASTER TO 命令,指定 MASTER_LOG_FILE、MASTER_LOG_POS 表示从新主库(A)的哪个文件的哪个位点开始同步,这个位置就是**同步位点**,对应主库的文件名和日志偏移量
76477763

76487764
寻找位点需要找一个稍微往前的,然后再通过判断跳过那些在从库 B 上已经执行过的事务,获取位点方法:
76497765

@@ -7673,9 +7789,9 @@ MySQL 5.7.22 的并行复制策略在通用性上是有保证的,但是对于
76737789

76747790

76757791

7676-
#### 基于GITD
7792+
#### 基于GTID
76777793

7678-
##### GITD
7794+
##### GTID
76797795

76807796
GTID 的全称是 Global Transaction Identifier,全局事务 ID,是一个事务**在提交时生成**的,是这个事务的唯一标识,组成:
76817797

@@ -7718,7 +7834,7 @@ GTID 有两种生成方式,使用哪种方式取决于 session 变量 gtid_nex
77187834
START SLAVE;
77197835
```
77207836

7721-
前三条语句通过**提交一个空事务**把这个 GTID 加到实例 Y 的 GTID 集合中,实例 Y 就会直接跳过这个事务
7837+
前三条语句通过**提交一个空事务**把 X 的 GTID 加到实例 Y 的 GTID 集合中,实例 Y 就会直接跳过这个事务
77227838

77237839

77247840

@@ -8541,8 +8657,8 @@ SQL 注入攻击演示
85418657

85428658
PreparedStatement:预编译 sql 语句的执行者对象,继承 `PreparedStatement extends Statement`
85438659

8544-
* 在执行 sql 语句之前,将 sql 语句进行提前编译明确 sql 语句的格式,剩余的内容都会认为是参数
8545-
* sql 语句中的参数使用 ? 作为占位符
8660+
* 在执行 sql 语句之前,将 sql 语句进行提前编译**明确 sql 语句的格式**,剩余的内容都会认为是参数
8661+
* sql 语句中的参数使用 ? 作为**占位符**
85468662

85478663
为 ? 占位符赋值的方法:`setXxx(int parameterIndex, xxx data)`
85488664

@@ -9199,7 +9315,7 @@ Redis (REmote DIctionary Server) :用 C 语言开发的一个开源的高性
91999315
* 数据间没有必然的关联关系,**不存关系,只存数据**
92009316
* 数据**存储在内存**,存取速度快,解决了磁盘 IO 速度慢的问题
92019317
* 内部采用**单线程**机制进行工作
9202-
* 高性能,官方测试数据,50 个并发执行100000 个请求,读的速度是110000 次/s,写的速度是81000次/s
9318+
* 高性能,官方测试数据,50 个并发执行 100000 个请求,读的速度是 110000 次/s,写的速度是 81000次/s
92039319
* 多数据类型支持
92049320
* 字符串类型:string(String)
92059321
* 列表类型:list(LinkedList)
@@ -11456,7 +11572,7 @@ Redis 采用惰性删除和定期删除策略的结合使用
1145611572

1145711573
定时删除和惰性删除这两种方案都是走的极端,定期删除就是折中方案
1145811574

11459-
定期删除是周期性轮询 Redis 库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
11575+
定期删除是**周期性轮询 Redis 库中的时效性**数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
1146011576

1146111577
定期删除方案:
1146211578

Frame.md

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3469,19 +3469,7 @@ RCVBUF_ALLOCATOR:属于 SocketChannal 参数
34693469

34703470
![](https://gitee.com/seazean/images/raw/master/Frame/RocketMQ-数据分发.png)
34713471

3472-
主要缺点包含以下几点:
34733472

3474-
* 系统可用性降低:系统引入的外部依赖越多,系统稳定性越差,一旦 MQ 宕机,就会对业务造成影响
3475-
3476-
引申问题:如何保证 MQ 的高可用?
3477-
3478-
* 系统复杂度提高:MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用
3479-
3480-
引申问题:如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
3481-
3482-
* 一致性问题:A 系统处理完业务,通过 MQBCD 三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败
3483-
3484-
引申问题:如何保证消息数据处理的一致性?
34853473

34863474

34873475

@@ -4528,7 +4516,7 @@ Broker 包含了以下几个重要子模块:
45284516
RocketMQ 的工作流程:
45294517

45304518
- 启动 NameServer 监听端口,等待 BrokerProducerConsumer 连上来,相当于一个路由控制中心
4531-
- Broker 启动,跟所有的 NameServer 保持长连接,每隔 30s 时间向 NameServer 上报 Topic 路由信息(心跳包)。心跳包中包含当前 Broker 信息(IP、端口等)以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 TopicBroker 的映射关系
4519+
- Broker 启动,**所有的 NameServer 保持长连接**,每隔 30s 时间向 NameServer 上报 Topic 路由信息(心跳包)。心跳包中包含当前 Broker 信息(IP、端口等)以及存储所有 Topic 信息。注册成功后,NameServer 集群中就有 TopicBroker 的映射关系
45324520
- 收发消息前,先创建 Topic,创建 Topic 时需要指定该 Topic 要存储在哪些 Broker 上,也可以在发送消息时自动创建 Topic
45334521
- Producer 启动时先跟 NameServer 集群中的**其中一台**建立长连接,并从 NameServer 中获取当前发送的 Topic 存在哪些 Broker 上,同时 Producer 会默认每隔 30s 向 NameServer **定时拉取**一次路由信息
45344522
- Producer 发送消息时,根据消息的 Topic 从本地缓存的 TopicPublishInfoTable 获取路由信息,如果没有则会从 NameServer 上重新拉取并更新,轮询队列列表并选择一个队列 MessageQueue,然后与队列所在的 Broker 建立长连接,向 Broker 发消息
@@ -4698,9 +4686,9 @@ RocketMQ 网络部署特点:
46984686

46994687
- NameServer 是一个几乎**无状态节点**,节点之间相互独立,无任何信息同步
47004688

4701-
- Broker 部署相对复杂,Broker 分为 MasterSlaveMaster 可以部署多个,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个 MasterMasterSlave 的对应关系通过指定相同 BrokerName、不同 BrokerId 来定义,**BrokerId0Master,非 0 表示 Slave。每个 BrokerNameServer 集群中的所有节点建立长连接**,定时注册 Topic 信息到所有 NameServer
4689+
- Broker 部署相对复杂,Broker 分为 MasterSlaveMaster 可以部署多个,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个 MasterMasterSlave 的对应关系通过指定相同 BrokerName、不同 BrokerId 来定义,BrokerId0Master,非 0 表示 Slave**每个 BrokerNameServer 集群中的所有节点建立长连接**,定时注册 Topic 信息到所有 NameServer
47024690

4703-
注意:部署架构上也支持一 MasterSlave,但只有 BrokerId=1 的从服务器才会参与消息的读负载(读写分离)
4691+
说明:部署架构上也支持一 MasterSlave,但只有 BrokerId=1 的从服务器才会参与消息的读负载(读写分离)
47044692

47054693
- ProducerNameServer 集群中的其中**一个节点(随机选择)建立长连接**,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Master 建立长连接,且定时向 Master **发送心跳**Producer 完全无状态,可集群部署
47064694

@@ -4710,8 +4698,6 @@ RocketMQ 网络部署特点:
47104698

47114699
![](https://gitee.com/seazean/images/raw/master/Frame/RocketMQ-集群架构.png)
47124700

4713-
集群工作流程:参考通信机制 → 工作流程
4714-
47154701

47164702

47174703
官方文档:https://github.com/apache/rocketmq/blob/master/docs/cn/architecture.md
@@ -4722,12 +4708,12 @@ RocketMQ 网络部署特点:
47224708

47234709

47244710

4725-
#### 高可用
4726-
4727-
Consumer 的配置文件中,并不需要设置是从 Master 读还是从 Slave 读,当 Master 不可用或者繁忙的时候,Consumer 会被自动切换到从 Slave 读。有了自动切换的机制,当一个 Master 机器出现故障后,Consumer 仍然可以从 Slave 读取消息,不影响 Consumer 程序,达到了消费端的高可用性
4711+
#### 高可用性
47284712

47294713
在创建 Topic 的时候,把 Topic 的多个 Message Queue 创建在多个 Broker 组上(相同 Broker 名称,不同 brokerId 的机器组成一个 Broker 组),当一个 Broker 组的 Master 不可用后,其他组的 Master 仍然可用,Producer 仍然可以发送消息
47304714

4715+
Consumer 的配置文件中,并不需要设置是从 Master 读还是从 Slave 读,当 Master 不可用或者繁忙的时候,Consumer 会被自动切换到从 Slave 读。有了自动切换的机制,当一个 Master 机器出现故障后,Consumer 仍然可以从 Slave 读取消息,不影响 Consumer 程序,达到了消费端的高可用性
4716+
47314717
RocketMQ 目前还不支持把 Slave 自动转成 Master,需要手动停止 Slave 角色的 Broker,更改配置文件,用新的配置文件启动 Broker
47324718

47334719
![](https://gitee.com/seazean/images/raw/master/Frame/RocketMQ-高可用.png)
@@ -6132,6 +6118,8 @@ MappedFileQueue 用来管理 MappedFile 文件
61326118
private volatile long beginTimeInLock = 0; // 写数据时加锁的开始时间
61336119
protected final PutMessageLock putMessageLock; // 写锁,两个实现类:自旋锁和重入锁
61346120
```
6121+
6122+
因为发送消息是需要持久化的,在 Broker 端持久化时会获取该锁,**保证发送的消息的线程安全**
61356123

61366124
构造方法:
61376125

@@ -6805,7 +6793,7 @@ GroupTransferService 用来控制数据同步
68056793

68066794
###### 成员属性
68076795

6808-
HAClient 是 slave 端运行的代码,用于和 master 服务器建立长连接,上报本地同步进度,消费服务器发来的 msg 数据
6796+
HAClient 是 slave 端运行的代码,用于** master 服务器建立长连接**,上报本地同步进度,消费服务器发来的 msg 数据
68096797

68106798
成员变量:
68116799

@@ -7225,8 +7213,6 @@ WriteSocketService 类是一个任务对象,master向 slave 传输的数据帧
72257213

72267214
* `if (writeSize > 0)`:写数据成功,但是不代表 SMBR 中的数据全部写完成
72277215

7228-
7229-
72307216
* `boolean result`:判断是否发送完成,返回该值
72317217

72327218

@@ -7947,7 +7933,7 @@ DefaultMQProducerImpl 类是默认的生产者实现类
79477933
private SendResult sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout)
79487934
```
79497935

7950-
* `brokerAddr = this.mQClientFactory(...)`:获取指定 BrokerName 对应的 mater 节点的地址,master 节点的 ID0
7936+
* `brokerAddr = this.mQClientFactory(...)`:**获取指定 BrokerName 对应的 mater 节点的地址**,master 节点的 ID0,集群模式下,**发送消息要发到主节点**
79517937

79527938
* `brokerAddr = MixAll.brokerVIPChannel()`:Broker 启动时会绑定两个服务器端口,一个是普通端口,一个是 VIP 端口,服务器端根据不同端口创建不同的的 NioSocketChannel
79537939

@@ -8374,7 +8360,7 @@ MQClientInstance 是 RocketMQ 客户端实例,在一个 JVM 进程中只有一
83748360
MQClientInstance.this.adjustThreadPool();
83758361
```
83768362

8377-
* updateTopicRouteInfoFromNameServer():**更新路由数据**
8363+
* updateTopicRouteInfoFromNameServer():**更新路由数据**,通过加锁保证当前实例只有一个线程去更新
83788364

83798365
* `if (isDefault && defaultMQProducer != null)`:需要默认数据
83808366

@@ -8388,13 +8374,15 @@ MQClientInstance 是 RocketMQ 客户端实例,在一个 JVM 进程中只有一
83888374

83898375
* `if (changed)`:不一致进入更新逻辑
83908376

8391-
`Update Pub info`:更新生产者信息
8377+
`this.brokerAddrTable.put(...)`:更新客户端 broker 物理**节点映射表**
83928378

8379+
`Update Pub info`:更新生产者信息
8380+
83938381
* `publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData)`:将主题路由数据转化为发布数据,会**创建消息队列 MQ**,放入发布数据对象的集合中
83948382
* `impl.updateTopicPublishInfo(topic, publishInfo)`:生产者将主题的发布数据保存到它本地,方便发送消息使用
8395-
8396-
`Update sub info`:更新消费者信息,创建 MQ 队列,更新订阅信息,用于负载均衡
83978383

8384+
`Update sub info`:更新消费者信息,创建 MQ 队列,更新订阅信息,用于负载均衡
8385+
83988386
`this.topicRouteTable.put(topic, cloneTopicRouteData)`:**将数据放入本地路由表**
83998387

84008388

@@ -9537,7 +9525,7 @@ PullAPIWrapper 类封装了拉取消息的 API
95379525

95389526
* pullKernelImpl():拉消息
95399527

9540-
* `FindBrokerResult findBrokerResult`:查询指定 BrokerName 的地址信息,主节点或者推荐节点
9528+
* `FindBrokerResult findBrokerResult`:**本地查询指定 BrokerName 的地址信息**,推荐节点或者主节点
95419529

95429530
* `if (null == findBrokerResult)`:查询不到,就到 Namesrv 获取指定 topic 的路由数据
95439531

@@ -9603,7 +9591,7 @@ private RemotingCommand processRequest(final Channel channel, RemotingCommand re
96039591

96049592
* `switch (this.brokerController.getMessageStoreConfig().getBrokerRole())`:如果当前主机节点角色为 slave 并且**从节点读**并未开启的话,直接给客户端 一个状态 `PULL_RETRY_IMMEDIATELY`,并设置为下次从主节点读
96059593

9606-
* `if (this.brokerController.getBrokerConfig().isSlaveReadEnable())`:消费太慢,下次从另一台机器拉取
9594+
* `if (this.brokerController.getBrokerConfig().isSlaveReadEnable())`:消费太慢,**下次从另一台机器拉取**
96079595

96089596
* `switch (getMessageResult.getStatus())`:根据 getMessageResult 的状态设置 response 的 code
96099597

0 commit comments

Comments
 (0)