我们先从 Producer 的启动开始。一个 Producer 启动的时候,它可不是傻乎乎地直接就开始发消息了——它得先弄清楚“把消息发到哪里去”。
Producer 启动的核心步骤:
创建 Producer 实例
设置 ProducerGroup 名称
(同一个业务使用同一个 Group)设置 NameServer 地址列表
(指定集群注册中心的地址)配置其他参数
(发送超时时间、重试次数等)调用 start 方法启动 Producer
初始化 MQClientInstance
(客户端核心实例,负责网络通信和路由管理)启动 Netty 通信客户端
(建立与 NameServer 的连接)从 NameServer 拉取所有 Topic 的路由信息
启动定时任务
(每 30 秒更新一次路由信息)
Producer 就绪,等待发送消息
关键点解析:
ProducerGroup:一个业务标识,同一个业务里的 Producer 实例归属于同一个 Group。在事务消息中,同一个 Group 的 Producer 可以互相回查事务状态。
MQClientInstance:这是客户端最核心的实例,所有 Producer 和 Consumer 共用一个 MQClientInstance(同一个 JVM 里)。它负责:
管理与 NameServer 的连接
管理与 Broker 的连接
维护本地路由缓存
统一的心跳和网络通信
路由获取:Producer 启动时,会从 NameServer 拉取所有 Topic 的路由信息(不仅仅是某个特定 Topic),这样后续发消息时就不需要再等路由查询了。
定时更新:路由信息在本地缓存后,会有一个定时任务每隔 30 秒 从 NameServer 拉取最新路由,保证路由信息的时效性。
消息发送的三种方式:同步、异步、单向
在《入门认知篇》里我们简单提过三种发送方式,现在我们从底层实现的角度再来看一遍。
- 同步发送(Sync)
这是最常用、最简单的方式。发送消息后,线程阻塞等待 Broker 返回响应,收到响应后才继续执行。
SendResult sendResult = producer.send(msg);
// 阻塞等待,直到收到 Broker 的响应
Broker
Producer
Broker
Producer
线程阻塞等待
线程恢复
⏱️ 整个过程中线程处于阻塞状态
构造消息
发送请求(同步)
返回响应(成功/失败)
根据结果处理业务逻辑
适用场景:关键业务——比如下单成功后的订单消息,必须确认 Broker 收到了才能继续。异步发送(Async)
发送消息后,线程不阻塞,立即返回。等 Broker 响应回来后,通过回调函数来处理结果。
producer.sendAsync(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// 处理成功逻辑
}
@Override
public void onException(Throwable e) {
// 处理失败逻辑
}
});
// 这里立即返回,不阻塞
Broker
Producer 异步线程
Producer 主线程
Broker
Producer 异步线程
Producer 主线程
主线程立即返回
可以继续其他工作
✅ 主线程未被阻塞
⏱️ 响应处理在异步线程中
构造消息
提交异步发送任务
发送请求
返回响应
回调 SendCallback
适用场景:对延迟敏感但需要知道结果——比如前端请求发消息,不能阻塞用户操作,但失败时需要通知用户。单向发送(Oneway)
只发不管,连响应都不等。最轻量、最快,但最不可靠。
producer.sendOneway(msg);
// 不管结果,直接继续
Broker
Producer
Broker
Producer
发完就走
不等待任何响应
🚀 最快,但没有任何可靠性保证
- 构造消息
- 发送请求(单向)
- 继续执行后续代码
适用场景:日志上报、监控数据等——丢了就丢了,业务上能接受。
💡 小贴士:同步发送和异步发送虽然看起来差异很大,但在底层,它们都使用了异步网络通信(Netty)。同步发送只是在异步通信的基础上,用 CountDownLatch 等同步工具做了一个“阻塞等待”的封装。本质上,RocketMQ 的网络通信模型是全异步的。
消息发送的负载均衡策略
当一个 Topic 有多个 MessageQueue(分布在不同的 Broker 上)时,Producer 如何选择将消息发到哪个 Queue?这就涉及负载均衡策略。
RocketMQ 默认提供了两种内置策略,你也可以自定义实现:
策略一:轮询(Round Robin)—— 默认策略
依次轮流选择 Queue,确保消息均匀分布。在顺序消息场景下,会结合消息 Key 做哈希,保证同一个 Key 的消息落到同一个 Queue。
MessageQueue
消息序列
第 1 条
第 2 条
第 3 条
第 4 条
第 5 条
第 6 条
消息 1
消息 2
消息 3
消息 4
消息 5
消息 6
Queue 0
Queue 1
Queue 2
按顺序轮流分配:1→Q1, 2→Q2, 3→Q3, 4→Q1…
策略二:一致性哈希(Consistent Hash)
根据消息 Key 的哈希值,通过哈希环来决定消息发到哪个 Queue。保证同一个 Key 的消息始终落在同一个 Queue 上(对于顺序消息特别重要)。
消息 Key
哈希值映射到环上
哈希值映射到环上
哈希值映射到环上
顺时针查找第一个节点
顺时针查找第一个节点
顺时针查找第一个节点
一致性哈希环
节点 1
Queue 0
节点 2
Queue 1
节点 3
Queue 2
节点 4
Queue 3
节点 5
Queue 0
节点 6
Queue 1
order_123
order_456
order_789
位置 A
位置 B
位置 C
同一 Key 始终落到同一 Queue
新增节点时只影响局部 Key 的重新分配
自定义策略:实现 MessageQueueSelector 接口,根据自己的业务逻辑选择 Queue(比如按订单 ID 取模)。
消息发送的重试机制与故障规避
消息发送不是总能一次成功的。网络抖动、Broker 故障、磁盘满了……各种原因都可能导致发送失败。RocketMQ 有一套完善的重试和故障规避机制。
重试机制
是
否
否
是
是
否
是
否
发送消息
第 1 次尝试
成功?
返回成功
是否为
可重试的异常?
直接返回失败
不重试
第 2 次尝试
等待 50ms
成功?
… 最多重试 retryTimesWhenSendFailed 次
最后一次成功?