RocketMQ架构精讲:NameServer、Broker、Producer、Consumer核心流程
作者:黒漂技术佬 系列专栏:RocketMQ核心原理与无人售货柜项目实战
一、先看全景:RocketMQ整体架构
把RocketMQ的架构想象成一个快递物流网络,有四个核心角色:
┌─────────────────┐
│ NameServer │ ← 注册中心(物流信息中心)
│ (路由信息存储) │
└────────┬────────┘
│ 心跳注册/路由查询
┌──────────────┼──────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌────▼──────┐
│ Broker-A │ │ Broker-B │ │ Broker-C │ ← 消息存储服务器(快递分拣中心)
│ (Master) │ │ (Master) │ │ (Master) │
│ (Slave) │ │ (Slave) │ │ (Slave) │
└─────┬─────┘ └─────┬─────┘ └────┬──────┘
│ │ │
│ 发送消息 │ │
┌─────┴──────────────┴──────────────┴─────┐
│ Producer │ ← 消息生产者(寄件人)
│ (订单服务/支付服务/...) │
└──────────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ Consumer │ ← 消息消费者(收件人)
│ (库存服务/推送服务/告警服务) │
└──────────────────────────────────────────┘
│ │ │
└──────────────┼──────────────┘
│ 拉取消息
┌────────▼────────┐
│ Broker │
└─────────────────┘
四个角色的分工一句话概括:
- NameServer:路由信息中心,告诉Producer消息该往哪发,告诉Consumer消息该从哪取
- Broker:消息存储服务器,真正存消息和转发消息的地方
- Producer:消息生产者,发消息的
- Consumer:消息消费者,收消息的
下面逐个拆解。
二、NameServer:轻量级注册中心
2.1 它是干什么的
NameServer是RocketMQ的"通讯录"。Broker启动时把自己的地址、Topic信息、队列信息注册到NameServer;Producer和Consumer启动时从NameServer拉取这份路由表,才知道消息往哪发、从哪取。
2.2 为什么不用Zookeeper
很多中间件(Kafka、Dubbo)用Zookeeper做注册中心,RocketMQ早期版本也用过,后来自己搞了个NameServer,原因是一个经典的分布式理论取舍:AP vs CP。
先解释下CAP定理的三个字母:
- C(Consistency)一致性:所有节点看到的数据是一致的
- A(Availability)可用性:每个请求都能收到响应(不保证是最新数据)
- P(Partition tolerance)分区容错:网络分区时系统仍能运行
分布式系统三者只能选其二,由于网络分区(P)不可避免,实际选择是CP还是AP。
| 对比项 | Zookeeper (CP) | NameServer (AP) |
|---|---|---|
| 一致性 | 强一致 | 最终一致 |
| 可用性 | 写入需多数派投票,leader选举期间不可用 | 各节点独立,互不通信 |
| 复杂度 | 高(ZAB协议、leader选举) | 低(就是个内存HashMap) |
| 部署 | 至少3节点集群 | 可单节点,也可多节点互不关联 |
RocketMQ的选择逻辑:在消息中间件场景里,可用性比强一致更重要。Broker挂了NameServer感知到就行,不追求所有NameServer瞬间数据一致。每个NameServer节点独立维护路由表,Producer/Consumer连任意一个都能拿到数据,偶尔短暂不一致在业务上可接受(Broker心跳30秒上报一次,最多30秒感知到变化)。
2.3 心跳机制
Broker每隔30秒向所有NameServer发心跳,NameServer收到后更新Broker的存活时间戳。NameServer每隔10秒扫描一次路由表,如果发现某个Broker超过120秒没心跳,就判定它下线,从路由表里移除。
这套机制简单粗暴但有效:不搞复杂的一致性协议,用心跳+超时来维护状态。
三、Broker:消息存储和转发服务器
3.1 Master/Slave架构
Broker分Master和Slave两种角色:
- Master Broker:负责读写,Producer发消息到Master,Consumer也从Master拉消息(默认)
- Slave Broker:只负责备份,从Master同步数据,Master挂了可接管读请求
同步方式有两种:
异步复制(ASYNC_MASTER):Master收到消息后立即返回成功,然后异步复制给Slave。优点是延迟低,缺点是Master挂了可能丢少量未复制的数据。
同步双写(SYNC_MASTER):Master收到消息后,等Slave也写入成功才返回。优点是数据不丢,缺点是延迟略高。
售货柜场景怎么选?支付相关消息用同步双写保数据安全,日志类消息用异步复制换性能。
3.2 Broker的存储结构
Broker存消息不是随便往磁盘一堆,而是有精心的设计:
CommitLog(统一存储文件)
├── 所有Topic的消息混在一起,按写入顺序追加
├── 每个文件固定1GB,写满新建
└── 顺序写磁盘,性能接近内存写
ConsumeQueue(消费队列/逻辑索引)
├── 每个Topic的每个Queue对应一个ConsumeQueue文件
├── 存储的是消息在CommitLog中的偏移量(offset)、大小、Tag的hashcode
└── Consumer通过ConsumeQueue快速定位消息
IndexFile(索引文件)
└── 支持按MessageId和Key查询消息
这个设计的精妙之处在于:写入时所有消息追加到同一个CommitLog,顺序写性能极高;读取时通过ConsumeQueue索引快速定位,读取效率也不低。写入和读取两不误。
3.3 Broker注册到NameServer
Broker启动时把自己的信息打包发给所有NameServer,注册信息包括:
- Broker名称(brokerName)
- Broker地址(IP:Port)
- 集群名称(clusterName)
- Master/Slave角色标识
- 该Broker上所有的Topic和Queue信息
注册后每30秒心跳续约,心跳时也会带上最新的Topic路由信息。
四、Producer:消息生产者
4.1 启动流程
Producer启动时做三件事:
- 从NameServer拉取路由信息(哪些Broker有哪些Topic的哪些Queue)
- 建立到目标Broker的网络连接(Netty长连接)
- 启动定时任务,每30秒从NameServer更新路由信息
路由信息缓存在本地,发消息时不用每次都问NameServer,30秒更新一次够用了。
4.2 消息发送流程
Producer发送一条消息的完整流程:
1. Producer校验消息(Topic、Body非空等)
2. 查找Topic的路由信息(本地缓存)
3. 选择目标Queue(负载均衡策略)
├── 轮询(默认):Round Robin
├── 故障隔离:上次发送失败的Queue暂时排除
└── 指定Queue:MessageQueueSelector自定义
4. 构建请求,通过网络发送给目标Broker
5. 等待Broker返回确认(根据发送方式决定是否阻塞)
├── 同步发送:阻塞等待ACK
├── 异步发送:不阻塞,回调通知结果
└── 单向发送:直接返回不等
6. 根据结果做后续处理(重试/记录日志等)
售货柜场景举例:关门后发送"订单创建"消息,用同步发送确保消息不丢;发送"用户行为日志"用单向发送,丢了也无所谓。
4.3 发送失败重试
Producer发送失败时会自动重试,默认重试2次(共3次尝试)。重试时会避开上次失败的Broker,选择其他Broker的Queue发送。这个机制配合Broker的Master/Slave架构,能在单节点故障时自动切换,对用户透明。
五、Consumer:消息消费者
5.1 两种消费模式
集群消费(Clustering):同一个ConsumerGroup下的多个Consumer实例分摊消费所有消息。比如100条消息,2个Consumer各消费50条。这是最常用的模式,适合横向扩展消费能力。
ConsumerGroup-A (集群模式)
├── Consumer-1 ← 消费 Queue-0, Queue-1
├── Consumer-2 ← 消费 Queue-2, Queue-3
└── Consumer-3 ← (如果只有4个Queue,这个会空闲)
广播消费(Broadcasting):同一个ConsumerGroup下的每个Consumer实例都消费全量消息。比如100条消息,2个Consumer各消费100条。适合所有节点都要感知同一份数据的场景,比如本地缓存刷新。
售货柜场景:库存扣减用集群消费(一台柜子的消息只需一个消费者处理),设备配置全量下发用广播消费(所有柜子都要收到配置更新)。
5.2 ConsumerGroup的概念
ConsumerGroup是逻辑上的一组消费者实例,同一个Group必须消费同一个Topic,且消费逻辑必须一致。你可以把它理解为"一个消费团队",团队里的人分工干活(集群模式)或者各自干全部活(广播模式)。
一个典型的微服务部署:库存服务部署3个实例,都配置同一个ConsumerGroup inventory_consumer_group,3个实例自动分摊消费库存Topic的消息。某个实例挂了,Rebalance机制会自动把它负责的Queue分配给其他实例,消费不中断。
5.3 消费者拉取机制
RocketMQ的Consumer本质上是Pull模式——消费者主动从Broker拉取消息,不是Broker推过来的。
但用起来感觉像Push(DefaultPushConsumer),因为它内部用长轮询做了封装:
Consumer向Broker发拉取请求
├── Broker有消息 → 立即返回
└── Broker没消息 → Hold住请求,挂起5秒(默认)
├── 5秒内有新消息 → 立即返回
└── 5秒后还没消息 → 返回空,Consumer再次发起拉取
长轮询的好处:既避免了Push模式下Consumer处理不过来被压垮,又避免了Pull模式下频繁空轮询浪费资源。
六、消息流转全链路
把上面四个角色串起来,一条消息从产生到消费的完整链路:
1. Producer启动 → 从NameServer获取Topic路由信息
2. Producer发送消息 → 根据路由选择Queue → 发送到对应Broker
3. Broker收到消息 → 写入CommitLog → 构建ConsumeQueue索引 → 返回ACK
4. Consumer启动 → 从NameServer获取Topic路由信息
5. Consumer向Broker发拉取请求 → 通过ConsumeQueue定位消息 → 从CommitLog读取
6. Consumer处理消息 → 返回消费状态(CONSUME_SUCCESS/RECONSUME_LATER)
7. 消费失败 → Broker按延迟等级重新投递 → 超过重试上限进入死信队列
七、结合售货柜场景的角色映射
把架构映射到我们的无人售货柜项目:
| RocketMQ角色 | 售货柜项目对应 | 具体说明 |
|---|---|---|
| NameServer | 部署在总部机房 | 所有服务都连总部NameServer获取路由 |
| Broker-A | 总部主Broker | 存储订单、支付、库存等核心消息 |
| Broker-B | 区域分中心Broker | 按区域分Topic,减少跨地域网络延迟 |
| Producer | 订单服务、支付服务、柜子网关 | 各业务服务发消息到Broker |
| Consumer | 库存服务、推送服务、告警服务、ERP同步服务 | 各服务消费各自Topic |
比如一个完整的支付流程:
- 用户关门 → 订单服务(Producer)发送"订单创建"消息到Broker
- 库存服务(Consumer)消费消息扣减本地库存
- 用户支付完成 → 支付服务(Producer)发送"支付成功"消息
- 柜子网关(Consumer)消费消息,通知对应柜子出货
- ERP同步服务(Consumer)消费同一消息,更新总部库存
- 推送服务(Consumer)消费同一消息,给用户发扣款通知
一条支付消息被4个消费者服务各自消费(不同ConsumerGroup),各司其职,互不干扰。这就是解耦的威力——支付服务只管发消息,谁来消费、怎么消费,它一概不关心。
八、小结
这一篇我们拆解了RocketMQ的四大核心组件:NameServer作为轻量级AP注册中心,Broker负责消息存储和转发(Master/Slave架构+CommitLog/ConsumeQueue存储设计),Producer和Consumer通过长轮询完成消息的发送和拉取。最后把这些角色映射到无人售货柜项目的实际部署中。
下一篇我们动手实操,搭建RocketMQ开发环境。