Broker主从同步详解

本文详细介绍了RocketMQ Broker的主从同步过程,包括配置数据和CommitLog数据的同步,涉及同步复制和异步复制两种方式。同步复制提供高可靠性,而异步复制则能提高性能。主从同步确保服务高可用性和性能提升,详细解析了数据在Master和Slave间的同步流程。
摘要由CSDN通过智能技术生成

主从同步概述

众所周知,Broker中有两种角色:Master和Slave。Master主要用于处理生产者、消费者的请求和存储数据,Slave从Broker中同步所有的数据到本地,具体体现以下两个作用:

  • 保证Broker服务高可用:当Broker宕机时,消费者可以通过连接Slave继续消费,这样可以保证服务的高可用
  • 提高服务性能:消费者从Master Broker拉取消息时,发现拉取消息的offset和CommitLog的物理offset相差太多,会转向Slave拉取消息,这样可以减轻Master的压力,从而提高性能。

Broker同步数据的方式有两种:

  • 同步复制:指客户端发送消息到Master,Master将消息同步复制到Slave的过程,可以通过设置参数 brokerRole = BrokerRole.SYNC_MASTER来实现,可靠性高但效率比较低。
  • 异步复制:指客户端发送消息到Master,再由异步线程HAService异步同步到Slvae的过程,可以通过设置参数brokerRole = BrokerRole.ASYNC_MASTER来实现,效率非常高,但是可靠性比同步复制差。

Broker同步的数据有两种:配置数据和消息数据。配置数据主要包含Topic配置、消费者位点信息,延迟消息位点信息、订阅关系配置等。

主从同步流程

名词解释

服务名 功能
SlaveSynchronize Slave从Master同步配置数据的服务
HAService Slave从Master同步CommitLog数据
HAConnection Slave连接信息
HAConnection.WriteSocketService 将CommitLog写入网络,发送给Slave
HAConnection.ReadSocketService 读取Slave发送的offset请求
HAClient Slave处理与Master通信的客户端封装
GroupTransferService 主从同步通知类,实现同步、异步复制提供新数据通知服务
AcceptSocketService Master接受Slave发送的上报offset请求的服务

配置数据同步流程

配置数据的同步包含4中类型:Topic配置(TopicConfigManager)、消费者位点(ConsumerOffsetManager)、延迟位点(ScheduleMessageService)、订阅关系配置(SubscriptionGroupManager),它们都继承自ConfigManager抽象类。

当Slave Broker(brokerRole = BrokerRole.SLAVE)启动时,会初始化SlaveSynchronize方法,调用org.apache.rocketmq.broker.BrokerController#handleSlaveSynchronize方法,每10s调用一次org.apache.rocketmq.broker.slave.SlaveSynchronize.syncAll方法:

    private void handleSlaveSynchronize(BrokerRole role) {
   
        if (role == BrokerRole.SLAVE) {
   
            if (null != slaveSyncFuture) {
   
                // 取消正在运行的slave同步任务,不强制中断
                slaveSyncFuture.cancel(false);
            }
            this.slaveSynchronize.setMasterAddr(null);
            slaveSyncFuture = this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
   
                @Override
                public void run() {
   
                    try {
   
                        BrokerController.this.slaveSynchronize.syncAll();
                    }
                    catch (Throwable e) {
   
                        log.error("ScheduledTask SlaveSynchronize syncAll error.", e);
                    }
                }
            }, 1000 * 3, 1000 * 10, TimeUnit.MILLISECONDS);
        } else {
   
            //handle the slave synchronise
            if (null != slaveSyncFuture) {
   
                slaveSyncFuture.cancel(false);
            }
            this.slaveSynchronize.setMasterAddr(null);
        }
    }

syncAll方法一次调用四种配置数据(Topic配置、消费者位点、同步延迟位点、订阅关系配置)的同步方法同步全量数据:

    public void syncAll() {
   
        // 同步Topic配置
        this.syncTopicConfig();
        // 同步消费者位点
        this.syncConsumerOffset();
        // 同步延迟位点
        this.syncDelayOffset();
        // 同步订阅关系配置
        this.syncSubscriptionGroupConfig();
    }

syncAll方法执行的4个方法都是通过调用BrokerOuterAPI中的方法,根据RequestCode.GET_ALL_TOPIC_CONFIG、RequestCode.GET_ALL_CONSUMER_OFFSET、RequestCode.GET_ALL_DELAY_OFFSET、RequestCode.GET_ALL_SUBSCRIPTIONGROUP_CONFIG使用Remoting模块远程调用Master Broker,而Master Broker初始化过程中注册了org.apache.rocketmq.broker.processor.AdminBrokerProcessor处理器接受对应的请求,根据RequestCode的值进行不同方法的调用(四种配置数据信息获取的方法很简单,不在赘述)。

Slave根据上面说的流程获取到Master中的配置数据信息同步持久化到磁盘中。

CommitLog数据同步流程

