RocketMQ 源码分析 —— 高可用

探秘RocketMQ 5.0 client端Nameserver地址更新的源码实现方式 客户端每隔30s就会去扫描判断Nameserver是否存活客户端每隔2分钟才会去http服务器拉取最新的Nameserver地址客户端topic相关的缓存信息每隔30s会去Nameserver拉取最新的topic、broker相关信息作者:weihubeats。 阅读详情

点击上方“芋道源码”,选择“设为星标”

做积极的人,而不是积极废人!

源码精品专栏

 

摘要: 原创出处 http://www.iocoder.cn/RocketMQ/high-availability/ 「芋道源码」欢迎转载,保留摘要,谢谢!

  • 1. 概述

  • 2. Namesrv 高可用

    • 2.1 Broker 注册到 Namesrv

    • 2.2 Producer、Consumer 访问 Namesrv

  • 3. Broker 高可用

    • 3.2 Broker 主从

    • 3.2 Producer 发送消息

    • 3.3 Consumer 消费消息

  • 4. 总结


阅读源码最好的方式,是使用 IDEA 进行调试 RocketMQ 源码,不然会一脸懵逼。

胖友可以点击「芋道源码」扫码关注,回复 git001 关键字
获得艿艿添加了中文注释的 RocketMQ 源码地址。

阅读源码很孤单,加入源码交流群,一起坚持!

1. 概述

本文主要解析 Namesrv、Broker 如何实现高可用,Producer、Consumer 怎么与它们通信保证高可用。

2. Namesrv 高可用

启动多个 Namesrv 实现高可用。
相较于 Zookeeper、Consul、Etcd 等,Namesrv 是一个超轻量级的注册中心,提供命名服务。

2.1 Broker 注册到 Namesrv

  • ???? 多个 Namesrv 之间,没有任何关系(不存在类似 Zookeeper 的 Leader/Follower 等角色),不进行通信与数据同步。通过 Broker 循环注册多个 Namesrv。

  1: // ⬇️⬇️⬇️【BrokerOuterAPI.java】
  2: public RegisterBrokerResult registerBrokerAll(
  3:     final String clusterName,
  4:     final String brokerAddr,
  5:     final String brokerName,
  6:     final long brokerId,
  7:     final String haServerAddr,
  8:     final TopicConfigSerializeWrapper topicConfigWrapper,
  9:     final List<String> filterServerList,
 10:     final boolean oneway,
 11:     final int timeoutMills) {
 12:     RegisterBrokerResult registerBrokerResult = null;
 13: 
 14:     List<String> nameServerAddressList = this.remotingClient.getNameServerAddressList();
 15:     if (nameServerAddressList != null) {
 16:         for (String namesrvAddr : nameServerAddressList) { // 循环多个 Namesrv
 17:             try {
 18:                 RegisterBrokerResult result = this.registerBroker(namesrvAddr, clusterName, brokerAddr, brokerName, brokerId,
 19:                     haServerAddr, topicConfigWrapper, filterServerList, oneway, timeoutMills);
 20:                 if (result != null) {
 21:                     registerBrokerResult = result;
 22:                 }
 23: 
 24:                 log.info("register broker to name server {} OK", namesrvAddr);
 25:             } catch (Exception e) {
 26:                 log.warn("registerBroker Exception, {}", namesrvAddr, e);
 27:             }
 28:         }
 29:     }
 30: 
 31:     return registerBrokerResult;
 32: }

2.2 Producer、Consumer 访问 Namesrv

  • ???? Producer、Consumer 从 Namesrv列表选择一个可连接的进行通信。

  1: // ⬇️⬇️⬇️【NettyRemotingClient.java】
  2: private Channel getAndCreateNameserverChannel() throws InterruptedException {
  3:     // 返回已选择、可连接Namesrv
  4:     String addr = this.namesrvAddrChoosed.get();
  5:     if (addr != null) {
  6:         ChannelWrapper cw = this.channelTables.get(addr);
  7:         if (cw != null && cw.isOK()) {
  8:             return cw.getChannel();
  9:         }
 10:     }
 11:     //
 12:     final List<String> addrList = this.namesrvAddrList.get();
 13:     if (this.lockNamesrvChannel.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
 14:         try {
 15:             // 返回已选择、可连接的Namesrv
 16:             addr = this.namesrvAddrChoosed.get();
 17:             if (addr != null) {
 18:                 ChannelWrapper cw = this.channelTables.get(addr);
 19:                 if (cw != null && cw.isOK()) {
 20:                     return cw.getChannel();
 21:                 }
 22:             }
 23:             // 从【Namesrv列表】中选择一个连接的返回
 24:             if (addrList != null && !addrList.isEmpty()) {
 25:                 for (int i = 0; i < addrList.size(); i++) {
 26:                     int index = this.namesrvIndex.incrementAndGet();
 27:                     index = Math.abs(index);
 28:                     index = index % addrList.size();
 29:                     String newAddr = addrList.get(index);
 30: 
 31:                     this.namesrvAddrChoosed.set(newAddr);
 32:                     Channel channelNew = this.createChannel(newAddr);
 33:                     if (channelNew != null)
 34:                         return channelNew;
 35:                 }
 36:             }
 37:         } catch (Exception e) {
 38:             log.error("getAndCreateNameserverChannel: create name server channel exception", e);
 39:         } finally {
 40:             this.lockNamesrvChannel.unlock();
 41:         }
 42:     } else {
 43:         log.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
 44:     }
 45: 
 46:     return null;
 47: }

