基于本文回答

播面 播面

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

事务消息中的“回查机制”是如何工作的?如果回查一直失败,RocketMQ 会如何处理?

在 RocketMQ 的事务消息中,回查机制(Transaction Check Mechanism)是保证分布式事务最终一致性的核心保障兜底方案。它主要用于解决两阶段提交(2PC)中,因为网络闪断、生产者重启等原因导致 Broker 迟迟未收到二次确认(Commit 或 Rollback)的问题。

下面详细拆解回查机制的工作原理以及 RocketMQ 对持续失败的处理方式。


一、 回查机制是如何工作的?

事务消息的整体流程是:发送半消息(Half Message) -> 执行本地事务 -> 发送二次确认(Commit/Rollback)
当第三步发生异常(丢失或超时),回查机制就会介入。具体流程如下:

1. 触发回查的条件

当 Broker 接收到半消息(Half Message)后,会将消息存储在一个特殊的内部 Topic(RMQ_SYS_TRANS_HALF_TOPIC)中。如果经过了一段时间(默认是 1 分钟),Broker 仍然没有收到该消息的 Commit 或 Rollback 指令,就会主动向生产者发起回查请求

2. 回查的执行流程

  1. Broker 寻找客户端:Broker 会根据半消息所属的 ProducerGroup,在当前连接的生产者集群中随机挑选一台存活的 Producer 节点发起回查请求。(注意:不一定是最初发送消息的那台机器,因此本地事务状态必须是全局可查的,通常存在数据库中)。
  2. 执行回查逻辑:Producer 收到回查请求后,会触发开发者实现并注册的 TransactionListener 接口中的 checkLocalTransaction() 方法。
  3. 返回回查结果:开发者在 checkLocalTransaction() 方法中,通过检查数据库中本地事务的执行结果(例如查询业务流水表或事务状态表),向 Broker 返回以下三种状态之一:
    • COMMIT_MESSAGE:本地事务已成功。Broker 会将半消息转换为普通消息投递给消费者。
    • ROLLBACK_MESSAGE:本地事务已失败。Broker 会直接丢弃(删除)该半消息,消费者永远不会收到。
    • UNKNOWN:本地事务状态未知(可能还在执行中,或者数据库查询超时)。Broker 会保持半消息状态,等待下一次回查。

二、 如果回查一直失败,RocketMQ 会如何处理?

如果回查一直返回 UNKNOWN,或者因为网络/客户端原因回查请求一直超时失败,RocketMQ 不会无休止地进行回查,以防止内部积压大量无法决断的半消息耗尽资源。

RocketMQ 的具体处理策略如下:

1. 最大回查次数限制

RocketMQ 为事务回查设置了最大次数限制,由 Broker 端的配置参数 transactionCheckMax 控制,默认值是 15 次

2. 回查间隔

默认情况下,RocketMQ 的回查间隔是 1 分钟(早期版本受限于定时消息的精度,现在版本内部有单独的定时任务控制)。

3. 达到最大次数后的处理:默认丢弃(Rollback)

如果一个半消息的回查次数达到了 transactionCheckMax(默认 15 次),并且最后一次回查结果依然是 UNKNOWN 或失败:

  • RocketMQ 默认会将该半消息当作 ROLLBACK 处理
  • 也就是说,Broker 会直接丢弃该消息,消费者永远不会消费到这条消息。
  • 原因:如果一个本地事务经过了 15 分钟(15次)还无法确定结果,大概率是本地事务执行失败或发生了严重的系统异常,为了保护 MQ 系统自身的稳定性,选择丢弃是相对安全的兜底策略。

4. 可自定义丢弃策略(扩展点)

如果你不希望达到 15 次后直接丢弃,可以通过重写 Broker 的 AbstractTransactionalMessageCheckListener 类或修改 Broker 源码来改变这一默认行为,比如将其放入死信队列(DLQ)并触发人工告警。但在绝大多数业务场景下,默认的丢弃策略配合业务监控已经足够。


三、 生产环境的最佳实践(防坑指南)

为了配合好 RocketMQ 的回查机制,开发者在编写代码时必须注意以下几点:

  1. 引入“本地事务状态表”

    • 痛点:Broker 可能向 Producer 集群的任意一台机器发起回查,如果只靠内存状态是无法回查的。
    • 方案:在执行本地业务(如扣减库存)时,在同一个数据库事务中,向一张专门的local_transaction_log表写入一条记录(包含全局唯一的事务ID/消息ID)。
    • 回查逻辑checkLocalTransaction() 方法只需要去数据库 SELECT 这张表,如果有记录,返回 COMMIT;如果没有记录,返回 UNKNOWNROLLBACK
  2. 正确区分 ROLLBACKUNKNOWN

    • 在回查时,如果数据库查不到记录,不要立刻返回 ROLLBACK。因为可能是本地数据库事务还没 Commit(还在阻塞),而回查请求先到了。
    • 应该检查消息的生成时间,如果距离现在很短(比如几秒),应返回 UNKNOWN 让其稍后重试;如果距离现在已经很久(比如 10 分钟)依然查不到流水,才可以安全地返回 ROLLBACK
  3. 保证回查方法的幂等性和快速返回

    • checkLocalTransaction 方法不应该包含复杂的业务逻辑或耗时的远程调用,只应单纯地查询本地事务状态,否则会导致 Broker 回查线程池阻塞。
00:00
00:00