Skip to content

RocketMQ:交易型消息中间件指南

RocketMQ 是 Apache 顶级项目,最早来自阿里巴巴大规模电商交易场景。
相比“通用队列”,RocketMQ 更突出的是业务消息语义:普通消息、顺序消息、延时消息、事务消息、消费重试与死信。

一句话理解

RocketMQ 是一个面向业务系统的分布式消息中间件:NameServer 负责路由发现,Broker 负责消息存储和投递,Producer/Consumer 通过 Topic 和 Queue 完成发布订阅。

RocketMQ 路由发现与消息投递

适合与不适合

适合:

  • 订单、支付、库存等核心交易链路。
  • 需要事务消息保证最终一致。
  • 需要顺序消息处理状态流转。
  • 需要延时消息处理超时取消、到期提醒。

不适合:

  • 纯日志/埋点/数据湖事件流,Kafka 生态更成熟。
  • 简单后台任务队列,RabbitMQ 更轻。
  • 多租户平台化和存储计算分离诉求特别强,Pulsar 更合适。

核心组件

组件作用
NameServer路由注册中心,保存 Broker 与 Topic 路由
Broker消息存储、投递、查询、复制
Topic消息逻辑分类
MessageQueueTopic 的物理队列,并发和顺序的基本单位
Producer消息生产者
Consumer消息消费者
Consumer Group消费者组,同组共同消费
Proxy5.x 引入的重要接入层能力,便于协议与云原生接入

Topic 与 Queue

一个 Topic 通常包含多个 Queue,Queue 分布在不同 Broker 上。

RocketMQ Topic 与 Queue

设计原则:

  • Topic 按业务域和事件类型规划,例如 order-event-topicpayment-event-topiccoupon-command-topic。不要把所有业务事件塞进一个大 Topic,否则权限、监控、重试和排障都会混在一起。
  • Queue 数量决定消费并发上限。Queue 太少会限制 Consumer Group 的并行度,Queue 太多会增加调度、拉取和路由管理成本。
  • 有顺序要求的消息要按同一业务 key 路由到同一 Queue,例如同一个 orderId 的创建、支付、发货、完成事件。
  • Topic 名称和消息类型要稳定。普通消息、顺序消息、延时消息、事务消息的行为不同,不建议在同一个 Topic 内随意混用。

工程上可以把 Topic 看成“事件契约的容器”,把 Queue 看成“并发与顺序的执行单元”。规划 Topic 时先问业务边界,规划 Queue 时再问吞吐和顺序。

消息契约设计

RocketMQ 能保证消息可靠投递,但不能替业务定义消息含义。真正影响后续维护的是消息契约。

一条订单事件至少应包含:

字段说明
eventId事件唯一标识,用于链路追踪和排重
eventType事件类型,例如 order.createdorder.paid
eventVersion契约版本,例如 v1
businessKey业务主键,例如 orderId,用于顺序、幂等和排查
occurredAt业务事件发生时间
traceId请求链路标识
payload业务载荷,只放消费者需要的字段

消息体不要直接序列化数据库对象。数据库表字段会随实现变化,消息契约面向系统协作,应该保持兼容演进。新增字段尽量向后兼容,删除字段要走版本升级,不要让下游消费者在运行时才发现字段消失。

四类核心消息

类型能力场景
Normal Message普通消息异步通知、系统解耦
FIFO Message顺序消息订单状态流转、交易撮合
Delay Message延时/定时投递超时关闭订单、到期提醒
Transaction Message事务消息本地事务与消息发送最终一致

普通消息流程

RocketMQ 普通消息流程

普通消息适合大多数事件通知,例如订单创建后通知库存、搜索、积分、消息中心。它的重点是可靠传输,不额外承诺顺序、延时或本地事务一致性。

生产端要关注三件事:

  • 发送失败要明确处理。网络异常、Broker 重启、请求超时都可能触发重试,但重试不能替代业务最终一致设计。
  • 消息要设置业务 key。排查“这笔订单的消息去哪了”时,orderId 比内部 msgId 更有用。
  • 发送结果只代表 Broker 接收成功,不代表消费者已经处理成功。

消费端要接受“至少一次”的现实:消息可能重复投递,消费者必须幂等。

顺序消息

RocketMQ 顺序消息的关键是:同一业务序列进入同一个 MessageQueue,并由消费者顺序处理

RocketMQ 顺序消息

适合:

  • 订单状态:创建 -> 支付 -> 发货 -> 完成。
  • 账户流水。
  • 数据同步中的同一主键变更。

