← 返回题目列表

什么是事务消息?RocketMQ 的事务消息如何实现?

高频 困难 第 9 / 25 题 更新于 2026/07/28
消息队列事务消息RocketMQ分布式事务

简化版

事务消息解决的是「本地事务发送消息要么都成功、要么都失败」的一致性问题——比如「扣款成功」和「发送扣款通知消息」必须同时成立,不能扣了款却没发消息、或发了消息却没扣款。RocketMQ 的实现基于**「半消息 + 二次确认 + 事务回查」:先发一条半消息**(对消费者不可见)→ 执行本地事务 → 根据事务结果提交或回滚半消息 → 如果二次确认丢失,Broker 会回查本地事务状态来决定最终提交还是回滚。

详细版

RocketMQ 事务消息的完整流程:

1. 生产者发送【半消息】(half message) 到 Broker
   —— 半消息已存储,但对消费者【不可见】
2. Broker 返回半消息发送成功
3. 生产者执行【本地事务】(如扣款、写库)
4. 生产者根据本地事务结果,向 Broker 发送二次确认:
   - COMMIT   → 半消息变为正式消息,消费者可见、可消费
   - ROLLBACK → 删除半消息,消费者永远收不到
5. 如果第 4 步的确认丢失(网络问题/生产者宕机):
   - Broker 会定期【回查】(check back) 生产者的本地事务状态
   - 生产者实现回查接口,返回事务的最终状态
   - Broker 据此决定 COMMIT 或 ROLLBACK
  • 半消息:已发到 Broker 但对消费者不可见,等本地事务结果决定命运。
  • 二次确认:本地事务成功则 COMMIT(消息可见),失败则 ROLLBACK(删除)。
  • 事务回查:二次确认丢失时的兜底,Broker 主动问生产者「你那个事务到底成没成」。

完整版教学

一、要解决的问题:本地事务与发消息的原子性

考虑一个典型场景:用户下单后,订单服务要「创建订单(本地数据库事务)」+「发一条消息通知库存服务扣库存」。这两件事必须一致

  • 如果订单创建成功、但消息没发出去→ 库存没扣,超卖。
  • 如果消息发出去了、但订单创建失败(回滚了)→ 库存被扣了,但订单不存在,数据错乱。

问题在于「操作本地数据库」和「发消息到 MQ」是两个独立系统的操作,无法用一个本地事务包起来。普通的「先写库再发消息」或「先发消息再写库」都存在中间失败导致不一致的窗口。事务消息就是为解决这个「本地事务 + 发消息」的原子性而生。

二、核心机制:半消息(Half Message)

RocketMQ 的巧妙之处在于引入半消息

  1. 生产者先发一条半消息到 Broker。这条消息已经被 Broker 存储了,但对消费者不可见——消费者此刻消费不到它,它处于「待定」状态。
  2. Broker 确认半消息收到后,生产者再去执行本地事务(创建订单)。
  3. 根据本地事务的成败,生产者告诉 Broker 怎么处理这条半消息:
    • 本地事务成功 → 发 COMMIT,半消息「转正」,变成正式消息,消费者能看到并消费。
    • 本地事务失败 → 发 ROLLBACK,Broker 删除半消息,消费者永远看不到。

关键点:消息先发(占个位),但暂不可见;等本地事务尘埃落定,再决定这条消息是「转正」还是「删除」。 这样保证了「本地事务成功 ⟺ 消息最终可见」。

三、兜底机制:事务回查(Check Back)

上面第 3 步的「二次确认(COMMIT/ROLLBACK)」也可能失败——比如生产者执行完本地事务后、还没来得及发确认就宕机了,或者确认消息在网络中丢失了。此时 Broker 手里有一条「半消息」,却迟迟等不到确认,不知道该 COMMIT 还是 ROLLBACK。

RocketMQ 用事务回查解决:

  • Broker 发现某条半消息长时间没收到二次确认,就会主动回查生产者:「你之前那个本地事务,到底成功了没?」
  • 生产者需要实现一个回查接口:根据本地事务的记录(比如查订单表看订单到底建没建成),返回事务的最终状态。
  • Broker 根据回查结果决定最终 COMMIT 还是 ROLLBACK。

回查会重试多次(默认最多 15 次),确保最终能确定这条半消息的命运。这就是为什么事务消息保证的是最终一致性——即使中间出故障,靠回查也能最终达成一致。

四、为什么这套机制能保证一致性

把各种失败情况过一遍:

  • 半消息发送失败:本地事务还没执行,什么都没发生,无不一致。
  • 本地事务失败:ROLLBACK 删除半消息,消息不可见——「事务没成,消息也没发」,一致。
  • 本地事务成功、COMMIT 成功:消息转正可见——「事务成了,消息也发了」,一致。
  • 本地事务成功、但 COMMIT 丢失:靠回查,生产者查到本地事务成功,返回 COMMIT——最终消息可见,一致。
  • 本地事务失败、但 ROLLBACK 丢失:靠回查,生产者查到本地事务失败,返回 ROLLBACK——最终删除消息,一致。

所有分支都能收敛到「本地事务与消息状态一致」,这就是事务消息的可靠性来源。

五、注意事项与局限

  • 保证的是「本地事务 → 发消息」这一端的一致性:即「本地事务成功则消息一定被消费者收到」。但下游消费者能否成功处理消息,是另一回事——消费端还需要自己保证幂等和重试(消息可能重复投递)。
  • 回查接口必须幂等且可靠:生产者要能准确回答「事务成没成」,通常靠查业务表状态实现。
  • 本地事务要能被查询:设计上要留下事务是否成功的痕迹(如订单表记录)供回查。
  • 只保证最终一致,不是强一致:中间有短暂的「消息待定」窗口。

六、常见误区与追问

考点正确口径
事务消息保证本地事务和消息发送最终一致
RocketMQ 流程半消息、本地事务、提交/回滚、事务回查
适用下单后通知、积分、库存等跨服务最终一致
send half message
execute local transaction
if success -> commit message
if fail -> rollback message
if unknown -> broker checks transaction status

事务消息解决的是“本地事务成功后消息必须可见,失败后消息不能被消费”。

  • 误区:事务消息等于分布式强一致事务。 它通常实现最终一致,不是两阶段提交式强一致。
  • 误区:半消息会被消费者立即消费。 半消息对消费者不可见,只有提交后才可投递。
  • 误区:事务回查可以不实现。 本地事务结果未知时,Broker 需要回查来决定提交或回滚。
  • 追问:为什么先发半消息? 先让 Broker 知道有待确认消息,避免本地事务成功后发送消息失败。
  • 追问:本地事务表有什么用? 记录事务状态,供回查接口可靠返回提交/回滚结果。
  • 追问:消费者还需要幂等吗? 需要,事务消息保证投递一致性,不保证消费端只执行一次。

七、加强记忆

事务消息解决「本地事务 + 发消息」的原子一致性(如扣款与发通知不能一个成一个败)。RocketMQ 用 「半消息 + 二次确认 + 事务回查」① 先发半消息(Broker 已存但消费者不可见)→ ② 执行本地事务③ 二次确认(成功发 COMMIT 让消息转正可见、失败发 ROLLBACK 删除)→ ④ 确认丢失时 Broker 回查生产者的本地事务状态(生产者实现回查接口、查业务表返回结果,重试多次)来最终决定。所有失败分支都收敛到「事务成⟺消息可见」,实现最终一致。注意消费端仍需幂等。核心:消息先占位不可见、事务定了再转正或删除、确认丢了靠回查兜底