主从模式
RocketMQ通过Broker的主从模式实现服务的高可用,Slave节点定时向Master节点发送请求同步最新数据,并且在4.5版本后支持主从节点的自动切换,当主节点出现故障后,根据RAFT算法从Slave节点中选择一个作为Master,保证Broker服务的正常生产消费。
主从模式优点:
- 数据备份 : 数据冗余多份,一定程度上保证了Master出现不可恢复的故障后,数据不丢失。
- 高可用性 : 即使Master宕机,Consumer会自动重连到对应的Slave节点,不会出现消费停滞的情况。
- 读写分离 : Slave节点可以一定程度上缓解Master节点的读压力。
元数据复制
BrokerController类有个handleSlaveSynchronize方法,当role为BrokerRole.SLAVE时,会注册一个定时任务,每隔10秒钟执行一次SlaveSynchronize类的syncAll()方法,进行元数据的复制:
1 | public void syncAll() { |
没有什么复杂的逻辑,就是通过NameServer可以得到Master节点的服务地址,然后直接调用接口就能拿到所有数据信息。Consumer在拉取完消息并消费完毕后,仍然是向Master节点提交消费进度,Slave定时进行拉取刷新。当Master节点宕机后,Consumer无法向Master提交offset信息,对此RocketMQ提供了俩种机制确保不丢失offset信息。
第一种是Consumer端将消费进度存储在自身内存中并标记hasCommitOffsetFlag属性为true,仍然定时向Broker反馈offset信息,当Broker恢复后会收到并更新offset,从节点也会定时同步Master最新offset,如果再恢复期间Consumer宕机或关闭服务,仍然会丢失部分消费进度,造成数据重复消费。
第二种是消费者在向Broker节点拉取消息时,如果为Master节点,拉取消息后会顺带更新Master的offset。
CommitLog复制
复制流程:
- AcceptSocketService: Master启动后会运行此线程,监听并接受Slave的连接请求。
- HAConnection: 每接受一个Slave的连接请求,都会创建一个对应的HAConnection,本质是对SocketChannel的read/write的封装。
- SocketReadService: 由HAConnection创建,是对SocketChannel中read的封装,作用是读取slave发送的已同步offset。
- SocketWriteService: 由HAConnection创建,是对SocketChannel中write的封装,作用是将消息发送给Slave。
- GroupTransferService: 同步复制模式下,将Master挂起直到Slave发出同步成功信号后唤醒。
异步复制
异步复制的好处是将消息写入commitlog后直接返回,无需关心与Slave的数据同步问题,消息同步给Slave的逻辑处理由HAConnection异步处理。消息持久化后无需等待Slave确认是否存储成功,从效率上高于同步复制,缺点是如果Master宕机,由于数据同步有延迟导致Slave和Master存在一定程度的数据不一致问题,适用于数据可靠性要求不高的业务场景。
同步复制
同步复制与异步复制的唯一区别是持久化消息后,需等待Slave节点返回此消息的同步结果,期间被GroupTransferService线程使用CountDownLatch阻塞挂起,直到Slave返回成功结果或超时再进行唤醒。这种模式不会造成消息丢失,但是工作效率相对降低,适用于数据可靠性要求很高的业务场景。
判断主从同步是否完成的依据是Slave中已成功复制的最大消息偏移量是否大于等于消息生产者发送消息后消息服务端返回的下一条消息的起始偏移量。如果大于等于说明主从同步完成,否则等待1秒后继续检查,每一批任务中循环5次加上初始的一次一共6次,6次还没确认就关闭连接。
这里只需要有一个Slave复制成功并成功应答即算成功。
读写分离
传统的读写分离是完全按照Master节点写入、Slave节点读取的规则进行,而RocketMQ有属于自己的一套读写分离逻辑。在默认情况下RocketMQ会优先选择从Master进行拉取消息,并且计算堆积量是否超过物理内存40%,表示主服务繁忙,当超过时则建议Consumer下次从Slave拉取消息。
开启读写分离需要Master与Slave添加配置slaveReadEnable=true,默认为false。