RocketMQ 主从同步( HA)机制

高可用特性是目前分布式系统中必备的特性之一,对一个中 间件来说没有 HA 机制是一个重大的缺陷,本章将主要分析 RocketMQ 主从同步( HA ) 机制 。
本章重点内容如下 。
• 主从同步复制实现原理 。
• RocketMQ 读写分离机制 。

1.RocketMQ 主从复制原理

为了提高消息消费的高可用性,避免 Broker 发生单点故障引起存储在 Broker 上的消
息无法及时消 费, RocketMQ 引 入了 Broker 主备机制 , 即消息消费到达主服务器后需要将消息同步到消息从服务器,如果主服务器 Broker 宕机后,消息消费者可以从从服务器拉取消息 。接下来将详细探讨 RocketMQ HA 的实现原理,
从图 7-1 中我们知道 RocketMQ HA 由 7 个核心类实现,分别如下 。
1 ) HAService: RocketMQ 主从同步核心实现类 。
2 ) HAService$AcceptSocketService : HA Master 端监昕客户端连接实现类 。
3 ) HAService$GroupTransferService :主从同步通知实现类。
4 ) HAService$HAClient: HA Client 端实现类。
5 ) HAConnection : HA Master 服务端 HA 连接对象的封装,与 Broker 从服务器的网络读写实现类 。
6 ) HAConnection$ReadSocketService: HA Master 网络读实现类 。
7 ) HAConnection$WriteSocketServicce: HA Master 网络写实现类 。
1 .1 HAService 整体工作机制
RocketMQ HA 的实现原理如下 。
1 )主服务器启动,并在特定端口上监听从服务器的连接 。
2 )从服务器主动连接主服务器,主服务器接收客户端 的连接,并建立相关TCP连接。
3 )从服务器主动向主服务器发送待拉取消息偏移量 ,主服务器解析请求并返回消息给从服务器 。
4 )从服务器保存消息并继续发送新的消息同步请求 。
 

1.2 AcceptSocketService 实现原理
AcceptSocketService 作为 HA Service 的内部类,实现 Master 端监听 Slave 连接
AcceptSocketService 的属性如下 。
1 ) SocketAddress socketAddressListen: Broker 服务监听套接字(本地 IP+端口 号) 
2 ) ServerSocketChannel serverSocketChannel :服务端 Socket 通道, 基于 NIO 。
3 ) Selector selector : 事件选择器,基于 NIO 。
创建ServerSocketChannel 、创建 Selector 、设置 TCP reuseAddress 、绑定监听端口 、设置为非阻塞模式,并注册 OP_ACCEPT (连接事件) 。

该方法是标准的基于 NIO 的服务端程式实例,选择器每 ls 处理一次连接就绪事件 。 连接事件就绪后,调用 ServerSocketChannel 的 accept()方法创建 SocketChannel 。 然后为每一个连接创建一个 HAConnection 对象 , 该 HAConnection 将负责 M-S 数据同步逻辑 。

1.3 GroupTransferService 实现原理
GroupTransferService 主从同步阻塞实现,如果是同步主从模式,消息发送者将消息刷写到磁盘后,需要继续等待新数据被传输到从服务器,从服务器数据的复制是在另外一个线程 HAConnection 中去拉取,所以消息发送者在这里需要等待数据传输的结果,
GroupTransferService 就是实现该功能,该类的 整体结构与同步刷盘实现类(CommitLog$-GroupCommitService )类似。


GroupTransferService 的职责是负责当主从同步复制结束后通知由于等待 HA 同步结果而阻塞的消息发送者线程 。 判断主从同步是否完成的依据是 Slave 中已成功复制的最大偏移量是否大于等于消息生产者发送消息后消息服务端返回下一条消息的起始偏移量 ,如果是则表示主从同步复制已经完成,唤醒消息发送线程 ,否则等待 ls 再次判断, 每一个任务在一批任务中循环判断 5 次 。 消息发送者返回有两种情况: 等待超过 5s或 GroupTransferService通知主从复制完成 。 可以通过 syncFlushTimeout 来设置发送线程等待超时时间 。
GroupTransferService 通知主从复制的实现如下 。HAService$GroupTransferService#notifyTransferSome
该方法在 Master 收到从服务器的拉取请求后被调用,表示从服务器当前 已同步的偏移量,既然 收到从服务器的反馈信息,需要唤醒某些消息发送者线程 。 如果从服务器收到的确认偏移量大于 push2SlaveMaxOffset,则更新 push2SlaveMaxOffset,然后唤醒
GroupTransferService 线程,各消息发送者线程再次判断自己 本次发送的消息是否已经成功复制到从服务器。
 

