核心概念:事务消息、半消息、消息回查、事务状态表
好,咱们正式开始啃这块硬骨头。很多同学第一次接触分布式事务,都会被一堆术语搞晕。什么半消息、回查、状态表……说白了,它们就是一套组合拳,用来解决一个核心问题:在分布式系统里,怎么保证本地数据库操作和消息发送要么一起成功,要么一起失败。
我个人习惯把这四个概念串成一条线来理解。你想想看,一个事务消息从诞生到结束,其实就是一个「试探 → 确认 → 兜底」的过程。咱们一个一个拆开讲。
2.1 事务消息
事务消息,不是普通的消息。它是 RocketMQ 这类消息中间件提供的一种特殊消息类型。普通消息你发出去,Broker 就存下来了,消费者就能消费了。但事务消息不一样——它允许你在发送消息之后,根据本地事务的执行结果,再决定这条消息是「让消费者看见」还是「直接丢弃」。
我在项目中遇到过好几次这样的场景:用户下单后,需要发一条消息给积分系统。如果订单没创建成功,积分消息却发出去了,那用户就白嫖了积分。这就是典型的需要事务消息的场景。
核心要点:事务消息的本质,是把「消息发送」和「本地事务」绑定在同一个分布式事务里。要么都成,要么都败。
2.2 半消息
半消息(Half Message)是事务消息的「中间态」。你可以把它理解成一条被「冻结」的消息。
流程是这样的:
- 生产者先发送一条半消息到 Broker。
- Broker 收到后,把消息存下来,但标记为「不可投递」状态。
- 消费者此时是看不到这条消息的。
- 生产者接着执行本地事务(比如插入订单数据)。
- 根据本地事务结果,生产者再向 Broker 发送 Commit 或 Rollback 指令。
- 如果是 Commit,Broker 把半消息标记为「可投递」,消费者就能消费了。
- 如果是 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 四者关系:一张图说清楚
这四个概念不是孤立的。它们共同构成了一个完整的事务消息生命周期。我画了一张图,帮你理清它们之间的关系:
从这张图你可以看到,整个流程是环环相扣的:
- 半消息是起点,它让消息先「冻结」起来。
- 本地事务 + 事务状态表是决策依据,决定消息的最终命运。
- 消息回查是兜底方案,确保即使生产者挂了,消息也不会永远卡在半消息状态。
我的建议:在设计事务状态表时,一定要加上事务ID和消息Key的联合唯一索引。这样既能保证幂等,又能快速响应回查请求。
好了,这四个核心概念就讲到这里。你只要记住一句话:事务消息是目标,半消息是手段,消息回查是保障,事务状态表是根基。把这四个概念吃透了,后面讲具体实现和调试的时候,你就能游刃有余了。