本地消息表实现分布式事务保证最终一致性
摘要
在微服务或分布式系统中,跨服务操作常常要求在数据库写入成功的同时,确保消息/事件也被可靠地发送给下游系统。两阶段提交(2PC)虽然能保证原子性,但代价高、可用性差。**Transactional Outbox / 本地消息表(local message table)**成为工程上常用的折衷:把要发布的事件写入同一事务的 outbox
表,然后异步将该表的数据可靠地投递到消息中间件或其他服务,从而实现最终一致性。(Home, martinfowler.com)
为什么需要本地消息表(问题陈述)
场景:订单系统在写入订单状态的同时,需要通知库存、账单、第三方等。如果直接在 DB 写入后再调用消息队列发布(两个独立操作),在第一步成功但第二步失败时会出现不一致;若使用 2PC,则需要分布式事务支持,增加复杂性与可用性风险。Outbox 模式通过“把事件写入数据库中的专用表”的方式,把写业务数据与写消息变成单个本地事务,保证不会出现“写业务成功但消息未发出”的状态。(Home)
模式概览(两种主流实现)
-
Polling Publisher(轮询发布)
-
业务事务:写业务表 + 写 outbox 表(在同一数据库事务里)。
-
后台进程/线程:周期性扫描 outbox 表、读取待发送行、把消息发给消息中间件,再标记为已发送或删除。
-
优点:实现简单、在任何支持事务的 DB 上都可用。示例实现大量出现在 Spring Boot 生态与开源文章中。(wimdeblauwe.com, IK.AM)
-
-
CDC(Change Data Capture)实现(Debezium / DB log tailing)
-
通过 Debezium 或类似工具订阅数据库的 binlog/事务日志,基于变更流直接把 outbox 表中的插入流化到 Kafka 等系统。
-
优点:避免轮询开销、延迟低、可扩展性好,适合高吞吐场景。(Debezium, Medium)
-
两者并非互斥:很多团队先用轮询实现快速上线,后期在需要时迁移到 CDC。Decodable、Confluent 等对这两种实现以及其权衡有深入讨论。(decodable.co, Medium)