1.4 HAClient 实现原理
HAClient 类的基本属性和常量如下 。
1 ) private static final int READ_MAX_BUFFER_SIZE = 1024 * 1024 * 4: Socket 读缓
存区大小 。
2) AtomicReference<String> masterAddress: master 地址 。
3 ) ByteBuffer reportOffset = ByteBuffer.allocate ( 8 ) : Slave 向 Master 发起主从同步的拉取偏移量 。
4 ) SocketChannel sock巳tChannel :网络传输通道 。
5 ) Selector selector: NIO 事件选择器 。
6 ) long lastWriteTimestamp : 上一次写入时间戳 。
7 ) long currentReportedOffset :反馈 Slave 当前的复制进度, commitlog 文件最大偏移量。
8 ) dispatchPostion : 本次己处理读缓存区的指针 。
9) ByteBuffer byteBufferRead :读缓存区,大小为 4M 。
I 0 ) ByteBuffer byteBufferBackup : 读缓存区备份,与 BufferRead 进行交换 。


Step1: Slave 服务器连 接 Master 服 务器 。 如果 socketChannel 为 空, 则 尝 试连接
Master 。 如果 master 地址为 空 ,返回 false ;如果 master 地址不为 空 ,则建立 到 Master 的TCP 连接, 然后注册 OP_READ (网络读事件 ), 初始化 currentR巳ported Offset 为 commitlog文件的最大偏移量 、 lastWriteTimestamp 上次写人时间戳为当前时间 戳,并返 回 true 。 在Broker 启动时,如果 Broker 角色为 SLAVE 时将读取 Broker 配置文件中的 haMasterAddress属性并更新 HAClient 的 masterAddrees ,如果角色为 SLAVE 并且 haMasterAddress 为 空 ,启动并不会报错,但不会执行主从同步复制, 该方法最终返回是否成功连接上 Master。
Step2 :判断是否需要 向 Master 反馈当前待拉取偏移量 , Master 与 Slave 的 HA 心跳发送间隔默认为5S,可通过配置 haSendHeartbeatlnterval 来改变心跳间隔 。
Step3 :向 Master 服务器反馈拉取偏移量 。 这里有两重意义 , 对于 Slave 端来说,是发送下次待拉取消息偏移量,而对于 Master 服务端来说,既可 以认为是 Slave 本次请求拉取的消息偏移量,也可以理解为 Slave 的消息同步 ACK 确认消息 。
Step4 :进行事件选择, 其执行间隔为 1s 。
Step5 :处理网络读请求,即处理从 Master 服务器传田的消息数据 。 同样 RocketMQ 作者给出了一个处理网络读的 NIO 示例 。 循环判断 readByteBuffer 是否还有剩余空间,如果存在剩余空间,则调用 SocketChannel#read ( ByteBuffer readByteBuffer),将通道中的数据读入到读缓存区中 。
1 )如果读取到的字节数大于 0 ,重置读取到 0 字节的次数,并更新最后一次写入时间
戳( lastWriteTimestamp ),然后调用 dispatchReadRequest 方法将读取到的所有消息全部追
加到消息内存映射文件中,然后再次反馈拉取进度给服务器 。
2 )如果连续 3 次从网络通道读取到 0 个字节,则结束本次读,返回 true 。
3 )如果读取到的字节数小于 0 或发生 IO 异常,则返回 false 。
HAClient 线程反复执行上述 5 个步骤完成主从同步复制功能 。
 

1.5 HAConnection 实现原理
Master 服务器在收到从服务器的连接请求后,会将主从服务器的连接 SocketChannel 封装成 HAConnection 对象,实现主服务器与从服务器的读写操作 。
Step1 :如果 byteBufferRead 没有剩余 空 间,说明该 position==limit==capacity ,调用byteBufferRead.flip()方法, 产生 的效果为 position=0,limit=capacity 并设置 processPostion为 0 ,表示从头开始处理, 其实这里调用 byteBuffer.clear()方法会更加容易理解 。
Step2 : NIO 网络读的常规方法,一般使用循环的方式进行读写,直到 byteBuffer 中没有剩余的空间 。
Step3 :如果读取的字节大于 0 并且本次读取到的内容大于等于 8 ,表明收到了从服务
器一条拉取消息的请求 。 由于有新的从服务器反馈拉取偏移量, 服务端会通知由于同步等待 HA 复制结果而阻塞的消息发送者线程 。
Step4 :如果读取到的字节数等于 0 ,则重复三次,否则结束本次读请求处理 ;如果读取到的字节数小于 0 ,表示连接处于半关闭状态 , 返回 false 则意味着消息服务器将关闭该链接。