3. Broker 高可用

启动多个 Broker分组 形成 集群 实现高可用。
Broker分组 = Master节点x1 + Slave节点xN。
类似 MySQL,Master节点 提供读写服务,Slave节点 只提供读服务。

3.2 Broker 主从

  • 每个分组,Master节点 不断发送新的 CommitLog 给 Slave节点。Slave节点 不断上报本地的 CommitLog 已经同步到的位置给 Master节点。

  • Broker分组 与 Broker分组 之间没有任何关系,不进行通信与数据同步。

  • 消费进度 目前不支持 Master/Slave 同步。

    • org.apache.rocketmq.broker.slave.SlaveSynchronize 类,Slave 节点会从 Master 节点拉取消费进度、Topic 配置等等。

集群内,Master节点 有两种类型:Master_SYNC、Master_ASYNC:前者在 Producer 发送消息时,等待 Slave节点 存储完毕后再返回发送结果,而后者不需要等待。

3.1.1 配置

目前官方提供三套配置:

  • 2m-2s-async

brokerClusterNamebrokerNamebrokerRolebrokerId
DefaultClusterbroker-aASYNC_MASTER0
DefaultClusterbroker-aSLAVE1
DefaultClusterbroker-bASYNC_MASTER0
DefaultClusterbroker-bSLAVE1
  • 2m-2s-sync

brokerClusterNamebrokerNamebrokerRolebrokerId
DefaultClusterbroker-aSYNC_MASTER0
DefaultClusterbroker-aSLAVE1
DefaultClusterbroker-bSYNC_MASTER0
DefaultClusterbroker-bSLAVE1
  • 2m-noslave

brokerClusterNamebrokerNamebrokerRolebrokerId
DefaultClusterbroker-aASYNC_MASTER0
DefaultClusterbroker-bASYNC_MASTER0

3.1.2 组件

再看具体实现代码之前,我们来看看 Master/Slave节点 包含的组件:

  • Master节点

    • ReadSocketService :读来自 Slave节点 的数据。

    • WriteSocketService :写到往 Slave节点 的数据。

    • AcceptSocketService :接收 Slave节点 连接。

    • HAConnection

  • Slave节点

    • HAClient :对 Master节点 连接、读写数据。

3.1.3 通信协议

Master节点 与 Slave节点 通信协议很简单,只有如下两条。

对象用途第几位字段数据类型字节数说明
Slave=>Master上报CommitLog已经同步到的物理位置






0maxPhyOffsetLong8CommitLog最大物理位置
Master=>Slave传输新的 CommitLog 数据






0fromPhyOffsetLong8CommitLog开始物理位置


1sizeInt4传输CommitLog数据长度


2bodyBytessize传输CommitLog数据

3.1.4 Slave

