Skip to content

分布式事务:2PC、Saga 与事务性 Outbox 的选择 ​

修订于 2026-10-12:原文的“双十一事故”、作者十年经验和生产项目数据没有可核查材料,已删除。

一个业务操作跨多个服务时,单个数据库事务通常不能直接覆盖全部资源。设计前先写清楚业务需要的保证:哪些状态必须同时生效,哪些状态允许短暂不一致,失败后能否补偿,用户何时看到最终结果。没有一种模式适合所有流程。

三类常见方案 ​

方案解决的问题主要代价
两阶段提交(2PC)参与资源支持协调协议时,协调跨资源提交。协调器与资源的可用性、锁持有和恢复复杂度。不能简单说“生产中绝不用”。
Saga把长流程拆成多个本地事务,失败时执行后续恢复或补偿。补偿不一定能把现实世界完全“回滚”;中间状态对外可见。
事务性 Outbox在同一数据库事务中写业务数据和待发布事件,再异步发布。发布器、重复投递、事件顺序和积压监控仍需设计。

Saga 可以采用编排:协调器明确控制流程;也可以采用事件协作:各参与方响应事件。前者便于看全局流程,但协调器需高可用;后者减少集中控制,但参与者多时依赖关系难追踪。选择要看流程长度、参与方数量和故障恢复方式。

订单示例:先写失败路径 ​

假设“下单 → 预留库存 → 支付”是一个跨服务流程。支付失败后,可能需要释放库存;但若支付请求超时,不能仅凭超时断定未扣款。应先查询支付状态或使用幂等操作,再决定补偿。退款也不等于“撤销付款”,它是新的业务操作,可能产生费用、延迟和用户通知。

对每一步至少定义:

  1. 请求和结果的持久标识;
  2. 重试是否安全、幂等键如何保存;
  3. 正向动作、补偿动作及其失败后的恢复方式;
  4. 超时后的“未知状态”如何查询;
  5. 用户可见的中间状态和最终一致时间目标。

Outbox 与重复投递 ​

如果服务先提交数据库,再发送消息,发送失败会留下“数据已变、下游未收到”状态。事务性 Outbox 将业务变更和待发布记录放在同一个本地事务中,再由发布器读取并发送。这解决本地数据库与事件记录的双写原子性问题,但发布器故障、确认丢失仍可能导致重发。因此消费者必须按事件 ID 或业务 ID 去重。

验证方法 ​

在测试环境分别注入数据库提交失败、事件发布失败、消费者崩溃、网络超时和重复消息。检查最终状态、补偿记录、死信处理及人工介入路径。监控长时间未完成的流程,并为每个状态设置可查询的原因与下一步操作。

参考资料 ​

梧桐和风 · 让复杂的技术讲得更清楚