事务回查涉及多个时间参数,合理配置这些参数是事务消息稳定运行的关键:
参数 默认值 说明 配置位置
transactionTimeout 6000ms 本地事务执行超时时间。超过这个时间,Broker 认为事务状态未知,触发回查 Producer 端
transactionCheckInterval 60000ms Broker 回查半消息的间隔 Broker 端
transactionCheckMax 15 次 最大回查次数。超过后默认回滚 Broker 端
Broker 端配置
transactionCheckInterval=60000 # 回查间隔 60 秒
transactionCheckMax=15 # 最大回查 15 次
transactionCheckTimeout=6000 # 回查请求超时时间 6 秒
Producer 端配置
DefaultMQProducer producer = new DefaultMQProducer();
producer.setTransactionTimeout(6000); // 事务超时 6 秒
配置建议:
transactionTimeout 要根据业务调整:如果本地事务涉及多个数据库操作或远程调用,需要设置足够的超时时间
transactionCheckInterval 不宜过短:频繁回查会增加 Broker 和 Producer 的负载
transactionCheckMax 要结合业务容忍度:核心业务可以设置更大的值(如 30 次),给系统更长的恢复时间
事务消息的使用限制与注意事项
事务消息虽强,但并非万能。在使用时,有几条重要的限制需要牢记:
限制一:不支持延迟消息和批量消息
事务消息不能和延迟消息、批量消息组合使用。事务消息本身就是“先存后发”的机制,和延迟消息的“定时投递”在逻辑上冲突。
限制二:Consumer 必须处理重复消费
事务消息虽然保证了“事务提交后消息可见”,但在异常情况下(如网络重传、Rebalance),Consumer 仍然可能重复消费消息。业务方必须做好幂等处理。
限制三:事务消息的发送是同步的
事务消息只支持同步发送,不支持异步或单向发送。因为需要等待本地事务执行结果,同步是最自然的模式。
限制四:事务消息不支持广播消费
广播消费模式下,每个 Consumer 独立消费,事务消息的状态管理会变得复杂,所以 RocketMQ 的事务消息不支持广播模式。
限制五:半消息对 Broker 有存储开销
每条半消息在 COMMIT 或 ROLLBACK 之前,都会占用 Broker 的存储空间(CommitLog 和 ConsumeQueue)。如果有大量半消息长时间 UNKNOWN,会导致存储膨胀。
使用限制
❌ 不支持延迟消息
❌ 不支持批量消息
❌ 不支持异步/单向发送
❌ 不支持广播消费
⚠️ 半消息占用存储空间
事务消息回查失败的处理策略
回查不是万能的,它也可能失败。我们需要理解各种失败场景及应对策略:
场景一:Producer 节点宕机
如果所有 Producer 节点都宕机了,Broker 的回查请求无法送达。
→ 策略:配置 transactionCheckMax 和 transactionCheckInterval,在 Producer 恢复前,Broker 会持续尝试回查。如果超过最大次数仍无响应,Broker 默认回滚。运维层面需要监控“超时未确认”的半消息数量。
场景二:回查请求超时
Producer 收到了回查请求,但因为网络或负载原因,响应超时。
→ 策略:Broker 会重试回查(下一次扫描时再次发起)。Producer 端应确保 checkLocalTransaction() 方法的执行时间远小于 transactionCheckTimeout。
场景三:checkLocalTransaction 返回 UNKNOWN
Producer 仍然无法确定事务状态(比如依赖的数据库暂时不可用)。
→ 策略:返回 UNKNOWN 后,Broker 会在下一个回查周期再次询问。建议在业务层实现重试机制,直到能确定状态为止。
场景四:超过最大回查次数
15 次回查都失败了,消息仍然 UNKNOWN。
→ 策略:Broker 默认回滚消息(从 5.x 开始,可以通过配置修改默认行为)。运维需要监控这类情况,手动介入处理。
Producer 宕机
响应超时
返回 UNKNOWN
超过最大次数
回查失败
失败类型
等待 Producer 恢复
Broker 持续重试
超限后默认回滚
优化网络
或缩短 checkLocalTransaction 耗时
优化业务检查逻辑
确保能快速确定状态
监控告警
人工介入排查
Done
事务消息与分布式事务的对比(TCC、Saga)
事务消息只是分布式事务的一种解决方案。我们来对比一下主流的几种方案:
TCC(Try-Confirm-Cancel)
维度 TCC 事务消息
核心思想 预留资源 → 确认/取消 半消息 → 提交/回滚
业务侵入 高(需要实现 Try/Confirm/Cancel) 中(只需实现本地事务 + 回查)
数据一致性 强一致性 最终一致性
适用场景 跨服务调用、资源预留 消息驱动的异步场景
性能 较低(多次 RPC) 较高(一次半消息 + 本地事务)
Saga
维度 Saga 事务消息
核心思想 长事务拆分为多个本地事务 + 补偿 本地事务 + 消息最终一致性
业务侵入 高(需要实现正向操作 + 补偿操作) 中
数据一致性 最终一致性 最终一致性
适用场景 长事务、跨多个服务的复杂流程 消息驱动的异步解耦场景
对比总结:
选型建议
分布式事务方案对比
TCC
强一致性
业务侵入高
Saga
最终一致性
需要补偿
事务消息
最终一致性
通过回查自愈
🔵 事务消息:
消息驱动、异步解耦、
对一致性要求不是强同步
🟠 TCC:
跨服务调用、需要强一致性、
资源可预留
🟢 Saga:
长事务、复杂流程、
需要有补偿逻辑
选型建议:
事务消息适合:消息驱动的异步场景,对性能要求高,能接受最终一致性
TCC 适合:跨服务调用的强一致性场景,资源可以预留(如扣减库存、冻结资金)
Saga 适合:复杂的长事务流程,每个步骤都有明确的补偿操作
事务消息与普通消息的性能对比
事务消息因为多了“半消息 + 本地事务 + 可能的回查”这些步骤,性能自然不如普通消息。我们来量化对比一下:
对比维度 普通消息 事务消息 性能差距
网络往返 1 次(发送) 2-3 次(发送半消息 + 提交/回滚 + 可能的回查) 约 2 倍延迟
Broker 写入 1 次 CommitLog 写入 2 次 CommitLog 写入(半消息 + 提交标记) 约 2 倍存储 IO
存储空间 消息体 消息体 + 事务状态记录 约额外 30%-50%
CPU 开销 低 中(需要事务状态管理和扫描) 约 20%-30% 额外
TPS 上限 ~10 万级 ~2-3 万级 约为普通消息的 1/3 - 1/5
事务消息发送
发送半消息
写入半消息
返回 ACK
执行本地事务
发送 COMMIT/ROLLBACK
标记消息状态
返回 ACK
2 次网络往返 + 2 次写入
- 本地事务 + 可能的回查
普通消息发送
发送请求
写入 CommitLog
返回 ACK
1 次网络往返 + 1 次写入
性能损耗的核心来源:
半消息额外写入:每条事务消息至少多一次 CommitLog 写入
事务状态维护:Broker 需要维护和扫描半消息的状态
网络往返增加:从 1 次变成 2 次(半消息 + 提交/回滚)
回查开销:如果返回 UNKNOWN,还有额外的回查负载
💡 小贴士:如果你的业务不需要强一致性保障,普通消息 + 幂等消费可能是更好的选择。事务消息虽好,但不要滥用——它只应该在“本地事务和消息发送必须保持一致”的场景下使用。
事务消息的内部存储结构
了解事务消息的存储细节,能帮你更深入地理解它的工作原理:
事务消息存储结构
回滚后
标记为已删除
不影响 CommitLog
消息不可消费
提交后
在原 ConsumeQueue 位置
标记为可见
可能增加 Op 消息
记录提交操作