Skip to content

说一下 RocketMQ 中关于事务消息的实现? ​

  • 标签:RocketMQ、后端、消息队列

🏛 业务架构师 ​

RocketMQ 事务消息解决"本地事务和发消息的原子性",我用下单送积分讲流程:

  1. 发半消息(half message):生产者先发一条对消费者不可见的消息到 broker;
  2. 执行本地事务:落订单库。这一步是业务真实写入;
  3. 提交或回滚:本地事务成功则 commit,半消息可见、消费者扣发积分;失败则 rollback;
  4. 回查兜底:生产者宕机没来得及 commit/rollback,broker 定时回查 checkLocalTransaction,由业务代码根据订单表状态给出结论。

设计精髓:用"先把消息藏起来"换取本地事务与消息的最终一致,代价是业务必须实现回查逻辑(回查要看订单表状态幂等判断)。

业务评估上我的排序:这类最终一致方案比 2PC/TCC 便宜一个量级,"下单成功"和"积分到账"允许秒级延迟,绝大多数交易链路够用。真正的成本在于:回查逻辑要写得对(以本地库为准)、消费端要幂等(消息可能重投)、还要有对账兜底。

落地建议:新系统优先考虑"本地消息表 + 定时扫描"或 RocketMQ 事务消息二选一——前者逻辑直白易排查,后者省了消息表和扫描任务,但对团队 MQ 熟悉度要求高。按团队能力选可运维的方案,而不是最先进的方案。

🏗 系统架构师 ​

RocketMQ 事务消息解决"本地事务与发消息的原子性"(半消息机制),我按流程讲:

  1. 发送半消息(half message):生产者先发一条对消费者不可见的消息(存在内部 topic RMQ_SYS_TRANS_HALF_TOPIC),broker 返回成功;
  2. 执行本地事务:生产者回调执行 DB 操作,返回 commit / rollback / unknown;
  3. 提交或回滚:commit 后消息转入真实 topic 对消费者可见;rollback 则删除(记入 OP 队列);
  4. 回查兜底:本地事务返回 unknown 或生产者掉线时,broker 默认 60 秒后定时回查生产者的本地事务状态,最多回查 15 次,避免事务状态悬空。

关键设计:半消息先落盘,把"先操作 DB 还是先发消息"的两难拆成"半消息(可回滚)+ 本地事务 + 回查(最终确认)",本质是最终一致而非强一致。

生产要点:回查接口必须幂等且能根据业务状态推导出结果(查 DB 里订单是否已创建);消费端仍要幂等兜底;对一致性窗口敏感的业务(支付扣款)不建议用,走本地消息表 + 对账更可控。

💻 全栈开发专家 ​

RocketMQ 事务消息解决"本地事务和发消息的原子性",用的是半消息 + 回查:

text
① 发送 half 消息(对消费者不可见,存于内部 topic)
② 执行本地事务(下单落库)
③ 本地事务成功 → commit,消息转正式 topic 投递
   本地事务失败 → rollback,半消息删除
④ 如果 broker 没收到 ③(生产者宕机),定时回查本地事务状态
java
TransactionMQProducer producer = new TransactionMQProducer("pg");
producer.setTransactionListener(new TransactionListener() {
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        orderService.create(...);              // 本地事务
        return LocalTransactionState.COMMIT_MESSAGE;
    }
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 回查:按订单号查库,存在→COMMIT,不存在→ROLLBACK
        return orderService.exists(msg.getKeys())
            ? LocalTransactionState.COMMIT_MESSAGE
            : LocalTransactionState.ROLLBACK_MESSAGE;
    }
});

踩过的坑:回查默认 15 次(transactionTime=6s 起步),间隔超限后半消息直接删除——回查逻辑必须幂等且查可靠表,别查缓存;本地事务里别再发别的 MQ,嵌套事务难排查;commit 丢了靠回查补偿,所以本地事务表要有状态字段能被查出来。

它保证的是"本地事务成功则消息必达"的最终一致,不是分布式强一致。