1 概述
主从消息复制方式:RocketMQ中主从同步采用的是主节点主动向从节点发送同步消息,是由一个后台不断运行的线程执行。注意是后台。并不是生产者给主Broker发送消息,主Broker处理接收消息时进行显示调用同步消息给从Broker。
从Broker向主Broker反馈主从消息复制进度:从Broker定时的向主Broker反馈复制消息进度。主Broker便知道从Broker的消息复制进度。这个反馈主要是用来实现主从同步复制。
发送消息同步的方式进行主从消息复制的实现:主Broker在处理接收消息时,去询问从Broker复制消息的进度是否已经到达当前消息所在的偏移量,如果是就返回,否则就进行等待。
下图是整个主从同步时,从Broker和主Broker的交互过程
初始化
-
副节点:启动
HAService
,HAService
创建HAClient
,HAClien
t是一个不断运行的RocketMQ服务线程,其作用就是与主节点建立连接、接收主节点的同步消息、向主节点同步确认其本地消息偏移量。 -
主节点:启动
HAService
,HAService
创建并启动AcceptSocketService
,顾名思义,此服务主要就是接受Socket连接。
主从复制消息步骤
-
第一步:从Broker与主Broker之间建立网络连接,由从Broker主动发起连接
,从Broker通过HAClient
向主节点请求建立TCP连接,主Broker的AcceptSocketService
接受连接请求,并建立TCP连接通道SocketChannel
,并用HAConnection
来进行包装。HAConnection
表示主Broker与某个从Broker之间的连接关系,处理了主Broker与从Broker之间的主从同步消息。它里面维护了两个服务:ReadSocketService
和WriteSocketService
,前者主要处理网络通道的读事件(从Broker同步复制进度偏移量),后者主要用来处理写事件,也即主Broker向从Broker发送的复制消息。通过此步后,从Broker与主Broker之间就建立网络通道。图中的1、2两个步骤 -
第二步:从Broker向主Broker发送复制进度。从Broker通过
HAClient
向主Broker汇报其已经复制的消息偏移量。主Broker通过HAConnection
中的ReadSocketService
处理。即图中的3、4、5。 -
第三步:主Broker向从Broker发送复制消息。主Broker通过
WriteSocketService
向从Broker发送消息。从Broker通过HAClient
处理。即6、7、8、9、10
二三步不断执行下去,便实现了主从复制。
关键点
- 主Broker向从Broker发送消息的时候都会带上物理偏移量。从Broker接收消息并读取物理偏移量,并与其本地的物理偏移量进行比较,如果相等则存储并继续与主Broker进行通信。如果不相等,则表示主从复制出现了混乱,此时会主动断开与主Broker之间的TCP连接,重新建立一条TCP连接,再次开始主从复制。
- 使用原生Java NIO网络通信编程。
2 源码分析
2.1 主要类及其作用
HAService
:主从复制模块对外的类,开启主从复制功能时,首先创建该类实例,然后通过它创建相关组件。然后通过启动其来启动整个主从复制模块。
AcceptSocketService
:HAService
的内部类,主Broker使用,主要用来监听从Broker的网络连接。
GroupTransferService
:HAService
的内部类,主Broker使用,主要用来实现发送消息时的主从"同步"复制功能。
HAClient
:HAService
的内部类,从Broker使用,用来向主Broker发起网络连接,处理与主Broker复制消息的事情。
HAConnection
:主Broker使用,此类是在主Broker接受从Broker网络连接时,对主从连接主端SocketChannel
的封装,一对一的处理与从Broker之间的复制消息事情。
ReadSocketService
:HAConnection
的内部类,主Broker使用,向从Broker发送消息。
WriteSocketService
:HAConnection
的内部类,主Broker使用,接受从Broker反馈的消息偏移量。
2.2 主从Broker在启动阶段对主从复制做的准备工作
主/从Broker其实是一样的启动步骤:
- 实例化主要作用类的对象
- 启动相应的服务。
2.2.1 实例化主要作用类的对象
创建HAService
对象,HAService
实例化时创建了AcceptSocketService
,GroupTransferService
和HAClient
。前两者是主Broker用的,最后者是从Broker用的。
创建HAService对象实例:HAService是在实例化DefaultMessageStore
的对象时创建的,后者的构造函数:
public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, final BrokerStatsManager brokerStatsManager,
final MessageArrivingListener messageArrivingListener, final BrokerConfig brokerConfig) throws IOException {
...省略
// 判断是否启用了Dleger模式,也即Dleger模式与传统的主从模式采用的是不同的实现方式
if (!messageStoreConfig.isEnableDLegerCommitLog()) {
this.haService = new HAService(this);
} else {
this.haService = null;
}
...省略
}
HAService
的构造函数
public HAService(final DefaultMessageStore defaultMessageStore) throws IOException {
this.defaultMessageStore = defaultMessageStore;
// 主Broker用来接受从Broker连接的后台线程服务
// 也即Server中的accept
this.acceptSocketService =
new AcceptSocketService(defaultMessageStore.getMessageStoreConfig().getHaListenPort());
// 提供Broker设置“同步复制”时,发送消息时判断是否已将消息复制给Broker,一个后台服务线程
this.groupTransferService = new GroupTransferService();
// 启动从Broker实现主从复制的关键类
// 作用:与主Broker建立连接;报告从Broker消息复制偏移量;接收处理主Broker发送的消息落盘存储
this.haClient = new HAClient();
}
2.2.2 启动主从服务的相关的服务类
主从复制相关服务的启动都是通过HAService
来启动的。HAService
的启动函数start()
是在DefaultMessageStore
的启动函数start()
调用的。
启动的最终结果:
- 主Broker:开启监听从Broker的连接,
AcceptSocketService
服务线程启动 - 从Broker:HAClient服务线程启动
HAService#start()
:
public void start() throws Exception {
// 打开主Broker监听端口进行监听,创建ServerSocketChannel
// 从Broker将通过此端口来与主Broker进行通信
this.acceptSocketService.beginAccept();
// 启动接受连接服务
this.acceptSocketService.start();
this.groupTransferService.start();
// 启动从Broker使用的主从复制的服务客户端
this.haClient.start();
}
下面主要分析:this.acceptSocketService.beginAccept()
,this.acceptSocketService.start()
和this.haClient.start()
主Broker启动监听acceptSocketService#beginAccept()
,NIO启动服务端的标准写法。创建ServerSocketChannel
,绑定监听端口,设置为非阻塞模式,注册感兴趣的事件OP_ACCEPT
。至此主Broker就开始监听从Broker的连接。
public void beginAccept() throws Exception {
// 打开一个服务端监听通道
this.serverSocketChannel = ServerSocketChannel.open();
// 创建一个选择器
this.selector = RemotingUtil.openSelector();
this.serverSocketChannel.socket().setReuseAddress(true);
// 绑定监听端口号
this.serverSocketChannel.socket().bind(this.socketAddressListen);
// 设置为非阻塞模式
this.serverSocketChannel.configureBlocking(false);
// 注册selector, 感兴趣的事件为OP_ACCEPT
this.serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
}
主Broker启动AcceptSocketService
服务线程,acceptSocketService.start()
,此方法是RocketMQ中标准的服务线程的使用模式,在该方法中会创建一个线程,并使用该线程运行acceptSocketService
的run()
方法,所以只需关注其run()
方法。
其run()
方法是不断循环执行的,主要逻辑就是接受从Broker向主Broker发起的网络连接,获得一个SocketChannel
,并将此用HAConnnection
进行包装,一对一的处理与从Broker之间的事情。
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
// 轮询,超时设置为1000毫秒
this.selector.select(1000);
Set<SelectionKey> selected = this.selector.selectedKeys();
if (selected != null) {
for (SelectionKey k : selected) {
if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
// 获得网络通信通道
SocketChannel sc = ((ServerSocketChannel) k.channel()).accept();
if (sc != null) {
HAService.log.info("HAService receive new connection, "
+ sc.socket().getRemoteSocketAddress());
try {
// 对网络通道进行封装,创建HAConnection
HAConnection conn = new HAConnection(HAService.this, sc);
// 启动连接
conn.start();
HAService.this.addConnection(conn);
} catch (Exception e) {
log.error("new HAConnection exception", e);
sc.close();
}
}
} else {
log.warn("Unexpected ops in select " + k.readyOps());
}
}
selected.clear();
}