Broker 主从同步机制
一、主从同步概述
Broker 有2种角色:
-
- Master:主要用于处理生产者、消费者的请求和存储数据。
- Slave:从 Master 同步所有数据到本地。具体体现在以下2个方面:
-
- Broker 服务高可用。一般生产环境会部署两个主Broker节点和两个从Broker(2m2s),一个 Master 宕机后,另一个 Master 可以接管工作;如果两个 Master 都宕机,消费者可以通过连接 Slave 继续消费。这样可以保证服务的高可用。
- 提高服务性能。如果消费者从 Master Broker 拉取消息时,发现拉取消息的 offset 和 CommitLog 的物理 offset 相差太多,会转向 Slave 拉取消息,这样可以减轻 Master 的压力,从而提高性能。
Broker 同步数据的方式有2种:
-
- 同步复制:指客户端发送消息到 Master,Master 将消息同步复制到 Slave 的过程,可以通过设置参数 brokerRole=BrokerRole.SYNC_MASTER 来实现。这种消息配置的可靠性很强,但是效率比较低,适用于金融、在线教育等对消息有强可靠需求的场景。
- 异步复制:指客户端发送消息到 Master,再由异步线程 HAService 异步同步到 Slave的过程,可以通过设置参数 brokerRole=BrokerRole.ASYNC_MASTER 来实现。这种消息配置的效率非常高,可靠性比同步复制差,适用于大部分业务场景。
Broker 主从同步数据有两种:
-
- 配置数据:包含 Topic 配置、消息者位点信息、延迟消息位点信息、订阅关系配置等。
- 消息数据:生产者发送的消息,保存在 CommitLog中,由 HAService 服务实时同步到 Slave Broker 中。所有实现类都在 org.apache.rocketmq.store.ha 包下。
Broker 主从同步的逻辑是通过 D: ocketmq-masterrokersrcmainjavaorgapache ocketmqrokerslaveSlaveSynchronize.syncAll() 方法实现的,该方法在 D: ocketmq-masterrokersrcmainjavaorgapache ocketmqrokerBrokerController.initialize()方法中初始化,每 60s 同步一次,并且同步周期不能修改。
二、主从同步流程
2.1 名词解释
2.2 配置数据同步流程
每种配置数据由一个继承自 ConfigManager 的类来管理,配置数据包含4种类型:
-
- Topic 配置
- 消息位点
- 延迟位点
- 订阅关系配置
Slave 如何从 Master 同步这些配置呢?先来看一下初始化服务的步骤:
第一步:Master Broker 在启动时,初始化一个 BrokerOuterAPI,这个服务的功能包含 Broker 注册到 Namesrv、Broker 从 Namesrv 解绑、获取 Topic 配置信息、获取消费位点信息、获取延迟位点信息及订阅关系等。
第二步:Slave Broker 在初始化 Controller 的定时任务时,会初始化 SlaveSynchronize 服务,每 60s 调用一次 SlaveSynchronize.syncAll() 方法。
第三步:syncAll() 方法依次调用 4 种配置数据(Topic配置、消费者位点、延迟位点、订阅关系配置)的同步方法同步全量数据。
第四步:syncAll()中执行的4个方法都通过 Remoting 模块同步调用 BrokerOuterAPI,并从 Master Broker 获取数据,保存到 Slave 中。
第五步:Topic 配置和订阅关系配置随着保存内存信息的同事持久化到磁盘上;消费者位点通过 BrokerController 初始化定时任务持久化到磁盘上;延迟位点信息通过 ScheduleMessageService 定时将内存持久化到磁盘上,如下图:
2.3 CommitLog 数据同步流程
CommitLog 的数据同步分为2种:
- 同步复制:生产者生产消息后,等待 Master Broker 将数据同步到 Slave Broker 后,再返回生产者数据存储状态。
- 异步复制:生产者生产消息后,不用等待 Master Broker 将数据同步到 Slave Broker,直接返回 Master 存储结果。
详解:
(1)同步复制:在CommitLog 消息存储到 Page Cache 后,调用 CommitLog.handleHA() 方法处理同步复制,代码路径:D: ocketmq-masterstoresrcmainjavaorgapache ocketmqstoreCommitLog.java,具体代码如下:
1 public void handleHA(AppendMessageResult result, PutMessageResult putMessageResult, MessageExt messageExt) { 2 if (BrokerRole.SYNC_MASTER == this.defaultMessageStore.getMessageStoreConfig().getBrokerRole()) { #当前Master Broker 需要同步将消息“发送”到Slave. 3 HAService service = this.defaultMessageStore.getHaService(); 4 if (messageExt.isWaitStoreMsgOK()) { 5 // Determine whether to wait 6 if (service.isSlaveOK(result.getWroteOffset() + result.getWroteBytes())) { 7 GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes()); 8 service.putRequest(request); 9 service.getWaitNotifyObject().wakeupAll(); 10 PutMessageStatus replicaStatus = null; 11 try { 12 replicaStatus = request.future().get(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout(), 13 TimeUnit.MILLISECONDS); 14 } catch (InterruptedException | ExecutionException | TimeoutException e) { 15 } 16 if (replicaStatus != PutMessageStatus.PUT_OK) { 17 log.error("do sync transfer other node, wait return, but failed, topic: " + messageExt.getTopic() + " tags: " 18 + messageExt.getTags() + " client address: " + messageExt.getBornHostNameString()); 19 putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_SLAVE_TIMEOUT); 20 } 21 } 22 // Slave problem 23 else { 24 // Tell the producer, slave not available 25 putMessageResult.setPutMessageStatus(PutMessageStatus.SLAVE_NOT_AVAILABLE); 26 } 27 } 28 } 29 }
当 BrokerRole 配置为 SYNC_MASTER 时,表示当前 Master Broker 需要同步将消息 "发送" 到 Slave。根据 Master Broker CommitLog 的存储结果构造一个 GroupCommitRequest 放入 HAService 中,再将 GroupCommitRequest 放入 GroupTransferService 服务中,等待 GroupTransferService 同步成功的锁。如果同步成功那么 GroupCommitRequest 中的锁会被唤醒,并设置 flushOK 为 True,表示生产者发送的消息被 Master Broker 和 Slave Broker 同时保存。
一个 Master Broker 可以配置多个 Slave Broker,当需要同步数据时,通过 service.getWaitNotifyObject().wakeupAll() 来唤醒全部的 Slave 同步。虽然多个 Slave 都同步了数据,但是一旦 Master Broker 不可用时,消费者只会从一个 Slave 中拉取消息,所以生产环境建议 SLave 不要配置太多。
Slave 在发送请求数据的 Request 时,会带上 Slave 请求的位点 HAConnection.slaveRequestOffset,该值如果等于 -1(默认),则表示没有 Slave 请求过位点数据。
ReadSocketService 后台服务不断接受 Slave Broker 上报的 offset,每上报一次都通知 HAService.notifyTransferSome() 方法,判断 Slave 同步的位点是否大于 Master 标记的已同步位点。如果大于则更新标记值,同时通知同步复制服务 GroupTransferService。GroupTransferService 扫描所有的同步请求,依次判断哪些 GroupCommitRequest 的待同步复制的位点是比已同步位点小的,释放 GroupCommitRequest 中的锁,消息处理线程可以将消息存储成功过的结果返回给生产者。
消息队列文件(Consume Queue)和索引文件(Index File)为什么没有同步给 Slave 呢?因为这两个文件都可以在 Slave Broker 上追加 CommitLog 后由 ReputMessageService 进行创建,所以不需要同步。
(2)异步复制:Master Broker 启动时会启动 HAService.AcceptSocketService 服务,当监听来自 Slave 的注册请求时会创建一个 HAConnection,同时 HAConnection 会创建 ReadSocketService 和 WrieteSocketServcie 两个服务并启动,开始主从数据同步。
ReadSocketService 接收 Slave 同步数据请求,并将这些信息保存在 HAConnection 中。WriteSocketService 根据 HAConnection 中保存的 Slave 同步请求,从 CommitLog 中查询数据,并发送给 Slave。