HAClient顺序图

  • Slave 主循环,实现了不断不断不断从 Master 传输 CommitLog 数据,上传 Master 自己本地的 CommitLog 已经同步物理位置。

  1: // ⬇️⬇️⬇️【HAClient.java】
  2: public void run() {
  3:     log.info(this.getServiceName() + " service started");
  4: 
  5:     while (!this.isStopped()) {
  6:         try {
  7:             if (this.connectMaster()) {
  8:                 // 若到满足上报间隔,上报到Master进度
  9:                 if (this.isTimeToReportOffset()) {
 10:                     boolean result = this.reportSlaveMaxOffset(this.currentReportedOffset);
 11:                     if (!result) {
 12:                         this.closeMaster();
 13:                     }
 14:                 }
 15: 
 16:                 this.selector.select(1000);
 17: 
 18:                 // 处理读取事件
 19:                 boolean ok = this.processReadEvent();
 20:                 if (!ok) {
 21:                     this.closeMaster();
 22:                 }
 23: 
 24:                 // 若进度有变化,上报到Master进度
 25:                 if (!reportSlaveMaxOffsetPlus()) {
 26:                     continue;
 27:                 }
 28: 
 29:                 // Master过久未返回数据,关闭连接
 30:                 long interval = HAService.this.getDefaultMessageStore().getSystemClock().now() - this.lastWriteTimestamp;
 31:                 if (interval > HAService.this.getDefaultMessageStore().getMessageStoreConfig()
 32:                     .getHaHousekeepingInterval()) {
 33:                     log.warn("HAClient, housekeeping, found this connection[" + this.masterAddress
 34:                         + "] expired, " + interval);
 35:                     this.closeMaster();
 36:                     log.warn("HAClient, master not response some time, so close connection");
 37:                 }
 38:             } else {
 39:                 this.waitForRunning(1000 * 5);
 40:             }
 41:         } catch (Exception e) {
 42:             log.warn(this.getServiceName() + " service has exception. ", e);
 43:             this.waitForRunning(1000 * 5);
 44:         }
 45:     }
 46: 
 47:     log.info(this.getServiceName() + " service end");
 48: }
  • 第 8 至 14 行 :固定间隔(默认5s)向 Master 上报 Slave 本地 CommitLog 已经同步到的物理位置。该操作还有心跳的作用。

  • 第 16 至 22 行 :处理 Master 传输 Slave 的 CommitLog 数据。


  • 我们来看看 #dispatchReadRequest(...) 与 #reportSlaveMaxOffset(...) 是怎么实现的。

  1: // 【HAClient.java】
  2: /**
  3:  * 读取Master传输的CommitLog数据,并返回是异常
  4:  * 如果读取到数据,写入CommitLog
  5:  * 异常原因:
  6:  *   1. Master传输来的数据offset 不等于 Slave的CommitLog数据最大offset
  7:  *   2. 上报到Master进度失败
  8:  *
  9:  * @return 是否异常
 10:  */
 11: private boolean dispatchReadRequest() {
 12:     final int msgHeaderSize = 8 + 4; // phyoffset + size
 13:     int readSocketPos = this.byteBufferRead.position();
 14: 
 15:     while (true) {
 16:         // 读取到请求
 17:         int diff = this.byteBufferRead.position() - this.dispatchPostion;
 18:         if (diff >= msgHeaderSize) {
 19:             // 读取masterPhyOffset、bodySize。使用dispatchPostion的原因是:处理数据“粘包”导致数据读取不完整。
 20:             long masterPhyOffset = this.byteBufferRead.getLong(this.dispatchPostion);
 21:             int bodySize = this.byteBufferRead.getInt(this.dispatchPostion + 8);
 22:             // 校验 Master传输来的数据offset 是否和 Slave的CommitLog数据最大offset 是否相同。
 23:             long slavePhyOffset = HAService.this.defaultMessageStore.getMaxPhyOffset();
 24:             if (slavePhyOffset != 0) {
 25:                 if (slavePhyOffset != masterPhyOffset) {
 26:                     log.error("master pushed offset not equal the max phy offset in slave, SLAVE: "
 27:                         + slavePhyOffset + " MASTER: " + masterPhyOffset);
 28:                     return false;
 29:                 }
 30:             }
 31:             // 读取到消息
 32:             if (diff >= (msgHeaderSize + bodySize)) {
 33:                 // 写入CommitLog
 34:                 byte[] bodyData = new byte[bodySize];
 35:                 this.byteBufferRead.position(this.dispatchPostion + msgHeaderSize);
 36:                 this.byteBufferRead.get(bodyData);
 37:                 HAService.this.defaultMessageStore.appendToCommitLog(masterPhyOffset, bodyData);
 38:                 // 设置处理到的位置
 39:                 this.byteBufferRead.position(readSocketPos);
 40:                 this.dispatchPostion += msgHeaderSize + bodySize;
 41:                 // 上报到Master进度
 42:                 if (!reportSlaveMaxOffsetPlus()) {
 43:                     return false;
 44:                 }
 45:                 // 继续循环
 46:                 continue;
 47:             }
 48:         }
 49: 
 50:         // 空间写满,重新分配空间
 51:         if (!this.byteBufferRead.hasRemaining()) {
 52:             this.reallocateByteBuffer();
 53:         }
 54: 
 55:         break;
 56:     }
 57: 
 58:     return true;
 59: }
 60: 
 61: /**
 62:  * 上报进度
 63:  *
 64:  * @param maxOffset 进度
 65:  * @return 是否上报成功
 66:  */
 67: private boolean reportSlaveMaxOffset(final long maxOffset) {
 68:     this.reportOffset.position(0);
 69:     this.reportOffset.limit(8);
 70:     this.reportOffset.putLong(maxOffset);
 71:     this.reportOffset.position(0);
 72:     this.reportOffset.limit(8);
 73: 
 74:     for (int i = 0; i < 3 && this.reportOffset.hasRemaining(); i++) {
 75:         try {
 76:             this.socketChannel.write(this.reportOffset);
 77:         } catch (IOException e) {
 78:             log.error(this.getServiceName()
 79:                 + "reportSlaveMaxOffset this.socketChannel.write exception", e);
 80:             return false;
 81:         }
 82:     }
 83: 
 84:     return !this.reportOffset.hasRemaining();
 85: }

