我理解的RocketMQ:主从复制HA(high availability)的机制分析

本文详细分析了RocketMQ的主从复制机制,包括主从Broker在启动阶段的准备工作,从Broker如何与主Broker建立连接,反馈消费进度,以及主Broker如何发送同步消息。在主从同步过程中,主Broker通过后台线程主动发送消息到从Broker,并检查从Broker的复制进度,确保主从一致性。关键类包括HaService、SlaveSynchronizer、MasterSelector等。
摘要由CSDN通过智能技术生成

1 概述

主从消息复制方式:RocketMQ中主从同步采用的是主节点主动向从节点发送同步消息,是由一个后台不断运行的线程执行。注意是后台。并不是生产者给主Broker发送消息,主Broker处理接收消息时进行显示调用同步消息给从Broker。

从Broker向主Broker反馈主从消息复制进度:从Broker定时的向主Broker反馈复制消息进度。主Broker便知道从Broker的消息复制进度。这个反馈主要是用来实现主从同步复制

发送消息同步的方式进行主从消息复制的实现:主Broker在处理接收消息时,去询问从Broker复制消息的进度是否已经到达当前消息所在的偏移量,如果是就返回,否则就进行等待。

下图是整个主从同步时,从Broker和主Broker的交互过程

在这里插入图片描述

初始化

  • 副节点:启动HAServiceHAService创建HAClientHAClient是一个不断运行的RocketMQ服务线程,其作用就是与主节点建立连接、接收主节点的同步消息、向主节点同步确认其本地消息偏移量。

  • 主节点:启动HAServiceHAService创建并启动AcceptSocketService,顾名思义,此服务主要就是接受Socket连接。

主从复制消息步骤

  • 第一步:从Broker与主Broker之间建立网络连接,由从Broker主动发起连接
    ,从Broker通过HAClient向主节点请求建立TCP连接,主Broker的AcceptSocketService接受连接请求,并建立TCP连接通道SocketChannel,并用HAConnection来进行包装。HAConnection表示主Broker与某个从Broker之间的连接关系,处理了主Broker与从Broker之间的主从同步消息。它里面维护了两个服务:ReadSocketServiceWriteSocketService,前者主要处理网络通道的读事件(从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:主从复制模块对外的类,开启主从复制功能时,首先创建该类实例,然后通过它创建相关组件。然后通过启动其来启动整个主从复制模块。

AcceptSocketServiceHAService的内部类,主Broker使用,主要用来监听从Broker的网络连接。

GroupTransferServiceHAService的内部类,主Broker使用,主要用来实现发送消息时的主从"同步"复制功能。

HAClientHAService的内部类,从Broker使用,用来向主Broker发起网络连接,处理与主Broker复制消息的事情。

HAConnection:主Broker使用,此类是在主Broker接受从Broker网络连接时,对主从连接主端SocketChannel的封装,一对一的处理与从Broker之间的复制消息事情。

ReadSocketServiceHAConnection的内部类,主Broker使用,向从Broker发送消息。

WriteSocketServiceHAConnection的内部类,主Broker使用,接受从Broker反馈的消息偏移量。

在这里插入图片描述

2.2 主从Broker在启动阶段对主从复制做的准备工作

主/从Broker其实是一样的启动步骤:

  • 实例化主要作用类的对象
  • 启动相应的服务。
2.2.1 实例化主要作用类的对象

创建HAService对象,HAService实例化时创建了AcceptSocketService,GroupTransferServiceHAClient。前两者是主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中标准的服务线程的使用模式,在该方法中会创建一个线程,并使用该线程运行acceptSocketServicerun()方法,所以只需关注其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();
            }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值