RocketMQ 的架构可以用四个词来概括它们各司其职配合默契NameServer轻量级的“服务注册中心”和“路由表”负责管理所有 Broker 的信息Broker真正干活的“消息存储服务器”负责消息的写入、读取和持久化Producer消息的生产者负责发送消息Consumer消息的消费者负责拉取并处理消息用人话打个比方NameServer 是“通讯录”Broker 是“仓库”Producer 是“发货方”Consumer 是“收货方”。各组件职责与协作关系它们之间是怎么协作的呢单纯看文字很难形成立体感下面这张全链路架构协作图能帮你一眼看清从消息发送到消费的完整数据流向存储层 - Broker 主从集群注册中心层 - 无状态集群客户端层Broker 副本组 A① 获取路由信息② 发送消息③ 获取路由信息④ 拉取/消费消息⑤ 定时心跳注册⑤ 定时心跳注册⑤ 定时心跳注册⑥ 数据同步复制/异步复制Broker 副本组 B⑥ 数据同步Master BSlave BProducer 生产者Consumer 消费者NameServer 1NameServer 2NameServer 3Master A写入/读取Slave A数据备份/读取整个协作流程大致是这样的第一步Broker 启动后主动向所有 NameServer 节点注册自己的信息IP、端口、存储的 Topic 信息等并定时发送心跳。第二步Producer 发送消息前先从 NameServer 拉取路由信息知道消息应该发到哪个 Broker 去。第三步Producer 根据路由信息把消息发到对应的 Broker 上。第四步Consumer 消费消息前同样先从 NameServer 获取路由信息知道去哪个 Broker 拉取消息。第五步Consumer 主动从 Broker 拉取消息进行消费底层是 Pull 模式Push 模式是在此基础上封装的。第六步Broker Master 接收消息写入Broker Slave 从 Master 同步数据实现高可用。整个流程环环相扣但核心思想并不复杂NameServer 负责“指路”Broker 负责“收发货”客户端跟着指引走。NameServer 与 Broker 的关联方式NameServer 是整个 RocketMQ 集群的“大脑”。虽然它不存储消息但它知道所有 Broker 的信息。Broker 启动的时候会在配置文件中指定 NameServer 的地址列表。启动后Broker 会向所有 NameServer 发送注册请求把自己“登记”上去。之后每隔 30 秒Broker 会发送一次心跳告诉 NameServer“我还活着”。Producer 和 Consumer 在启动时同样需要配置 NameServer 地址。它们会从 NameServer 拉取 Topic 对应的 Broker 路由信息并缓存到本地。NameServer 的搭建与 Broker 的搭建在搭建 RocketMQ 集群时通常的步骤是NameServer 搭建下载 RocketMQ 二进制包后NameServer 的启动脚本在 bin/mqnamesrv启动命令nohup sh bin/mqnamesrv NameServer 本身非常轻量不保存任何持久化数据所有路由信息都在内存中Broker 搭建Broker 的启动脚本是 bin/mqbroker启动时需要指定配置文件sh bin/mqbroker -c conf/broker.conf配置文件中要指定 namesrvAddrNameServer 地址列表和 brokerClusterName集群名称 小贴士RocketMQ 5.x 推荐使用 Proxy Broker 存算分离架构部署其中 Proxy 是无状态的可以独立水平扩展。不过对于理解核心原理我们还是先从经典的 NameServerBroker 架构入手。NameServer 的多节点部署与无状态设计NameServer 最巧妙的设计在于它的无状态性。所谓“无状态”指的是 NameServer 自身不持久化任何数据所有的路由信息都是通过 Broker 的心跳上报动态构建的。这就意味着任意一个 NameServer 挂掉其他节点照样能提供服务NameServer 之间不需要互相通信不需要选主不需要数据同步水平扩展极其简单——多启动几个实例就行正因为 NameServer 节点之间完全对等、互不依赖我们才能轻松部署 3 个甚至更多节点来保证高可用。客户端会连接所有 NameServer 节点如果某个节点连不上自动切换到下一个。Broker 的 Master-Slave 主从架构和 NameServer 不同Broker 是有“身份”的。在 RocketMQ 4.x 及之前的经典部署模式中Broker 分为两种角色Master主节点负责接收 Producer 的消息写入也负责消息的读取。Master 可以配置多个每个 Master 都有自己的 Slave。Slave从节点不能接收写入只能从 Master 同步数据并承担一部分读请求可以配置是否开启 Slave 读权限。Master 和 Slave 之间的数据同步方式有两种同步方式 特点 适用场景同步复制 Master 收到消息后等 Slave 也写入成功才返回 ACK 对数据可靠性要求极高允许牺牲一点吞吐量异步复制 Master 写入成功就返回 ACKSlave 异步同步 追求高吞吐能容忍极少量数据丢失这里说的“同步复制”指的是消息数据本身在主从间的同步策略。它是 RocketMQ 保证消息零丢失的重要手段——如果你的业务连磁盘损坏这种极端情况都不能丢消息就必须配置同步复制。Broker 集群的多种部署模式RocketMQ 非常灵活Broker 集群支持多种部署方式你可以根据业务需求来选择。下面这张图清晰地展示了从简单到复杂的四种部署模式演进模式四DLedger 多主多从LeaderFollowerFollower优点自动故障切换Raft 协议保障模式三多主多从Master ASlave AMaster BSlave B优点高可用缺点需人工切主模式二多主无从Master 1无备份Master 2无备份缺点单主宕机数据不可达模式一单主单从MasterSlave缺点存在单点故障单主模式单 Master只有一个 Broker没有 Slave。部署最简单但存在单点故障。适用开发测试环境。2. 多主模式多 Master无 Slave多个 Master 节点每个 Master 存储不同的 Topic 分区数据。吞吐量高但某个 Master 宕机后该节点上的消息在恢复前无法消费。适用对可用性要求不高、允许少量数据暂时不可达的业务。3. 多主多从模式多 Master每个 Master 带 Slave生产环境的标准选择。每个 Master 至少有一个 Slave 作为备份高可用。缺点Master 宕机后虽然 Slave 可读但写服务会停且需要人工介入切换。4. 多主多从 Dledger 模式自动故障切换适用核心交易链路、金融级业务。Dledger 高可用架构与自动故障切换在传统的多主多从模式下如果 Master 挂了虽然 Slave 可以继续提供读服务但写服务就停了而且需要人工介入把 Slave 切换为 Master。Dledger 的出现解决了这个问题。Dledger 是 RocketMQ 基于 Raft 共识算法实现的高可用解决方案。当 Leader即 Master挂掉时集群如何秒级完成自动切换下图展示了它的内部选举流程客户端/监控Follower BLeader (Master)Follower A客户端/监控Follower BLeader (Master)Follower ALeader 正常运行处理读写请求 Leader 宕机进入选举超时计时 (Election Timeout)成为新 Leader同步数据同步数据连接中断发起投票请求 (RequestVote)投票响应 (Vote Granted)获得多数派投票 (1/2)恢复服务秒级切换无需人工干预在 Dledger 模式下一组 Broker 节点通常为 3 个组成一个副本组。当 Leader 挂了之后剩余的 Follower 会自动重新选举出新的 Leader整个过程无需人工干预秒级完成切换。NameServer 与 Broker 的启动顺序与关联流程部署时启动顺序很重要正确的启动顺序先启动 NameServer所有节点再启动 Broker所有节点如果 Broker 启动时 NameServer 还没启动Broker 会不断重试连接直到连上为止。NameServer 的心跳检测机制与 Broker 故障剔除NameServer 是怎么知道一个 Broker 是“活着”还是“死了”的答案就是心跳检测机制。这套机制非常精巧完全是 Broker 主动上报、NameServer 被动超时剔除的模式极大地降低了 NameServer 的压力。我们通过下面这张时序图来看清全流程Producer/ConsumerNameServer 集群Broker 节点Producer/ConsumerNameServer 集群Broker 节点记录心跳时间loop[每 30 秒 (心跳上报)]alt[当前时间 - 最后存活时间120 秒]loop[每 10 秒 (故障扫描)]alt[路由表发生变更 (Broker 被剔除)]loop[每 30 秒 (客户端拉取)]发送心跳包 (包含存活状态、路由信息)更新 Broker 的最后存活时间戳扫描 Broker 存活列表判定 Broker 失联从路由表剔除更新内存中的路由表拉取最新的 Topic 路由信息返回最新的 Broker 列表 (已剔除故障节点)更新本地缓存不再往故障 Broker 发消息Broker 主动上报Broker 每隔 30 秒向所有 NameServer 发送一次心跳心跳里包含了 Broker 的当前状态、存储容量、Topic 信息等NameServer 主动超时剔除NameServer 会定期扫描自己维护的 Broker 列表。如果一个 Broker 连续 120 秒默认 没有发送心跳NameServer 就会认为它“失联”了将其从路由表中移除客户端感知变化Producer 和 Consumer 每隔 30 秒会从 NameServer 拉取最新的路由信息。一旦某个 Broker 被剔除客户端在下次拉取时就能感知到从而不再往“死”掉的 Broker 发送消息四、Topic 与消息模型搞清楚了架构层面的组件协作现在我们向下深挖一层来到 RocketMQ 中最核心的数据模型——Topic。Topic 与 MessageQueue 的关系在 RocketMQ 中Topic 是一个逻辑概念MessageQueue 是物理概念。打个比方Topic 就像是一个“主题文件夹”而 MessageQueue 是这个文件夹下面的“物理文件”。Topic生产者发送消息时指定 Topic消费者订阅时指定 Topic。它是消息的分类标签。MessageQueueTopic 下面实际存储消息的最小物理单元。一条消息最终会被写到某个具体的 MessageQueue 里。一个 Topic 下面有多个 MessageQueue默认是 4 个。生产者发消息时会根据负载均衡策略比如轮询、哈希取模选择其中一个 Queue 来发送。Topic 与 Broker 的关系多对多映射Topic 和 Broker 之间的关系是多对多的一个 Topic 可以分布在多个 Broker 上每个 Broker 上存放该 Topic 的部分 Queue一个 Broker 上也可以存放多个 Topic 的 Queue这种多对多的映射关系让 RocketMQ 具备了水平扩展的能力。举个例子如果你的 Topic 流量太大一个 Broker 扛不住你可以在更多 Broker 上创建这个 Topic 的 Queue把流量分散开。物理节点Broker Master 2物理节点Broker Master 1逻辑概念Topic: order_topic映射映射映射映射Queue 0Queue 1Queue 2Queue 3Queue 0Queue 1Queue 0Queue 1如图order_topic 的 4 个 Queue 分布在 2 个 Broker 上每个 Broker 各存 2 个 Queue。这种设计不仅实现了存储的负载均衡也为后续的高可用打下了基础。MessageQueue 的物理存储结构这是 RocketMQ 最精妙的设计之一也是它为什么能具备极高写入吞吐量的核心机密。RocketMQ 采用了 CommitLog ConsumeQueue 的两层存储结构。光说概念可能有点抽象我们来看下面这张物理存储结构图它能帮你直观地理解“顺序写入”和“索引读取”是如何共存的消费流程快速定位异步索引构建写入流程极高吞吐量顺序追加写入异步构建索引异步构建索引异步构建索引根据逻辑偏移量查询返回物理偏移量 offset1024根据物理偏移量精准读取ProducerCommitLog单一物理文件所有 Topic 共用所有消息无论属于哪个 Topic都按到达顺序依次追加到 CommitLogConsumeQueue: order_topic-0存储偏移量 大小 Tag 哈希ConsumeQueue: order_topic-1ConsumeQueue: user_topic-0ConsumerCommitLog仓库所有消息顺序写入同一个大文件每个 Broker 只有一个 CommitLog。无论消息属于哪个 Topic都顺序追加到 CommitLog 里——这种顺序写入的方式让 RocketMQ 拥有了极高的写入吞吐量因为磁盘顺序写远快于随机写。ConsumeQueue货架标签每个 MessageQueue 对应一个 ConsumeQueue 文件它相当于一个“索引”里面记录了每条消息在 CommitLog 中的物理偏移量、大小、Tag 哈希值等信息。Consumer 消费时先查 ConsumeQueue 找到消息在 CommitLog 里的位置再去 CommitLog 读取消息内容。为什么一个 Topic 需要多个队列这可能是很多初学者最困惑的问题为什么一个 Topic 下面要有多个 Queue核心原因有三个