RocketMQ 事务消息的原理是什么?半消息和事务回查怎么工作?
简化版
RocketMQ 事务消息用来保证「本地事务执行」和「消息发送」两件事的原子性(要么都成、要么都不成)。核心是半消息(Half Message)+ 事务回查:先发一条对消费者不可见的「半消息」,再执行本地事务,本地事务成功就 commit(半消息转正、消费者可见),失败就 rollback(丢弃半消息);如果发起方在这中间宕机导致状态未知,MQ 会回查发起方的本地事务状态来决定最终 commit 还是 rollback。它相当于把「本地消息表」的机制内置进了 MQ。
详细版
完整流程(六步):
- 生产者向 MQ 发送半消息(Half Message)——消息已到 MQ,但标记为「暂不可投递」,消费者看不到。
- MQ 收到半消息,持久化并返回 ACK。
- 生产者收到 ACK 后,执行本地事务(如扣款、改订单)。
- 生产者根据本地事务结果向 MQ 提交二次确认:
- 成功 →
Commit:MQ 把半消息转为正式消息,消费者可以消费。 - 失败 →
Rollback:MQ 丢弃这条半消息。
- 成功 →
- 若 MQ 迟迟收不到二次确认(生产者在第 3、4 步之间宕机/超时),MQ 会主动回查(Check Back)生产者:这笔本地事务到底成没成?
- 生产者实现的回查接口查自己的业务状态,返回 Commit 或 Rollback,MQ 据此处理。
关键点:
- 半消息保证「消息先安全到 MQ,但暂不生效」,给本地事务留出执行窗口。
- 事务回查兜住「生产者宕机导致二次确认丢失」的边界情况。
- 事务消息只保证「本地事务 + 消息发送」原子,下游消费仍需幂等,且它保证的是最终一致(下游异步消费)。
完整版教学
一、要解决的问题:本地事务与发消息的原子性
和本地消息表一样,事务消息解决的是同一个经典难题:「执行本地事务」和「发一条 MQ 消息」如何保证要么都做、要么都不做。直接先发消息可能本地事务失败(消息多发),先做本地事务可能发消息失败(消息丢)。RocketMQ 用半消息机制在 MQ 层面解决它,好处是不用自己建本地消息表。
二、半消息:先占坑但不放行
半消息是精髓。生产者第一步就把消息发到 MQ,但 MQ 把它放进一个特殊的内部主题(RMQ_SYS_TRANS_HALF_TOPIC),对消费者不可见。这样做的意义是:消息已经安全落在 MQ 了(不会因后续生产者宕机而丢),但它还没「生效」,要等本地事务结果来决定放不放行。这等价于本地消息表里「待发送」状态的消息——已记录、未投递。
三、二次确认:本地事务结果决定放行还是丢弃
生产者执行完本地事务后,给 MQ 发第二次确认:
- 本地事务成功 → 发 Commit → MQ 把半消息从内部主题「转正」到真实主题,消费者开始能消费。
- 本地事务失败 → 发 Rollback → MQ 直接删掉这条半消息,消费者永远看不到。
到这一步,如果一切顺利,原子性就达成了:本地事务成 → 消息生效;本地事务败 → 消息作废。
四、事务回查:兜住「二次确认丢失」的边界
最棘手的情况:生产者执行完本地事务,但在发二次确认之前宕机了,或者确认消息在网络中丢了。此时 MQ 手里攥着一条半消息,却不知道该 Commit 还是 Rollback(本地事务到底成没成?)。
RocketMQ 的解法是主动回查:MQ 发现某条半消息长时间(默认一定时间后)没收到二次确认,就反向调用生产者实现的回查接口(checkLocalTransaction),问它:「你那笔本地事务成功了吗?」生产者去查自己的业务数据(比如订单状态),返回 Commit 或 Rollback,MQ 据此处理。回查可以多次(默认最多 15 次),彻底兜住宕机边界。
记忆点:半消息保证「消息先到但不生效」,二次确认在正常路径下决定生效/作废,事务回查兜住「生产者宕机导致确认丢失」这个异常路径——三者配合才完整。
五、事务消息 vs 本地消息表
| 维度 | 事务消息(RocketMQ) | 本地消息表 |
|---|---|---|
| 消息暂存 | MQ 内部半消息主题 | 自己数据库的消息表 |
| 建表 | 不用 | 要建消息表 |
| 兜底机制 | MQ 事务回查 | 后台任务扫表重投 |
| 依赖 | 需 MQ 支持事务消息 | 任意 MQ 均可 |
| 侵入 | 实现回查接口 | 维护消息表+投递任务 |
| 本质 | 相同:保证本地事务与消息发送原子 | 相同 |
选择:用 RocketMQ 且想少维护表 → 事务消息;MQ 不支持事务消息或想要更强掌控 → 本地消息表。
六、常见误区与追问
这道题不能只背概念,要把「事务消息」放回真实分布式系统里解释:谁发起、谁协调、状态如何变化、失败后怎么兜底,以及它和性能、可用性、一致性的取舍。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心结论 | 事务消息通过半消息和事务回查,把本地事务结果与消息投递绑定,实现可靠最终一致 | 不要停在名词解释 |
| 流程机制 | 发送半消息 -> 执行本地事务 -> 提交或回滚半消息 -> MQ 异常时回查本地事务状态 -> 消费者幂等消费 | 说明触发方、参与方、状态变化和兜底 |
| 工程取舍 | RocketMQ 先发送半消息,订单本地事务成功后提交消息,消费者才可见 | 分布式事务没有免费强一致,性能、可用性和业务侵入一定要取舍 |
事务消息 面试拆解:
1. 发送半消息
2. 执行本地事务
3. 提交或回滚半消息
4. MQ 异常时回查本地事务状态
5. 消费者幂等消费
记忆钩子:先说本地事务失效的原因,再拆协调、补偿、消息、幂等、对账和回滚边界;回答时要紧扣「事务消息」这道题,不要把相邻概念混成一段泛泛的分布式套话。
- 误区:事务消息保证全局强一致。 它保证消息与本地事务最终一致,下游消费仍是异步最终一致。
- 误区:提交消息后消费一定成功。 消费者可能失败,需要重试、死信和幂等。
- 误区:事务回查可以随便查。 回查必须基于可靠的本地事务状态表,不能靠内存状态。
- 追问:半消息是什么? 对消费者暂不可见的预备消息,等待本地事务结果决定提交或回滚。
- 追问:事务消息适合什么? 下单后异步通知库存、积分、优惠券等需要可靠触发的场景。
- 追问:和本地消息表怎么选? 事务消息依赖 MQ 能力,本地消息表更通用但需要自建扫描投递。
七、加强记忆
RocketMQ 事务消息用「半消息 + 事务回查」保证本地事务与消息发送的原子性:① 先发半消息(到 MQ 但消费者不可见);② 执行本地事务;③ 成功发 Commit(半消息转正可消费)、失败发 Rollback(丢弃);④ 若生产者宕机导致二次确认丢失,MQ 主动回查生产者的本地事务状态来决定 Commit/Rollback。它是本地消息表的 MQ 内置版,省去自建消息表,但要实现回查接口。下游消费仍需幂等,保证的是最终一致。