3.1.5 Master

  • ReadSocketService 逻辑同 HAClient#processReadEvent(...) 基本相同,我们直接看代码。

  1: // ⬇️⬇️⬇️【ReadSocketService.java】
  2: private boolean processReadEvent() {
  3:     int readSizeZeroTimes = 0;
  4: 
  5:     // 清空byteBufferRead
  6:     if (!this.byteBufferRead.hasRemaining()) {
  7:         this.byteBufferRead.flip();
  8:         this.processPostion = 0;
  9:     }
 10: 
 11:     while (this.byteBufferRead.hasRemaining()) {
 12:         try {
 13:             int readSize = this.socketChannel.read(this.byteBufferRead);
 14:             if (readSize > 0) {
 15:                 readSizeZeroTimes = 0;
 16: 
 17:                 // 设置最后读取时间
 18:                 this.lastReadTimestamp = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now();
 19: 
 20:                 if ((this.byteBufferRead.position() - this.processPostion) >= 8) {
 21:                     // 读取Slave 请求来的CommitLog的最大位置
 22:                     int pos = this.byteBufferRead.position() - (this.byteBufferRead.position() % 8);
 23:                     long readOffset = this.byteBufferRead.getLong(pos - 8);
 24:                     this.processPostion = pos;
 25: 
 26:                     // 设置Slave CommitLog的最大位置
 27:                     HAConnection.this.slaveAckOffset = readOffset;
 28: 
 29:                     // 设置Slave 第一次请求的位置
 30:                     if (HAConnection.this.slaveRequestOffset < 0) {
 31:                         HAConnection.this.slaveRequestOffset = readOffset;
 32:                         log.info("slave[" + HAConnection.this.clientAddr + "] request offset " + readOffset);
 33:                     }
 34: 
 35:                     // 通知目前Slave进度。主要用于Master节点为同步类型的。
 36:                     HAConnection.this.haService.notifyTransferSome(HAConnection.this.slaveAckOffset);
 37:                 }
 38:             } else if (readSize == 0) {
 39:                 if (++readSizeZeroTimes >= 3) {
 40:                     break;
 41:                 }
 42:             } else {
 43:                 log.error("read socket[" + HAConnection.this.clientAddr + "] < 0");
 44:                 return false;
 45:             }
 46:         } catch (IOException e) {
 47:             log.error("processReadEvent exception", e);
 48:             return false;
 49:         }
 50:     }
 51: 
 52:     return true;
 53: }

  • WriteSocketService 计算 Slave开始同步的位置后,不断向 Slave 传输新的 CommitLog数据。

