RocketMQ 的架构是怎么样的?
结论:RocketMQ 由四类角色组成——名称服务(NameServer)负责路由, broker 负责存储与投递,生产者(Producer)与消费者(Consumer)以组(Group)为单位接入。理解架构的关键是两条线:路由怎么同步(Broker 定时向所有 NameServer 心跳,NameServer 之间互不通信)与消息怎么落盘(CommitLog 顺序写 + ConsumeQueue 逻辑队列 + IndexFile 哈希索引)。
一、整体结构
┌──────────────┐ ┌──────────────┐
│ NameServer A │ │ NameServer B │ 无状态、互不通信
└──────┬───────┘ └──────┬───────┘
注册/心跳(30s) │ │ 拉取路由(30s)
┌───────────────┴──────────────────┴──────────────┐
│ │
┌──────▼──────┐ 主从同步 ┌─────────────┐ ┌──────▼──────┐
│ Broker 主 │ ──────────> │ Broker 从 │ │ Producer │
│ CommitLog │ │ CommitLog │ │ (Group) │
│ ConsumeQueue│ │ ConsumeQueue│ └──────┬──────┘
│ IndexFile │ │ IndexFile │ │ 发送
└──────┬──────┘ └─────────────┘ │
│ 拉消息(长轮询) │
┌──────▼──────┐ │
│ Consumer │ <────────────────────────────────────────┘
│ (Group) │ 同一 Group 内均分队列,位点存 Broker
└─────────────┘
二、各角色职责
| 角色 | 职责 | 关键点 |
|---|---|---|
| NameServer | 保存 Topic 到队列的路由、Broker 存活列表 | 无状态、节点间不通信,任一可用即可 |
| Broker | 存储消息、处理读写、主从复制 | 按 brokerId 区分主(0)从(>0) |
| Producer | 选队列、发送、失败重试 | 从 NameServer 拉路由并本地缓存 30s |
| Consumer | 拉取、消费、上报位点 | 推模式是对拉模式 + 长轮询的封装 |
路由注册是最终一致的:Broker 每 30 秒向所有 NameServer 发送心跳,NameServer 每 10 秒扫描一次,超过 120 秒没心跳的 Broker 被剔除。所以 Broker 宕机后,生产端最长可能有一分钟仍拿到旧路由,需要靠发送失败重试兜底。
三、存储三件套
| 文件 | 内容 | 写方式 |
|---|---|---|
| CommitLog | 所有 Topic 的消息体,单文件默认 1GB | 严格顺序追加,写完一个文件切下一个 |
| ConsumeQueue | 每个队列一个目录,存 offset + size + tag hash,每条 20 字节 | 由后台线程异步构建,消费者按它定位 |
| IndexFile | 按消息 Key 建的哈希索引 | 用于"按业务 ID 查消息",排查问题时用 |
消息先写 CommitLog,再异步分发到 ConsumeQueue,因此消费进度落后于写入进度是正常现象;也正因如此,堆积时磁盘只会顺序增长,不会产生随机写放大。
四、消费位点与推拉模型
- 集群消费(Clustering):同一消费组内队列被均分,位点保存在 Broker;
- 广播消费(Broadcasting):每个消费者都收全量,位点保存在本地文件;
- 推模式(Push):客户端注册监听器,内部由
PullMessageService长轮询拉取,Broker 无数据时hold 请求最多 15 秒再返回,兼顾实时性与空轮询开销。
五、部署配置示例
# broker-a.properties(主)
brokerClusterName=DefaultCluster
brokerName=broker-a
brokerId=0
namesrvAddr=10.0.0.11:9876;10.0.0.12:9876
deleteWhen=04
fileReservedTime=72
brokerRole=ASYNC_MASTER
flushDiskType=ASYNC_FLUSH
storePathRootDir=/data/rocketmq/store
# 先启 NameServer,再启 Broker(2m-2s 需要 4 份配置)
nohup sh bin/mqnamesrv &
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a.properties &
nohup sh bin/mqbroker -c conf/2m-2s-async/broker-a-s.properties &
# 验证集群
sh bin/mqadmin clusterList -n 10.0.0.11:9876
六、常见追问
- NameServer 为什么不做成 ZooKeeper 那样的选举集群? 路由数据是"可再生的弱一致数据",让每个 Broker 向全部节点上报即可,去掉选举就少了脑裂与运维负担。
- Broker 挂掉后消费者会怎样? 从节点在 4.x 中默认只读不接管(需 DLedger 或 5.x 控制器),消费者会因拉取失败而重试,期间消息在从节点可读但位点不前进。
- CommitLog 一个文件 1GB,为什么不按 Topic 分文件? 分 Topic 会让写入变成多路随机写;顺序写单文件才能用上页缓存(Page Cache)与磁盘顺序带宽。
- 消费位点存在 Broker 会不会有性能问题? 位点每 5 秒持久化一次,正常消费时几乎无感;频繁重平衡才会放大开销。
小结
- 四角色:NameServer 管路由、Broker 管存储、Producer/Consumer 按组接入。
- 两条主线:路由靠心跳最终一致,消息靠 CommitLog 顺序写保证吞吐。
- 存储三件套分工明确:CommitLog 存正文、ConsumeQueue 定位、IndexFile 查 Key。
- 推模式本质是长轮询拉取,实时性与开销靠 Broker 端 hold 请求平衡。