核心概念:事务消息、半消息、消息回查、事务状态表

好,咱们正式开始啃这块硬骨头。很多同学第一次接触分布式事务,都会被一堆术语搞晕。什么半消息、回查、状态表……说白了,它们就是一套组合拳,用来解决一个核心问题:在分布式系统里,怎么保证本地数据库操作和消息发送要么一起成功,要么一起失败。

我个人习惯把这四个概念串成一条线来理解。你想想看,一个事务消息从诞生到结束,其实就是一个「试探 → 确认 → 兜底」的过程。咱们一个一个拆开讲。

2.1 事务消息

事务消息,不是普通的消息。它是 RocketMQ 这类消息中间件提供的一种特殊消息类型。普通消息你发出去,Broker 就存下来了,消费者就能消费了。但事务消息不一样——它允许你在发送消息之后,根据本地事务的执行结果,再决定这条消息是「让消费者看见」还是「直接丢弃」。

我在项目中遇到过好几次这样的场景:用户下单后,需要发一条消息给积分系统。如果订单没创建成功,积分消息却发出去了,那用户就白嫖了积分。这就是典型的需要事务消息的场景。

核心要点:事务消息的本质,是把「消息发送」和「本地事务」绑定在同一个分布式事务里。要么都成,要么都败。

2.2 半消息

半消息(Half Message)是事务消息的「中间态」。你可以把它理解成一条被「冻结」的消息。

流程是这样的:

  1. 生产者先发送一条半消息到 Broker。
  2. Broker 收到后,把消息存下来,但标记为「不可投递」状态。
  3. 消费者此时是看不到这条消息的。
  4. 生产者接着执行本地事务(比如插入订单数据)。
  5. 根据本地事务结果,生产者再向 Broker 发送 Commit 或 Rollback 指令。
  6. 如果是 Commit,Broker 把半消息标记为「可投递」,消费者就能消费了。
  7. 如果是 Rollback,Broker 直接删除这条半消息。

嗯,这里要注意:半消息的存在,就是为了防止「本地事务还没执行完,消息就被消费了」这种尴尬情况。

我的经验:半消息在 Broker 端是持久化存储的,所以即使生产者挂了,半消息也不会丢。这为后面的「消息回查」提供了基础。

2.3 消息回查

消息回查(Message Checkback)是这套机制里最巧妙的设计。为什么需要它?

你想啊,生产者发送半消息后,开始执行本地事务。但如果这时候生产者突然宕机了,或者网络断了,Broker 就永远等不到 Commit 或 Rollback 指令了。那条半消息就会一直卡在那里,变成「僵尸消息」。

消息回查就是用来解决这个问题的。Broker 会定期扫描那些长时间未确认的半消息,然后主动回调生产者的接口,询问:「嘿,你那条半消息对应的本地事务,到底成没成?」

生产者收到回查请求后,需要查询本地事务的状态,然后告诉 Broker 是 Commit 还是 Rollback。

我曾经踩过的坑:消息回查接口必须是幂等的。因为 Broker 可能会多次回查同一条消息。如果你的回查接口有副作用,比如重复扣库存,那就出大事了。

2.4 事务状态表

说到回查,就不得不提事务状态表。这是生产者在本地数据库中维护的一张表,用来记录每条事务消息的执行状态。

我建议你在设计时,这张表至少包含以下字段:

字段名 类型 说明
transaction_id VARCHAR(64) 全局唯一的事务ID,与消息ID对应
message_key VARCHAR(128) 消息的业务Key,用于去重
status TINYINT 0=待提交, 1=已提交, 2=已回滚
create_time DATETIME 记录创建时间
update_time DATETIME 最后更新时间

这张表的作用很明确:当 Broker 发起回查时,生产者只需要查一下这张表,就能知道本地事务的状态,然后返回给 Broker。

关键设计原则:事务状态表的写入,必须和本地业务操作在同一个数据库事务里。这样才能保证「业务操作成功 = 状态表记录为已提交」,不会出现数据不一致。

2.5 四者关系:一张图说清楚

这四个概念不是孤立的。它们共同构成了一个完整的事务消息生命周期。我画了一张图,帮你理清它们之间的关系:

事务消息核心概念关系图 生产者 Broker 消费者 本地事务 事务状态表 半消息 消息回查 ① 发送半消息 ② 执行本地事务 ③ 记录状态 ④ Commit/Rollback ⑤ 更新状态 ⑥ 投递消息 ⑦ 超时触发回查 ⑧ 查询状态 ⑨ 返回结果 半消息:不可投递的中间态 回查:兜底机制,防止消息卡死

从这张图你可以看到,整个流程是环环相扣的:

  • 半消息是起点,它让消息先「冻结」起来。
  • 本地事务 + 事务状态表是决策依据,决定消息的最终命运。
  • 消息回查是兜底方案,确保即使生产者挂了,消息也不会永远卡在半消息状态。

我的建议:在设计事务状态表时,一定要加上事务ID和消息Key的联合唯一索引。这样既能保证幂等,又能快速响应回查请求。

好了,这四个核心概念就讲到这里。你只要记住一句话:事务消息是目标,半消息是手段,消息回查是保障,事务状态表是根基。把这四个概念吃透了,后面讲具体实现和调试的时候,你就能游刃有余了。