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

Windows环境下RocketMQ的安装及配置 关于Windows环境下RocketMQ的安装及配置保姆级教程 阅读详情

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

本文主要基于 RocketMQ 4.0.x 正式版


������关注微信公众号:【芋道源码】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
4. 新的源码解析文章实时收到通知。每周更新一篇左右。

> 5. 认真的源码交流微信群。

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 filterServerList,
 10:     final boolean oneway,
 11:     final int timeoutMills) {
 12:     RegisterBrokerResult registerBrokerResult = null;
 13: 
 14:     List 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 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 同步。

集群内,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节点 包含的组件:
HA组件图.png

  • Master节点
    • AcceptSocketService :接收 Slave节点 连接。
    • HAConnection
      • ReadSocketService :读来自 Slave节点 的数据。
      • WriteSocketService :写到往 Slave节点 的数据。
  • 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分组 的消息队列:
Producer.TopicPublishInfo.调试.png

3.3 Consumer 消费消息

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

4. 总结

HA总结.jpeg

666. 彩蛋

知识星球


������关注微信公众号:【芋道源码】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
4. 新的源码解析文章实时收到通知。每周更新一篇左右。
5. 认真的源码交流微信群。

Docker部署RocketMQ+SpringBoot整合RocketMQ(详细教程) 本文讲解了 RockerMQ 相关分布式详细队列理念,还有 将 RocketMQ 进行Docker容器化部署相关内容,最后还实现了SpringBoot 整合 RockerMQ。感兴趣的小伙伴可以看看,别忘了三连哦!谢谢! 阅读详情

相关推荐

RocketMQ

RocketMQ能够有效解决系统解耦、流量削峰、异步处理等问题,提高系统的可扩展性和稳定性。

weixin_51040479的博客 2093

PHP7-Laravel对接RocketMQ方法

如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

weixin_29397907的博客 1608

【檀越剑指大厂--RocketMQ】RocketMQ高阶篇

消息队列是一种“先进先出”的数据结构其应用场景主要包含以下 3 个方面系统的耦合性越高,容错性就越低。以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGOkVKkV-1685157541998)(http://qinyingjie.top/blogImg/%E8%A7%A3%E8%80%A61.png)]使用消息

优快云站内信: https://i.youkuaiyun.com/#/msg/chat/qyj19920704 3万+

RocketMq安装与配置

因为rocketMQ默认的启动参数内存占用非常大,如果环境没有这么多内存就必需修改JAVA_OPT参数。查看运行日志:tail -f ~/logs/rocketmqlogs/namesrv.log。brokerIP1:配置broker所在服务器的ip地址,以便Name Server连接。如果需要停止 RocketMQ 的服务,在生产环境不建议直接用kill,应该使用以下命令。修改 runserver.sh 和 runbroker.sh (服务器内存大可不改)直接找最新版下载后解压。只打包下面一个就可以。

Jarvan的博客 621

智能车学习日记【二】————十字和斜入十字

智能车学习日记【二】————十字和斜入十字 文章目录 智能车学习日记【二】————十字和斜入十字 前言 一、普通十字补线 二、斜入十字 1.判断是否为斜入十字和斜入十字类型 2.找斜入十字拐点 总结 前言 经过了一个多月的学习,小白的我终于也是能较稳定调出了2.8m以上的四轮车,过程中也遇到了很多困难,其中就有十字补线和斜入十字补线。尤其是斜入十字也是困扰了我快一周,现在和大家分享一下我处理斜入十字的方法(我的方法其实还有很多漏洞需要改进,仅供大家参考理解一下,欢迎各位在评论区指点出可

bsefef的博客 2743

Vivado 2018.3实战:用IDELAYE3和ISERDESE3搞定高速ADC数据采集(附仿真源码)

本文详细介绍了在Vivado 2018.3中使用Xilinx UltraScale架构的IDELAYE3和ISERDESE3原语实现高速ADC数据采集的实战方法。通过分析5GHz采样系统的时序挑战,提供从PCB布局到Vivado实现的完整解决方案,包括精密时序校准、多相位数据重组及系统级调试技巧,并附赠仿真源码助力工程师快速掌握高速数据采集技术。

weixin_30567225的博客 584

分布式消息队列 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

告别轮询!用STM32CubeMX给STM32F030配置ADC多通道+DMA,实测代码分享

本文详细介绍了如何使用STM32CubeMX为STM32F030配置ADC多通道采集与DMA传输,显著提升采集效率。通过实测对比轮询与DMA模式的性能差异,提供完整的代码实现和优化技巧,帮助开发者快速掌握这一高效采集方案。

weixin_42570296的博客 411

RocketMQ 实战与原理解析

内容简介 本书由云栖社区官方出品。 本书作者是阿里资深数据专家,对 RocketMQ 有深入的研究,并有大量的实践经验。在写这本书之前,作者不仅系统、深入地阅读了 RocketMQ 的源代码,而且还向 RocketMQ 的官方开发团队深入了解了它的诸多设计细节。作者结合自己多年使用 RocketMQ 的经验,从开发和运维两个维度,给出了大部分场景下的优秀实践,能帮助读者在学会使用和用好 Rocke...

GitChat 3895

达梦数据库(DM)的备份与恢复功能

本文系统介绍了达梦数据库(DM)的备份与恢复功能,主要包括作业系统和物理/逻辑备份还原两大模块。作业系统部分详细说明了通过代理环境创建定时备份和删除备份作业的步骤;物理备份还原部分阐述了联机和脱机备份方式,包括数据库、表空间、表和归档的备份还原操作;逻辑备份还原部分介绍了dexp/dimp工具的使用方法及四种级别的导入导出功能。文章还介绍了图形化管理工具进行备份还原的操作流程,全面覆盖了达梦数据库的数据保护方案,为数据库管理员提供了详细的技术指导。

weixin_48812832的博客 1214

嵌入式PCB电源完整性分析:PDN建模与电流密度优化

电源完整性(PI)是高速数字与混合信号系统稳定运行的基础,其核心在于保障芯片供电网络(PDN)在动态负载下的电压稳定与热安全。PDN分析本质上是通过量化电流密度与电压降两个关键物理量,揭示PCB走线、过孔及平面的载流瓶颈与压降路径。电流密度超标将引发局部温升与可靠性风险,而电压降则直接侵蚀器件供电裕量,导致复位、采样失真等典型故障。借助Altium Designer等工具开展工程级PDN建模,需严格依据电源预算表定义源/负载,并精准配置铜厚、过孔参数及IPC-2152限值,从而实现从原理图到制造工艺的闭环验

weixin_28949937的博客 212

堪称零瑕疵!仅用了330页直接封神,我要吹爆这份RocketMQ笔记

RocketMQ天生为金融互联网领域而生,追求高可靠、高可用、高并发、低延迟 RocketMQ在阿里集团也被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景 其主要功能有: 灵活可扩展性、 海量消息堆积能力、 能够保证严格的消息顺序 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅机制 亿级消息堆积能力 RocketMQ 核心的四大组件:Name Server、Broker、Producer、Consumer ,每个组件都可以部署成集群模式进行水平扩展

weixin_47083537的博客 1355

手把手教你学simulink(40.1)--水力发电场景示例:基于 Simulink 的抽水蓄能电站水力发电

项目背景抽水蓄能电站是一种重要的储能技术,广泛应用于电力系统的调峰填谷、频率调节和备用电源。它通过在电力负荷低谷时将水从下水库抽到上水库储存,在电力负荷高峰时释放水进行发电,从而实现电能的高效利用。与传统的水电站不同,抽水蓄能电站可以在短时间内快速响应电网的需求,具有良好的灵活性和可靠性。本项目旨在使用 MATLAB/Simulink 模拟一个完整的抽水蓄能电站的水力发电过程,包括水泵-涡轮机系统、发电机、水道系统、控制系统等,并实现对电站的精确控制,确保其在不同工况下都能稳定运行。

MHD0815的博客 886

还不会RocketMQ?吃透这份RocketMQ笔记,原地精通

消息队列 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,是阿里巴巴双 11 使用的核心产品。小编为了大家能在简历上写熟练RocketMQ,这份RocketMQ核心笔记,今天就拿出来分享给大家。

m0_73311735的博客 206

010、Zephyr RTOS开发环境搭建(Docker)

摘要 本文分享了使用Docker搭建Zephyr RTOS开发环境的实践经验。作者通过团队新人配置环境的真实案例,指出传统安装方式存在版本冲突、缓存污染等问题,而Docker方案能确保环境一致性。文章详细介绍了自定义Docker镜像的设计思路,重点解决工具链版本匹配、Python虚拟环境隔离等问题,将镜像体积从2GB优化到800MB。提供了从拉取镜像到点亮LED的完整工作流程,并总结了USB设备直通、文件权限、缓存问题等常见陷阱。还探讨了多阶段构建、CI集成等进阶用法,同时客观分析了Docker方案的局限性

m0_50546716的博客 81

Cadence OrCAD Capture软件启动过程卡死且无法恢复的解决方法

如下图所示Cadence OrCAD Capture软件在启动后出现卡死的现象,且等待后无法自动恢复,需要强行关闭,再次启动依然卡死。

fydar的博客 4045

RocketMQ实战与原理解析

网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击“普通下载”) 购买正版 封页 编辑推荐 (1)RocketMQ由阿里开源,Apache*开源项目,经受多年“双十一”流量峰值考验,在多个性能指标上远超同类产品 (2)作者是阿里资深数据专家,有多年RocketMQ使用经验,...

CiCi岛的专栏 2083

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

二面被RocketMQ虐后,狂刷这套实战到源码手册,再战阿里

RocketMQ 业界主流的消息中间件之一,承载公司核心业务消息的流转。对 RocketMQ 核心原理的理解与最佳实践成了Java开发者的必备技能。 现在对于开发几年的Java工程师在面试大厂时,总会被问到中间件这项技能!然而在学习中苦于缺少参考资料、遇到问题缺少专家指点、看源码掉入细节耗费大量时间精力、缺乏指引的最佳实践。 俗话说:“授人以鱼不如授人以渔”。在这分享这份RocketMQ实战进阶到源码分析手册给有需要的小伙伴,从原理到代码最佳实践全方位指引,知识原理讲解采用图文并茂的..

weixin_50666791的博客 139

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

吃不透RocketMQ?这份神仙RM笔记,让我佩服的五体投地。拿走不谢

消息队列 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,是阿里巴巴双 11 使用的核心产品。小编为了大家能在简历上写熟练RocketMQ,这份RocketMQ核心笔记,今天就拿出来分享给大家,有需要这份RocketMQ核心笔记的朋友,文末即可免费获取。

java_beautiful的博客 193

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

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

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

段子手168的博客 1664

面试基础---分布式架构基础消息队列Kafka vs RabbitMQ vs RocketMQ 对比

Kafka、RabbitMQ 和 RocketMQ 在分布式系统中扮演不同角色,理解其设计哲学与核心机制是技术选型的关键。Kafka 通过分区与顺序 I/O 实现高吞吐;RabbitMQ 通过灵活的 Exchange 路由满足复杂业务逻辑;RocketMQ 通过事务消息与主从同步保障金融级可靠性。在实际项目中,建议结合业务需求(如吞吐量、延迟、事务)与团队技术栈进行选择,必要时可混合使用多种消息队列(如 Kafka 处理日志 + RocketMQ 处理交易)。参考文献Kafka 官方文档。

weixin_43290370的博客 1916

RocketMQ 源码分析 —— 高可用

点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏原创 | Java 2020 超神之路,很肝~中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络...

芋艿V 218

Rocketmq之消息队列分配策略算法实现的源码分析

Rocketmq之消息队列分配策略算法实现的源码分析 本文中包含下面的内容 平均分配策略(默认)(AllocateMessageQueueAveragely) 环形分配策略(AllocateMessageQueueAveragelyByCircle) 手动配置分配策略(AllocateMessageQueueByConfig) 机房分配策略(AllocateMessageQueueByM...

yamaxifeng_132的专栏 719

分布式之消息队列

1、为什么要使用消息队列? 分析:一个用消息队列的人,不知道为啥用,这就有点尴尬。没有复习这点,很容易被问蒙,然后就开始胡扯了。 如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。 回答:这个问题,咱只答三...

javaQQ561487941的博客 257

分布式中间件实践之路

课程介绍 在分布式系统中,中间件是非常重要的组成,具有较高的技术门槛。同时,中间件相关的开源软件众多,适用场景各异,学习不易,对于初学者,本达人课无疑是值得一看的参考资料。 本课程是作者从事中间件研发的经验总结,来自实践,服务于实践,课程主要包括分布式缓存、分布式锁、分布式消息队列三大部分内容,涉及 Redis、Etcd、Kafka、RocketMQ 等众多主流开源软件的使用方案。 课程注重理论与...

GitChat 1万+

分布式消息队列 RocketMQ 源码分析——Message 顺序发送与消费

  RocketMQ 提供了两种顺序级别:   普通顺序消息 :Producer 将相关联的消息发送到相同的消息队列。完全严格顺序 :在 普通顺序消息 的基础上,Consumer 严格顺序消费。   绝大部分场景下只需要用到普通顺序消息。   例如说:给用户发送短信消息 + 发送推送消息,将两条消息发送到不同的消息队列,若其中一条消息队列消费较慢造成堵塞,用户可能会收到两条消息会存在一定的时间差,带来的体验会相对较差。当然类似这种场景,即使有一定的时间差,不会产生系统逻辑上BUG。另外,普通顺...

zouduchen的博客 3780
上一篇: 一起来学 SpringBoot 2.x | 第二篇:SpringBoot 配置详解
下一篇: 精尽 Dubbo 原理与源码专栏( 已经完成 69+ 篇,预计总共 75+ 篇 )
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值