HA.WriteSocketService流程图
  1: // ⬇️⬇️⬇️【WriteSocketService.java】
  2: @Override
  3: public void run() {
  4:     HAConnection.log.info(this.getServiceName() + " service started");
  5: 
  6:     while (!this.isStopped()) {
  7:         try {
  8:             this.selector.select(1000);
  9: 
 10:             // 未获得Slave读取进度请求,sleep等待。
 11:             if (-1 == HAConnection.this.slaveRequestOffset) {
 12:                 Thread.sleep(10);
 13:                 continue;
 14:             }
 15: 
 16:             // 计算初始化nextTransferFromWhere
 17:             if (-1 == this.nextTransferFromWhere) {
 18:                 if (0 == HAConnection.this.slaveRequestOffset) {
 19:                     long masterOffset = HAConnection.this.haService.getDefaultMessageStore().getCommitLog().getMaxOffset();
 20:                     masterOffset = masterOffset - (masterOffset % HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getMapedFileSizeCommitLog());
 21:                     if (masterOffset < 0) {
 22:                         masterOffset = 0;
 23:                     }
 24: 
 25:                     this.nextTransferFromWhere = masterOffset;
 26:                 } else {
 27:                     this.nextTransferFromWhere = HAConnection.this.slaveRequestOffset;
 28:                 }
 29: 
 30:                 log.info("master transfer data from " + this.nextTransferFromWhere + " to slave[" + HAConnection.this.clientAddr
 31:                     + "], and slave request " + HAConnection.this.slaveRequestOffset);
 32:             }
 33: 
 34:             if (this.lastWriteOver) {
 35:                 long interval = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now() - this.lastWriteTimestamp;
 36:                 if (interval > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaSendHeartbeatInterval()) { // 心跳
 37: 
 38:                     // Build Header
 39:                     this.byteBufferHeader.position(0);
 40:                     this.byteBufferHeader.limit(headerSize);
 41:                     this.byteBufferHeader.putLong(this.nextTransferFromWhere);
 42:                     this.byteBufferHeader.putInt(0);
 43:                     this.byteBufferHeader.flip();
 44: 
 45:                     this.lastWriteOver = this.transferData();
 46:                     if (!this.lastWriteOver)
 47:                         continue;
 48:                 }
 49:             } else { // 未传输完成,继续传输
 50:                 this.lastWriteOver = this.transferData();
 51:                 if (!this.lastWriteOver)
 52:                     continue;
 53:             }
 54: 
 55:             // 选择新的CommitLog数据进行传输
 56:             SelectMappedBufferResult selectResult =
 57:                 HAConnection.this.haService.getDefaultMessageStore().getCommitLogData(this.nextTransferFromWhere);
 58:             if (selectResult != null) {
 59:                 int size = selectResult.getSize();
 60:                 if (size > HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize()) {
 61:                     size = HAConnection.this.haService.getDefaultMessageStore().getMessageStoreConfig().getHaTransferBatchSize();
 62:                 }
 63: 
 64:                 long thisOffset = this.nextTransferFromWhere;
 65:                 this.nextTransferFromWhere += size;
 66: 
 67:                 selectResult.getByteBuffer().limit(size);
 68:                 this.selectMappedBufferResult = selectResult;
 69: 
 70:                 // Build Header
 71:                 this.byteBufferHeader.position(0);
 72:                 this.byteBufferHeader.limit(headerSize);
 73:                 this.byteBufferHeader.putLong(thisOffset);
 74:                 this.byteBufferHeader.putInt(size);
 75:                 this.byteBufferHeader.flip();
 76: 
 77:                 this.lastWriteOver = this.transferData();
 78:             } else { // 没新的消息,挂起等待
 79:                 HAConnection.this.haService.getWaitNotifyObject().allWaitForRunning(100);
 80:             }
 81:         } catch (Exception e) {
 82: 
 83:             HAConnection.log.error(this.getServiceName() + " service has exception.", e);
 84:             break;
 85:         }
 86:     }
 87: 
 88:     // 断开连接 & 暂停写线程 & 暂停读线程 & 释放CommitLog
 89:     if (this.selectMappedBufferResult != null) {
 90:         this.selectMappedBufferResult.release();
 91:     }
 92: 
 93:     this.makeStop();
 94: 
 95:     readSocketService.makeStop();
 96: 
 97:     haService.removeConnection(HAConnection.this);
 98: 
 99:     SelectionKey sk = this.socketChannel.keyFor(this.selector);
100:     if (sk != null) {
101:         sk.cancel();
102:     }
103: 
104:     try {
105:         this.selector.close();
106:         this.socketChannel.close();
107:     } catch (IOException e) {
108:         HAConnection.log.error("", e);
109:     }
110: 
111:     HAConnection.log.info(this.getServiceName() + " service end");
112: }
113: 
114: /**
115:  * 传输数据
116:  */
117: private boolean transferData() throws Exception {
118:     int writeSizeZeroTimes = 0;
119:     // Write Header
120:     while (this.byteBufferHeader.hasRemaining()) {
121:         int writeSize = this.socketChannel.write(this.byteBufferHeader);
122:         if (writeSize > 0) {
123:             writeSizeZeroTimes = 0;
124:             this.lastWriteTimestamp = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now();
125:         } else if (writeSize == 0) {
126:             if (++writeSizeZeroTimes >= 3) {
127:                 break;
128:             }
129:         } else {
130:             throw new Exception("ha master write header error < 0");
131:         }
132:     }
133: 
134:     if (null == this.selectMappedBufferResult) {
135:         return !this.byteBufferHeader.hasRemaining();
136:     }
137: 
138:     writeSizeZeroTimes = 0;
139: 
140:     // Write Body
141:     if (!this.byteBufferHeader.hasRemaining()) {
142:         while (this.selectMappedBufferResult.getByteBuffer().hasRemaining()) {
143:             int writeSize = this.socketChannel.write(this.selectMappedBufferResult.getByteBuffer());
144:             if (writeSize > 0) {
145:                 writeSizeZeroTimes = 0;
146:                 this.lastWriteTimestamp = HAConnection.this.haService.getDefaultMessageStore().getSystemClock().now();
147:             } else if (writeSize == 0) {
148:                 if (++writeSizeZeroTimes >= 3) {
149:                     break;
150:                 }
151:             } else {
152:                 throw new Exception("ha master write body error < 0");
153:             }
154:         }
155:     }
156: 
157:     boolean result = !this.byteBufferHeader.hasRemaining() && !this.selectMappedBufferResult.getByteBuffer().hasRemaining();
158: 
159:     if (!this.selectMappedBufferResult.getByteBuffer().hasRemaining()) {
160:         this.selectMappedBufferResult.release();
161:         this.selectMappedBufferResult = null;
162:     }
163: 
164:     return result;
165: }

3.1.6 Master_SYNC

  • Producer 发送消息时,Master_SYNC节点 会等待 Slave节点 存储完毕后再返回发送结果。

核心代码如下:

  1: // ⬇️⬇️⬇️【CommitLog.java】
  2: public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
  3:     // ....省略处理发送代码 
  4:     // Synchronous write double 如果是同步Master,同步到从节点
  5:     if (BrokerRole.SYNC_MASTER == this.defaultMessageStore.getMessageStoreConfig().getBrokerRole()) {
  6:         HAService service = this.defaultMessageStore.getHaService();
  7:         if (msg.isWaitStoreMsgOK()) {
  8:             // Determine whether to wait
  9:             if (service.isSlaveOK(result.getWroteOffset() + result.getWroteBytes())) {
 10:                 if (null == request) {
 11:                     request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
 12:                 }
 13:                 service.putRequest(request);
 14: 
 15:                 // 唤醒WriteSocketService
 16:                 service.getWaitNotifyObject().wakeupAll();
 17: 
 18:                 boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
 19:                 if (!flushOK) {
 20:                     log.error("do sync transfer other node, wait return, but failed, topic: " + msg.getTopic() + " tags: "
 21:                         + msg.getTags() + " client address: " + msg.getBornHostString());
 22:                     putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_SLAVE_TIMEOUT);
 23:                 }
 24:             }
 25:             // Slave problem
 26:             else {
 27:                 // Tell the producer, slave not available
 28:                 putMessageResult.setPutMessageStatus(PutMessageStatus.SLAVE_NOT_AVAILABLE);
 29:             }
 30:         }
 31:     }
 32: 
 33:     return putMessageResult;
 34: }
  • 第 16 行 :唤醒 WriteSocketService。

    • 唤醒后,WriteSocketService 挂起等待新消息结束,Master 传输 Slave 新的 CommitLog 数据。

    • Slave 收到数据后,立即上报最新的 CommitLog 同步进度到 Master。ReadSocketService 唤醒第 18 行:request#waitForFlush(...)。