注意:

  • 全局顺序通常不推荐,因为会牺牲并发。
  • 分区顺序是更常见做法。
  • 顺序消息失败时容易阻塞同一个 Queue 后续消息。消费者逻辑要尽量短,不能在顺序消费者里做长时间外部调用。
  • 顺序不等于状态一定正确。订单状态仍要由数据库状态机兜底,例如已支付订单不能被超时关闭。

适合顺序消息的判断标准是:同一个业务 key 的多个事件必须按先后关系处理,且可以接受该 key 维度的串行执行。只要业务能通过状态机、幂等和最终一致兜住,就不必强行上顺序消息。

延时消息

延时消息是“现在发送,将来投递”。常见场景:

  • 30 分钟未支付关闭订单。
  • 会议开始前 10 分钟提醒。
  • 优惠券到期提醒。

RocketMQ 延时消息流程

延时消息适合“到时间后检查”的场景,不适合表达“到时间后一定执行某个结论”。订单超时关闭就是典型例子:延时消息到期后只能触发检查,消费者仍要查询订单库。如果订单已支付或已取消,就应该忽略这条超时检查消息。

设计延时消息时要明确:

  • 延时消息是否允许重复触发。
  • 到期后执行的是“检查”还是“直接变更”。
  • 业务状态以哪个系统为准。
  • 如果延时消息丢失或延迟过长,是否有定时补偿任务兜底。

对于强业务场景,延时消息通常还要配合数据库状态、补偿扫描和监控告警,不能成为唯一控制点。

事务消息

事务消息用于解决“本地事务成功,但消息没发出去”或“消息发出去了,本地事务失败”的一致性问题。

RocketMQ 事务消息流程

关键点:

  • 事务消息不是强一致分布式事务,而是最终一致方案。
  • 本地事务状态回查必须可靠、可重入。
  • Consumer 仍然需要幂等。
  • 事务消息解决的是生产端本地事务与消息发送的一致性,不负责保证下游消费者一定成功处理。
  • 回查逻辑不能依赖内存变量。Broker 回查时,Producer 可能已经重启,必须能通过数据库订单状态、支付流水状态等持久化数据判断提交还是回滚。
  • 回查接口要可重入。重复回查同一笔业务时,应返回同一个结论,不能因为中间状态变化导致消息悬挂。

支付成功发券可以使用事务消息:支付服务先发送半消息,再提交本地支付事务;本地事务成功后提交消息,下游发券消费者收到 order.paid 后幂等发券。如果提交状态丢失,Broker 回查支付流水,确认支付成功就提交消息,确认失败就回滚消息。

消费重试与死信

RocketMQ 消费重试与死信

实践建议:

  • 可重试错误:网络超时、临时下游不可用。
  • 不可重试错误:消息格式错误、业务状态非法。
  • 重试次数达到上限后进入死信队列,并触发告警。

消费失败要先分类,再决定返回失败还是吞掉并记录。所有错误都抛出给 RocketMQ 重试,会把不可恢复错误变成重复压力。

错误类型例子建议处理
临时错误下游接口超时、数据库连接短暂异常返回失败,让 RocketMQ 按策略重试
资源限流第三方接口限流、线程池满本地限流 + 返回失败,必要时降低消费并发
业务等待订单状态暂未同步、支付回调还未到可以短暂重试,但要设置最大等待和补偿任务
数据错误消息缺少必填字段、金额格式非法记录错误,进入异常表或死信,不要无限重试
幂等冲突优惠券已发、状态已处理识别为成功,返回消费成功

死信队列不是垃圾桶,而是人工和系统补偿入口。生产环境至少要有死信告警、死信查看、死信原因记录和重放策略。重放前要先修复根因,否则只是把同一批毒丸消息重新打回主链路。

消费者幂等

RocketMQ 无法替业务消除所有重复消息。重复可能来自生产端重试、Broker 投递重试、消费者超时、网络抖动或人工重放。消费者要把“同一条业务消息处理多次”和“处理一次”的结果做成一致。

常见幂等方式:

方式适用场景说明
唯一键约束发券、积分、记账orderId + couponTemplateIdeventId 建唯一索引
状态机约束订单状态流转只允许 待支付 -> 已支付,拒绝非法回退
幂等记录表外部接口调用、跨库处理消费前检查处理记录,成功后落库
乐观锁库存扣减、账户变更通过版本号或状态条件更新
业务去重缓存短时间重复请求只能做辅助,不能替代持久化幂等

发券消费者的典型做法是:以 orderId + couponTemplateId 建唯一约束,插入成功表示首次发券,唯一键冲突表示已经发过,消费者应返回成功而不是继续重试。

统一案例:订单超时关闭 + 支付成功发券

Topic 规划

