RocketMQ 的架构是怎么样的?

#中间件#面试八股

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 请求平衡。
笔记加载中…