我们来看下 GroupTransferService 的核心逻辑代码:

  1: // ⬇️⬇️⬇️【GroupTransferService.java】
  2: private void doWaitTransfer() {
  3:     synchronized (this.requestsRead) {
  4:         if (!this.requestsRead.isEmpty()) {
  5:             for (CommitLog.GroupCommitRequest req : this.requestsRead) {
  6:                 // 等待Slave上传进度
  7:                 boolean transferOK = HAService.this.push2SlaveMaxOffset.get() >= req.getNextOffset();
  8:                 for (int i = 0; !transferOK && i < 5; i++) {
  9:                     this.notifyTransferObject.waitForRunning(1000); // 唤醒
 10:                     transferOK = HAService.this.push2SlaveMaxOffset.get() >= req.getNextOffset();
 11:                 }
 12: 
 13:                 if (!transferOK) {
 14:                     log.warn("transfer messsage to slave timeout, " + req.getNextOffset());
 15:                 }
 16: 
 17:                 // 唤醒请求,并设置是否Slave同步成功
 18:                 req.wakeupCustomer(transferOK);
 19:             }
 20: 
 21:             this.requestsRead.clear();
 22:         }
 23:     }
 24: }

3.2 Producer 发送消息

  • Producer 发送消息时,会对 Broker集群 的所有队列进行选择。

核心代码如下:

  1: // ⬇️⬇️⬇️【DefaultMQProducerImpl.java】
  2: private SendResult sendDefaultImpl(//
  3:     Message msg, //
  4:     final CommunicationMode communicationMode, //
  5:     final SendCallback sendCallback, //
  6:     final long timeout//
  7: ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
  8:     // .... 省略:处理【校验逻辑】
  9:     // 获取 Topic路由信息
 10:     TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
 11:     if (topicPublishInfo != null && topicPublishInfo.ok()) {
 12:         MessageQueue mq = null; // 最后选择消息要发送到的队列
 13:         Exception exception = null;
 14:         SendResult sendResult = null; // 最后一次发送结果
 15:         int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1; // 同步多次调用
 16:         int times = 0; // 第几次发送
 17:         String[] brokersSent = new String[timesTotal]; // 存储每次发送消息选择的broker名
 18:         // 循环调用发送消息,直到成功
 19:         for (; times < timesTotal; times++) {
 20:             String lastBrokerName = null == mq ? null : mq.getBrokerName();
 21:             MessageQueue tmpmq = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName); // 选择消息要发送到的队列
 22:             if (tmpmq != null) {
 23:                 mq = tmpmq;
 24:                 brokersSent[times] = mq.getBrokerName();
 25:                 try {
 26:                     beginTimestampPrev = System.currentTimeMillis();
 27:                     // 调用发送消息核心方法
 28:                     sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout);
 29:                     endTimestamp = System.currentTimeMillis();
 30:                     // 更新Broker可用性信息
 31:                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
 32:                     // .... 省略:处理【发送返回结果】
 33:                     }
 34:                 } catch (e) { // .... 省略:处理【异常】
 35:                     
 36:                 }
 37:             } else {
 38:                 break;
 39:             }
 40:         }
 41:         // .... 省略:处理【发送返回结果】
 42:     }
 43:     // .... 省略:处理【找不到消息路由】
 44: }

如下是调试 #sendDefaultImpl(...) 时 TopicPublishInfo 的结果,Producer 获得到了 broker-a,broker-b 两个 Broker分组 的消息队列:

3.3 Consumer 消费消息

  • Consumer 消费消息时,会对 Broker集群 的所有队列进行选择。

4. 总结

HA总结.jpeg


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

RocketMQ5.0源码分析-高可用组件AutoSwitchHAClient图文详解 AutoSwitchHAClient作为RocketMQ5.0高可用的重要一个主键,其功能主要是Slave Broker和Master进行通讯同时处理来自Master传输的日志数据。搭配其他的组件使用来确保DLedger Controller模式下的自主切换和RocketMQ的高可用。后续会有文章接着分析其他DLedger Controller模式高可用的其他组件。 阅读详情

相关推荐

全国各省份影像下载地址(11级别)

cesium中使用urltemplate加载,leaflet中使用tile加载,具体加载有问题可以给我留言。

资深三维GIS开发 5158

芋道源码 yudao-cloud:构建全功能后台管理系统的技术探索

芋道,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。master分支为 JDK 8 + Spring Boot 2.7,分支为 JDK 17/21 + Spring Boot 3.2管理后台的电脑端:Vue3 提供 element-plus、vben(ant-design-vue) 两个版本,Vue2 提供 element-ui 版本管理后台的移动端:采用 uni-app 方案,一份代码多终端适配,同时支持 APP、小程序、H5!

