RocketMQ 的架构可以用四个词来概括,它们各司其职,配合默契:

NameServer:轻量级的“服务注册中心”和“路由表”,负责管理所有 Broker 的信息
Broker:真正干活的“消息存储服务器”,负责消息的写入、读取和持久化
Producer:消息的生产者,负责发送消息
Consumer:消息的消费者,负责拉取并处理消息
用人话打个比方:NameServer 是“通讯录”,Broker 是“仓库”,Producer 是“发货方”,Consumer 是“收货方”。

各组件职责与协作关系
它们之间是怎么协作的呢?单纯看文字很难形成立体感,下面这张全链路架构协作图能帮你一眼看清从消息发送到消费的完整数据流向:

存储层 - Broker 主从集群

注册中心层 - 无状态集群

客户端层

Broker 副本组 A

① 获取路由信息

② 发送消息

③ 获取路由信息

④ 拉取/消费消息

⑤ 定时心跳注册

⑤ 定时心跳注册

⑤ 定时心跳注册

⑥ 数据同步复制/异步复制

Broker 副本组 B

⑥ 数据同步

Master B

Slave B

Producer 生产者

Consumer 消费者

NameServer 1

NameServer 2

NameServer 3

Master 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
配置文件中要指定 namesrvAddr(NameServer 地址列表)和 brokerClusterName(集群名称)
💡 小贴士:RocketMQ 5.x 推荐使用 Proxy + Broker 存算分离架构部署,其中 Proxy 是无状态的,可以独立水平扩展。不过对于理解核心原理,我们还是先从经典的 NameServer+Broker 架构入手。

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 写入成功就返回 ACK,Slave 异步同步 追求高吞吐,能容忍极少量数据丢失
这里说的“同步复制”,指的是消息数据本身在主从间的同步策略。它是 RocketMQ 保证消息零丢失的重要手段——如果你的业务连磁盘损坏这种极端情况都不能丢消息,就必须配置同步复制。

Broker 集群的多种部署模式
RocketMQ 非常灵活,Broker 集群支持多种部署方式,你可以根据业务需求来选择。下面这张图清晰地展示了从简单到复杂的四种部署模式演进:

模式四:DLedger 多主多从

Leader

Follower

Follower

优点:自动故障切换
Raft 协议保障

模式三:多主多从

Master A

Slave A

Master B

Slave B

优点:高可用
缺点:需人工切主

模式二:多主无从

Master 1

无备份

Master 2

无备份

缺点:单主宕机数据不可达

模式一:单主单从

Master

Slave

缺点:存在单点故障

  1. 单主模式(单 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 B
Leader (Master)
Follower A
客户端/监控
Follower B
Leader (Master)
Follower A
Leader 正常运行
处理读写请求
💥 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/Consumer
NameServer 集群
Broker 节点
Producer/Consumer
NameServer 集群
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。它是消息的分类标签。
MessageQueue:Topic 下面实际存储消息的最小物理单元。一条消息最终会被写到某个具体的 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 0

Queue 1

Queue 2

Queue 3

Queue 0

Queue 1

Queue 0

Queue 1

如图,order_topic 的 4 个 Queue 分布在 2 个 Broker 上,每个 Broker 各存 2 个 Queue。这种设计不仅实现了存储的负载均衡,也为后续的高可用打下了基础。

MessageQueue 的物理存储结构
这是 RocketMQ 最精妙的设计之一,也是它为什么能具备极高写入吞吐量的核心机密。RocketMQ 采用了 CommitLog + ConsumeQueue 的两层存储结构。

光说概念可能有点抽象,我们来看下面这张物理存储结构图,它能帮你直观地理解“顺序写入”和“索引读取”是如何共存的:

消费流程(快速定位)

异步索引构建

写入流程(极高吞吐量)

  1. 顺序追加写入

  2. 异步构建索引

  3. 异步构建索引

  4. 异步构建索引

  5. 根据逻辑偏移量查询

  6. 返回物理偏移量 offset=1024

  7. 根据物理偏移量精准读取

Producer

CommitLog
(单一物理文件,所有 Topic 共用)

所有消息无论属于哪个 Topic
都按到达顺序依次追加到 CommitLog

ConsumeQueue: order_topic-0
(存储偏移量 + 大小 + Tag 哈希)

ConsumeQueue: order_topic-1

ConsumeQueue: user_topic-0

Consumer

CommitLog(仓库):所有消息顺序写入同一个大文件(每个 Broker 只有一个 CommitLog)。无论消息属于哪个 Topic,都顺序追加到 CommitLog 里——这种顺序写入的方式让 RocketMQ 拥有了极高的写入吞吐量(因为磁盘顺序写远快于随机写)。
ConsumeQueue(货架标签):每个 MessageQueue 对应一个 ConsumeQueue 文件,它相当于一个“索引”,里面记录了每条消息在 CommitLog 中的物理偏移量、大小、Tag 哈希值等信息。
Consumer 消费时,先查 ConsumeQueue 找到消息在 CommitLog 里的位置,再去 CommitLog 读取消息内容。

为什么一个 Topic 需要多个队列?
这可能是很多初学者最困惑的问题:为什么一个 Topic 下面要有多个 Queue?

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