2 RocketMQ 读写分离机制
本节主要介绍从服务器如何参与消息拉取负载机制 。
接下来将重点分析 RocketMQ 根据 brokerName 查找 Broker 地址的过程 。RocketMQ 根据 MessageQueu巳查找 Broker 地址的唯一依据是 brokerName ,从 RocketMQ 的 Broker 组织结构中得知同 一组 Broker ( M-S )服务器,它们的 brokerName 相同但 brokerId 不同,主服务器的 brokerId 为 0 ,从服务器的 brokerId 大于 0, RocketMQ 提
供 MQClientFactory.findBrokerAddresslnSubscribe 来实现根据 brokerName 、 brokerId 查找Broker 地址 。

MQClientlnstance#findBrokerAddresslnSubscribe(String brokerName, long brokerid, boolean onlyThisBroker)

1 )首先解释一下该方法的参数 。
brokerName: Broker 名称 。
brokerld: BrokerId 。
onlyThisBroker : 是否必须返回 brokerId 的 Broker 对应的服务器信息 。
2 )从 ConcurrentMap < String/* Broker Name*/, HashMap<Long/* brokerld */, String/*address */>> brokerAddrTable 地址缓存表中根据 brokerName 获取所有的 Broker 信息 。
3 )根据 brokerId从Broker 主从缓存表中获取指定 Broker 名称, 如果根据 brokerId 未找到相关条目,此时若 onlyThisBroker 为 false ,则随机返回 Broker 中任意一个Broker ,否则返回 null 。
4 )组装FindBrokerResult 时, 需要设置是否是 slave 这个属性 。 如果 brokerld=0 , 表示返回的 Broker 是主节点,否则返回的是从节点 。
那消息服务端是根据何种规则来建议哪个消息消费队列该从哪台 Broker 服务器上拉取
消息呢?
DefaultMessageStore#getMessage
1 ) maxOffsetPy :代表当前主服务器消息存储文件最大偏移量 。
2 ) maxPhyOffsetPulling :此次拉取消息最大偏移量 。
3 ) diff:对于 Pul!MessageService 线程来说,当前未被拉取到消息消费端的消息长度
4 ) TOTAL_PHYSICAL_MEMORY_SIZE : RocketMQ 所在服务器总内存大小 。 accessMessagelnMemoryMaxRatio 表示 RocketMQ 所能使用的最大内存比例,超过该内存,消息将被置换出内存; memory 表示 RocketMQ 消息常驻内存的大小,超过该大小, RocketMQ会将旧的消息置换回磁盘 。
5 )如果 diff 大于 memory,表示当前需要拉取的消息已经超出了常驻内存的大小,表示主服务器繁忙,此时才建议从从服务器拉取 。
如果主服务器繁忙则建议下一次从从服务器拉取消息,设置 suggestWhichBrokerld 为
配置文件中 whichBrokerWhenConsumeSlowly 属性,默认为 1 。 如果一个 Master 拥有多台Slave服务器,参与消息拉取负载的从服务器只会是其中一个 。
 

3 本章小结
本章重点剖析了 RocketMQ HA 主从同步负载机制与主从服务器读写分离机制 。
RocketMQ 的 HA 机制 ,其核心实现是从服务器在启动的时候主动向主服务器建立 TCP长连接,然后获取服务器的 commitlog 最大偏移量 ,以此偏移量向主服务器主动拉取消息,主服务器根据偏移量,与自身 commitlog 文件的最大偏移量进行比较,如果大于从服务器的 commitlog 偏移量 ,主服务器将向从服务器返回一定数量的 消息, 该过程循环进行 ,达到主从服务器数据同步 。
RocketMQ 读写分离与其他中间件的实现方式完全不 同, RocketMQ 是消费者首先 向 主服务器发起拉取消息请求,然后主服务器返回一批消息,然后会根据主服务器负载压力与主从同步情况,向从服务器建议下次消息拉取是从主服务器还是从从服务器拉取 。
 

文章参考:《RocketMQ技术内幕》,作者:丁威,周继锋。

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值