CommitLog的数据同步分为同步复制和异步复制两种。同步复制是生产者生产消息后,等待Master Broker将数据同步到Slave Broker后,再返回生产者数据存储状态;异步复制是生产者在生产消息后,不用等待Slave同步,直接返回Master存储结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWDHoVxA-1649378441486)(C:\Users\liaozhirong\Desktop\learning\Images\webp.webp)]

异步复制

Master Broker启动时,会初始化和启动DefaultMessageStore存储服务,而DefaultMessageStore存储服务会初始化org.apache.rocketmq.store.ha.HAService服务,HAService服务会启动org.apache.rocketmq.store.ha.HAService。AcceptSocketService服务,监听10912端口,用于接收来自一个或多个Slave的注册请求,当有Slave注册请求进来时,会创建一个HAConnection,同时HAConnection会创建WriteSocketServiceReadSocketService服务并启动,开始主从数据同步。

Slave -> Master
ReadSocketService:Master接收Slave同步数据的请求,并将这些信息保存在HAConnection中。
    public void run() {
   
      HAConnection.log.info(this.getServiceName() + " service started");

      while (!this.isStopped()) {
   
        try {
   
          this.selector.select(1000);
          // 接收Slave的上报请求
          boolean ok = this.processReadEvent();
          if (!ok) {
   
            HAConnection.log.error("processReadEvent error");
            break;
          }

          long interval = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastReadTimestamp;
          // 判断slave心跳时间是否超过同步存活时间,表示Slave不可用(可能宕机或者网络不可用)
          // 终止获取Slave的上报请求循环,删除Slave的连接
          if (interval > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaHousekeepingInterval()) {
   
            log.warn("ha housekeeping, found this connection[" + HAConnection.this.clientAddr + "] expired, " + interval);
            break;
          }
        } catch (Exception e) {
   
          HAConnection.log.error(this.getServiceName() + " service has exception.", e);
          break;
        }
      }
      // 暂停该Slave的read、write服务,并移除该Slave连接
      this.makeStop();

      writeSocketService.makeStop();

      haService.removeConnection(HAConnection.this);

      HAConnection.this.haService.getConnectionCount().decrementAndGet();

      SelectionKey sk = this.socketChannel.keyFor(this.selector);
      if (sk != null) {
   
        sk.cancel();
      }

      try {
   
        this.selector.close();
        this.socketChannel.close();
      } catch (IOException e) {
   
        HAConnection.log.error("", e);
      }

      HAConnection.log.info(this.getServiceName() + " service end");
    }

    private boolean processReadEvent() {
   
      // 读取到0的次数
      int readSizeZeroTimes = 0;
      // 读取到最大子节,flip重置position为0,processPosition=0
      if (!this.byteBufferRead.hasRemaining()) {
   
        this.byteBufferRead.flip();
        this.processPosition = 0;
      }
      // byteBufferRead是否可以还可以写入数据
      while (this.byteBufferRead.hasRemaining()) {
   
        try {
   
          // 读取数据到缓存中
          int readSize = this.socketChannel.read(this.byteBufferRead);
          // readSize>0表示读取到子节数据
          if (readSize > 0) {
   
            // 设置读取到0的次数为0、最近读取数据的时间
            readSizeZeroTimes = 0;
            this.lastReadTimestamp = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now();
            // 读取的位点-已读位点 大于等于 8,表示读取到同步请求的偏移位点信息
            if ((this.byteBufferRead.position() - this.processPosition) >= 8) {
   
              // slave上报的最大偏移量占8个子节,正常情况下byteBufferRead的position是8的倍数,但是不能确定出现粘包情况的出现
              // 所以pos重新计算,规避该情况的出现,保证获取的位置是正确的
              // 获取Slave请求同步的最新偏移位点信息
              int pos = this.byteBufferRead.position() - (this.byteBufferRead.position() % 8);
              long readOffset = this.byteBufferRead.getLong(pos - 8);
              // 设置已读位点 = pos
              this.processPosition = pos;
              // 设置slave应答同步完成的偏移位点
              HAConnection.this.slaveAckOffset = readOffset;
              // 设置slave请求同步开始的偏移位点
              HAConnection.this.slaveRequestOffset = readOffset;
              if (HAConnection.this.slaveRequestOffset < 0) {
   
                log.info("slave[" + HAConnection.this.clientAddr + "] request offset " + readOffset);
              }
              // 比较更新push2SlaveMaxOffset,并唤醒GroupTransferService服务
              // GroupTransferService服务则比较push2SlaveMaxOffset判断是否完成同步复制返回future结果
              HAConnection.this.haService.notifyTransferSome(HAConnection.this.slaveAckOffset);
            }
            // 连续读取三次 0 则结束本次读取,返回true
          } else if (readSize == 0) {
   
            if (++readSizeZeroTimes >= 3) {
   
              break;
            }
            // 读取到负数,发生异常,关闭Slave连接
          } else {
   
            log.error("read socket[" + HAConnection.this.clientAddr + "] < 0");
            return false;
          }
        } catch (IOException e) {
   
          log.error("processReadEvent exception", e);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值