chenchuang0128的博客 1万+

CANoe进阶实战(第十一章)-LIN节点测试调度

《CANoe快速入门实战:LIN节点调度开发》摘要 本文是CANoe系列教程的第十一章,重点讲解如何通过CAPL编程实现LIN报文的灵活调度控制。文章采用"输入-输出-过程"三步骤法,详细介绍了调度表函数linChangeSchedTable()、启动/停止函数linStartScheduler()/linStopDisturbance()等关键API的使用方法。作者创新性地通过系统变量在Test节点间接控制Network节点的调度功能,并提供了完整的节点代码实现方案。最后的Trace窗

weixin_53605885的博客 1025

RocketMQ源码分析之Broker注册(1)

在broker启动时会开始第一次注册,以后会定时向注册中心注册本机的信息 this.registerBrokerAll(true, false, true); this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {...

1315

C语言关键字static的绝妙用途

为什么要说static妙,它确实是妙,在软件开发或者单片机开发过程中,大家总以为static就是一个静态变量,在变量类型的前面加上就自动清0了,还有就是加上static关键字的,不管是变量还是关键字,在局部变量中,变量仅限于局部可见,在全局区中,static变量或函数仅限于本文件可见,当然还有一个重要的特性被忽略了。。。。       就是static修饰的局部变量 它的数值 是上一次函数调用结

Bruce.yang的嵌入式之旅 5378

【OpenClaw从入门到精通】第66篇:Skill开发进阶——从零打造一个跨境选品Skill(附完整代码)(2026实测版)

2026年跨境电商效率革命中,AI Agent已成为选品核心工具,但通用Skill难以适配个性化需求。本文以“跨境选品”为实战场景,从零讲解OpenClaw Skill的完整开发流程:从需求分析、目录结构设计、SKILL.md编写,到Python脚本开发(含1688商品搜索、销量分析、竞品对比核心功能),再到调试测试、安全审计与ClawHub发布。文中提供可直接复制的完整代码,覆盖多平台适配、异常处理、数据可视化等进阶功能,同时解决API调用、链接失效等实际问题。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 568

rocketmq-producer

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33885253的博客 190

Python 爬虫实战:爬取豆瓣电影 Top250 完整数据

本文基于Python技术实现了豆瓣电影Top250榜单数据的合规爬取与分析。针对豆瓣电影页面的分页结构、反爬机制等特点,采用requests库获取数据,结合BeautifulSoup解析HTML节点,并通过正则表达式提取电影名称、评分、导演等核心字段。爬虫实现了随机User-Agent、请求间隔控制等反爬规避策略,严格遵守豆瓣社区使用规范。最终数据经清洗后存储为结构化Excel文件,并提供了数据可视化与增量更新方案。该方案为影视研究提供了可靠的数据获取方法,同时确保符合非商业使用、适度爬取等合规要求。

2503_91057718的博客 3461

RocketMQ源码阅读-七-高可用

Rocket的高可用包括NameServer和Broker的高可用,以及Producer和Consumer与他们的通信。RocketMQ实现高可用的架构如上图。Broker通过一个Master和0-N个Slave实现一个分组多个Broker分组,行程一个集群,每个Broker分组之间互相没有联系,不进行通信pro。

王飞的博客 1003

国科大学习资料--高级软件工程-复习题概念解释答案

国科大学习资料–高级软件工程(罗铁坚 魏峻 刘丹)-复习题概念解释答案 1. 敏捷开发方法与Scrum方法 a.敏捷是一种强调轻量的过程方法论,它强调拥抱变化而不是与之对抗,通过有效的沟通发挥群体的智慧。敏捷方法采用迭代/增量开发的过程模型。敏捷宣言是:个体和交互高于流程和工具;工作的软件高于详尽的文档;客户合作高于合同谈判;响应变化高于遵循计划。 虽然右边的也有价值,但是认为左边的更有价值。 b.Scrum(Schwaber和Beedle)是管理两个披萨规模(4-9人)的软件开发团队的方法,它高频率的短会

无名何许人的博客 1444

Winform从入门到精通(7)——ComboBox(史上最全)

本文介绍ComboBox控件的详细用法,包括所有属性、事件,绝对史上最全!

qq_34059233的博客 2万+

RocketMQ源码分析——消息发送的高可用

消息发送的高可用 RocketMQ 是怎么做的

mbtlami 1280

跨平台商品模型大解析:亚马逊 vs 阿里 vs 拼多多

本文深入探讨了亚马逊、阿里巴巴和拼多多三大电商平台的商品模型设计,分析了它们在变体关系、商品抽象层级和SKU定义等方面的异同。亚马逊模型以物流和变体为中心,强调全球仓配体系下的标准化管理;阿里模型采用多视角的弹性结构,适用于多类目、多品牌、多卖家环境;拼多多模型则结合平台标准化与商家个性化,适合短期促销和快速上新场景。文章还提供了实践建议,帮助自研电商平台或商品中台的开发者选择合适的商品结构,并强调了商品模型在电商系统中的核心作用。通过对比分析,本文旨在为读者提供商品建模的启发和参考。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 13万+

rocketmq 如何保证高可用_RocketMQ源码分析之RocketMQ高可用HA机制

HA主从同步的核心类图如图所示:1、初始RocketMQ HAHAService:主从同步核心实现类。1)AtomicInteger connectionCount:Master维护的连接数。(Slave的个数)。2)List connectionList:具体连接信息。3)AcceptSocketService acceptSocketService:服务端接收连接线程实现类。4)Default...

