基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

讲讲RocketMQ 实现事务消息的二阶段提交流程(Half Message 机制)

RocketMQ 的事务消息是为了解决分布式系统中的最终一致性问题而设计的。它完美解决了“执行本地数据库事务”与“发送消息”这两个操作之间的一致性问题(即:不能出现本地事务成功但消息发送失败,或者本地事务回滚但消息发出去被消费了的情况)。

RocketMQ 采用了二阶段提交(2PC)的思想,并结合了Half Message(半消息)定时回查(Transaction Check)机制来实现。

以下是该机制的详细原理解析:


一、 核心概念:什么是 Half Message(半消息)?

半消息是指:生产者已经成功发送到 Broker,Broker 也已经接收并存储下来的消息。但是,在生产者确认提交(Commit)之前,这条消息对消费者是“不可见”的,消费者无法拉取到这条消息。


二、 正常情况下的二阶段提交流程

在没有网络故障或应用崩溃的理想情况下,流程分为两个阶段:

阶段一:准备阶段(Prepare)

  1. 发送半消息:Producer 向 RocketMQ Broker 发送一条事务消息(半消息)。
  2. Broker 响应:Broker 将半消息持久化成功后,向 Producer 返回 Ack,确认半消息接收成功。
    • 设计用意:这一步证明了 MQ 服务正常,网络畅通,可以开始执行本地事务了。

阶段二:提交/回滚阶段(Commit/Rollback)

  1. 执行本地事务:Producer 收到 Broker 的 Ack 后,开始执行本地业务逻辑(例如:操作数据库,扣减库存,生成订单等)。
  2. 提交二次确认:Producer 根据本地事务的执行结果,向 Broker 发送二次确认(Commit 或 Rollback):
    • 如果本地事务执行成功:发送 Commit 指令。Broker 收到后,会将半消息标记为“可投递”,此时 Consumer 就能消费到这条消息了。
    • 如果本地事务执行失败:发送 Rollback 指令。Broker 收到后,会将该半消息丢弃/标记废弃,Consumer 永远不会收到这条消息。

三、 异常情况下的“事务回查”(Transaction Check)

问题来了:如果步骤 4 中,Producer 在发送 Commit/Rollback 之前宕机了,或者因为网络抖动导致 Broker 没有收到二次确认指令,怎么办?此时 Broker 里就会堆积一些处于“未知状态”的半消息。

为了解决这个问题,RocketMQ 引入了事务回查机制

  1. Broker 发起回查:Broker 内部有一个后台线程,会定时扫描那些长时间没有收到 Commit/Rollback 的半消息。如果发现,它会主动向 Producer 集群中的任意一个存活节点发起回查请求。
  2. Producer 检查本地事务状态:Producer 收到回查请求后,会执行开发者事先编写好的回查逻辑checkLocalTransaction 方法)。通常的做法是去数据库中查询一下那个业务的流水号是否存在、状态是否成功。
  3. Producer 再次发送确认:Producer 根据查询到的本地事务状态,再次向 Broker 发送 Commit 或 Rollback 指令。
  4. Broker 收到指令后,执行步骤 4 的逻辑。

注:如果回查后状态依然是 Unknown(未知),Broker 会在一段时间后再次回查。默认最多回查 15 次(可配置),如果 15 次后依然无法确认,Broker 将默认丢弃该消息(Rollback)。


四、 底层实现原理(Broker 是如何“隐藏”半消息的?)

RocketMQ 的高明之处在于,它并没有为了事务消息去大改底层的存储结构,而是巧妙地利用了Topic 替换机制:

  1. 替换 Topic(隐藏)
    当 Broker 收到半消息时,并不会把它写到用户指定的真实 Topic(如 OrderTopic)中,而是将其 Topic 修改为系统内部的 RMQ_SYS_TRANS_HALF_TOPIC。因为消费者没有订阅这个系统 Topic,所以消费者根本看不见它。
  2. 记录操作日志(OP Topic)
    为了跟踪半消息的状态,RocketMQ 还有一个内部 Topic 叫 RMQ_SYS_TRANS_OP_HALF_TOPIC。当半消息被 Commit 或 Rollback 时,Broker 会在这个 OP Topic 中写入一条记录,表示这个半消息已经处理过了。
  3. 回查的判断逻辑
    Broker 后台线程会对比 HALF_TOPICOP_TOPIC。如果发现 HALF_TOPIC 里有一条消息,但在 OP_TOPIC 里没有找到对应的操作记录,并且超时了,就会触发回查。
  4. 恢复 Topic(投递)
    当收到 Commit 指令时,Broker 会将消息的 Topic 恢复成最初的真实 Topic(OrderTopic),并重新写入 CommitLog。此时,消息对 Consumer 真正可见。

五、 总结与最佳实践

RocketMQ 事务消息的优势:

  • 实现了本地事务与消息发送的原子性。
  • 即使 Producer 宕机,依靠 Broker 的回查机制依然能保证最终一致性。
  • 对业务代码侵入性相对较小(只需实现执行本地事务和回查本地事务两个接口)。

开发中的注意点(最佳实践):

  1. 必须实现回查接口:业务方必须实现 TransactionListener 接口中的 checkLocalTransaction 方法,否则遇到网络异常会导致消息丢失或阻塞。
  2. 回查接口要保证幂等并快速返回:回查操作可能会被触发多次,只需简单查询数据库状态即可,不要在回查里做复杂的业务逻辑。
  3. 消费端必须保证幂等性:事务消息只能保证 Producer 端发送和本地事务的一致性,Consumer 端在消费时依然可能因为网络等原因发生重复投递,所以消费者接口必须是幂等的
00:00
00:00