Topic消息类型说明
order-topic普通/顺序订单状态事件
order-timeout-topic延时消息超时检查
payment-topic事务消息支付成功事件
coupon-topic普通消息发券任务

流程图

RocketMQ 订单超时与发券流程

关键设计

  1. 下单成功后发送 30 分钟延时消息。
  2. 延时消息到期后只做状态检查,不直接假设订单未支付。
  3. 支付成功使用事务消息,保证支付本地事务和 order.paid 事件最终一致。
  4. 发券消费者以 orderId + couponTemplateId 幂等。
  5. 同一订单状态事件可按 orderId 做分区顺序。

高可用与部署

模式说明适用
单 Master简单但有单点风险本地/测试
多 Master性能高,Master 宕机期间部分消息不可消费对可用性要求一般
多 Master 多 Slave 异步复制性能与可用性平衡常见生产场景
多 Master 多 Slave 同步双写可靠性更高,延迟略高核心交易链路

监控指标

指标含义
Topic 消息堆积消费能力是否不足
Consumer TPS消费吞吐
Send TPS发送吞吐
消费失败率下游或业务异常
Broker 磁盘使用率存储压力
CommitLog 刷盘延迟可靠性与性能风险
DLQ 数量是否存在毒丸消息

常见坑

问题现象原因解决方案
把事务消息当强一致事务本地支付成功了,但下游发券仍可能失败;团队误以为事务消息能覆盖所有下游事务消息只保证生产端本地事务与消息发送的一致性,下游消费仍是异步最终一致下游消费者必须幂等;失败进入重试和死信;关键业务增加补偿任务和对账任务
事务回查依赖内存状态Producer 重启后无法判断半消息该提交还是回滚,半消息长期悬挂回查逻辑没有以数据库业务状态为准回查接口只读持久化状态,例如支付流水、订单状态;同一事务 ID 重复回查必须返回稳定结果
顺序消息滥用全局顺序消费吞吐很低,一个慢消息拖住整个 Topic把“同订单有序”误写成“所有订单全局有序”orderIdaccountId 等业务 key 做分区顺序;只在同一 key 内保证顺序
顺序消费者做重外部调用某个订单处理慢,后续同 Queue 消息堆积明显顺序消费要求同 Queue 串行,慢调用会扩大阻塞顺序消费者只做短逻辑;外部慢操作拆成异步任务;失败要快速返回并进入受控重试
消费失败不分类毒丸消息反复重试,消费失败率和堆积持续上升消息格式错误、业务非法状态也被当作临时错误重试按错误类型处理:临时错误重试,数据错误记录异常并进入死信,幂等冲突返回成功
死信队列无人处理DLQ 数量增长,但业务缺失一直没人发现只配置了死信,没有告警、查看和重放流程对 DLQ 数量和增长率告警;记录失败原因;修复根因后再定向重放
Consumer 不幂等重复发券、重复扣库存、重复写账RocketMQ 是至少一次投递语义,重复消息不可完全避免用业务唯一键、状态机、幂等表或乐观锁兜底;重复处理应返回消费成功
不设置业务 key线上排查时只能看到内部 msgId,很难定位某笔订单消息消息缺少稳定业务标识发送消息时设置 orderIdpaymentId 等业务 key;日志里同时打印 key、Topic、Consumer Group、traceId
延时消息直接改状态订单已支付后仍被超时关闭把“到期检查”误写成“到期必然关闭”延时消息只触发检查;消费者查询订单库并用状态机判断;已支付或已取消时直接忽略
Topic 规划过粗多业务共用一个 Topic,权限、堆积、重试互相影响为了省事把所有事件放到一个主题按业务域和事件类型拆分 Topic;高风险、高吞吐、不同重试策略的消息分开治理
只看 TPS 不看堆积发送和消费 TPS 看起来正常,但延迟越来越高消费速率略低于生产速率,堆积持续增长同时看 Topic 堆积、消费延迟、失败率、重试量、DLQ;告警应关注趋势
消息体过大Broker 网络、磁盘和客户端内存压力异常把文件、长文本、大对象直接塞进 MQMQ 只传业务事件和文件引用;大文件放对象存储,消息里放 URL、版本和校验信息

总结

RocketMQ 更适合承载带业务语义的消息链路:普通消息用于解耦,顺序消息用于同 key 状态流转,延时消息用于到期检查,事务消息用于生产端本地事务与消息发送最终一致。

工程落地时不要把能力理解成魔法开关。顺序要靠 key 设计,事务要靠可靠回查,延时要靠状态检查,可靠消费要靠重试、死信、幂等和补偿共同完成。

别急,先让缓存热一下。