weixin_32531655的博客 249

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

RocketMQ 源码分析 高可用

为什么80%的码农都做不了架构师?>>> ...

weixin_33964094的博客 108

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

RocketMQ 源码分析 12 过期清除 && 高可用 && 读写分离

由于RocketMQ操作CommitLog、ConsumeQueue文件,都是基于内存映射方法并在启动的时候,会加载commitlog、ConsumeQueue目录下的所有文件,为了避免内存与磁盘的浪费,不可能将消息永久存储在消息服务器上,所以需要一种机制来删除已过期的文件。 RocketMQ清除过期文件的方法是:如果非当前写文件在一定时间间隔内没有再次被更新,则认为是过期文件,可以被删除,Ro...

郭传志的博客 437

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

RocketMQ源码分析——消息发送的核心原理与高可用

文章目录一、DefaultMQProducer1.1 消息发送方式1.1.1 同步发送1.1.2 异步发送1.1.3 批量发送1.1.4 单向发送1.1.5 自定义负载策略1.2 核心属性二、Producer 启动2.1 创建`MQClientInstance`实例2.2 注册Producer 到MQClientInstance2.3 启动MQClientInstance三、消息发送消息对象 Message3.1 消息校验3.2 获取 topic 信息3.2.1 从 NameServer 拉取路由3.3

一颗小陨石的博客 2104

分布式消息队列 RocketMQ 源码分析 —— 高可用

摘要: 原创出处 http://www.iocoder.cn/RocketMQ/high-availability/ 「芋道源码」欢迎转载,保留摘要,谢谢! 本文主要基于 RocketMQ 4.0.x 正式版 1. 概述 2. Namesrv 高可用 2.1 Broker 注册到 Namesrv 2.2 Producer、Consumer 访问 Namesrv 3. Broker 高可...

芋艿V 391

分布式消息队列 RocketMQ 源码分析——高可用

本文主要基于 RocketMQ 4.0.x 正式版 1. 概述 2. Namesrv 高可用 2.1 Broker 注册到 Namesrv 2.2 Producer、Consumer 访问 Namesrv 3. Broker 高可用 3.2 Broker 主从 3.1.1 配置 3.1.2 组件 3.1.3 通信协议 3.1.

longdan3105的博客 862

# 消息中间件 RocketMQ 高级功能和源码分析(一)

消息中间件 RocketMQ 高级功能和源码分析(一) 一、 消息中间件 RocketMQ 高级功能和源码分析 二、 消息中间件 RocketMQ 高级功能:消息存储方式介绍 三、 RocketMQ 消息存储和发送性能保证 四、 RocketMQ 消息存储结构 五、 RocketMQ 刷盘机制 六、 RocketMQ 高可用性 和 主从复制 七、 RocketMQ 负载均衡-消息生产者和消息消费者。 八、 RocketMQ 消息重试

段子手168的博客 1664

RocketMQ源码分析之Broker概述与同步消息发送原理与高可用设计及思考

continue;continue;throw e;= null) {times,主要的核心步骤如下:代码@1:获取topic的路由信息。代码@2:根据topic负载均衡算法选择一个MessageQueue。代码@3:向 MessageQueue 发送消息。

2401_84009698的博客 1084

源码分析RocketMQ与运维实战

RocketMQ是笔者当前最突出的亮点,正是由于在优快云中连载RocketMQ,最终促成了《RocketMQ技术内幕》一书的出版,也凭借此专栏的高质量,最终成为优快云2020年年度博客之星TOP2。 RocketMQ专栏目前已经输出48篇文章,并且被阿里巴巴相中,做成两本电子书被收录在阿里巴巴藏金阁,是笔者运维千亿级消息流转的实际经验,电子书详情如下图所示: 获取方式:关注我,并私信回复 RMQPDF 以下是RocketMQ专栏地址。 1、RocketMQ源码分析之NameServer ..

中间件兴趣圈 1180

rocketmq原理_RocketMQ源码分析:Broker概述+同步消息发送原理与高可用设计及思考...

1、Broker概述Broker 在 RocketMQ 架构中的角色,就是存储消息,核心任务就是持久化消息,生产者发送消息给 Broker,消费者从 Broker 消费消息,其物理部署架构图如下:备注:以上摘录自官方 RocketMQ 设计文档。上述基本描述了消息中间件的架构设计,不仅限于 RocketMQ,不同消息中间件的最大区别之一在消息的存储上。2、Broker存储设计概要接下来从配置文件的...

weixin_39856265的博客 372
上一篇: 微信支付软件架构,这也太牛逼了!
下一篇: 芋道 Spring Boot 数据库版本管理入门
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值