文章背景图

分布式事务实战:Outbox、Saga、TCC、幂等与补偿

2026-08-16
0
-
- 分钟

# 分布式事务实战:Outbox、Saga、TCC、幂等与补偿

微服务把一次业务操作拆到了多个数据库:订单创建成功、库存扣减成功、支付却失败,系统就会进入不一致状态。分布式事务的目标不是让所有服务“像单库事务一样简单”,而是根据业务不变量选择正确的提交、重试和补偿方式。

## 一、先判断是否真的需要分布式事务

在引入复杂框架前,优先考虑:

1. 把必须原子完成的数据放回同一个服务和数据库;

2. 通过业务流程重新划分聚合边界;

3. 把非关键动作改为异步,如通知、积分和报表;

4. 接受短暂不一致,但规定最大收敛时间。

只有跨服务不变量无法通过边界调整解决时,再选择分布式事务方案。

## 二、两阶段提交为什么不是默认答案

两阶段提交(2PC)由协调者组织 Prepare 和 Commit。它能提供较强的一致性,但参与者需要长时间持有资源;协调者或网络故障时,事务可能阻塞。跨地域、高并发业务通常难以接受它的延迟和可用性代价。

2PC 更适合参与者和基础设施都明确支持、规模受控且强一致优先的场景,而不是所有微服务的通用方案。

## 三、Transactional Outbox:最常用的可靠事件方案

典型流程:

1. 订单服务在同一个本地事务中写入订单表和 outbox_event 表;

2. 独立发布器轮询 Outbox,或通过 CDC 读取数据库变更;

3. 发布器把事件写入消息队列;

4. 消费者处理事件并记录幂等结果;

5. 发布成功的 Outbox 记录被标记或归档;

6. 定时任务扫描超时事件并告警、重试。

关键点是“业务数据与待发送事件同事务提交”。消息中间件即使短暂不可用,事件仍在数据库中,不会出现业务成功但消息永久丢失。Outbox 通常保证至少一次投递,因此消费者必须幂等,不能假设消息只到一次。

Outbox 表至少应包含事件 ID、聚合 ID、事件类型、载荷、状态、重试次数、下次重试时间、创建和发布时间。对大表要按时间归档并监控未发布事件数量。

## 四、Saga:长事务的正向步骤与补偿步骤

Saga 把长事务拆成多个本地事务:

- 创建订单 → 补偿:取消订单;

- 预占库存 → 补偿:释放库存;

- 冻结余额 → 补偿:解冻余额;

- 创建发货单 → 补偿:关闭发货单。

两种组织方式:

- 编排式:由一个流程编排器决定下一步,状态清晰,适合复杂流程;

- 事件协同式:服务通过事件推进流程,耦合低,但链路难以理解和排查。

补偿不等于数据库回滚。邮件无法“撤回”,已发货商品也不能简单删除记录。补偿必须符合业务语义,例如退款、冲正、创建反向流水,并保留审计记录。

## 五、TCC:业务级资源预留

TCC 包含:

- Try:检查并预留资源;

- Confirm:确认使用预留资源;

- Cancel:释放预留资源。

例如库存 Try 把可用数量转为冻结数量,Confirm 扣减冻结数量,Cancel 归还冻结数量。

生产实现必须处理三类异常:

1. 空回滚:Try 未成功或请求未到达,Cancel 却先到;

2. 幂等:Confirm 或 Cancel 可能被重复调用;

3. 悬挂:Cancel 已执行后,迟到的 Try 又成功。

因此每个分支事务都要保存全局事务号、分支号和状态,并使用状态机拒绝非法迁移。TCC 侵入业务较深,适合资金、库存等对资源预留有明确模型的核心链路。

## 六、幂等才是重试的前提

建议为每个写请求携带业务幂等键,例如 payment_request_id。服务端可以组合使用:

- 数据库唯一约束;

- 幂等请求表;

- 业务状态机;

- 乐观锁版本号;

- 已处理消息表。

消费端可在同一事务中先插入 processed_message(message_id),唯一键冲突就说明已经处理,直接返回成功;插入成功后再执行业务更新并提交。不要使用“先查询、后插入”作为唯一防线,并发请求可能同时查询不到。最终仍需唯一约束或原子条件更新兜底。

## 七、重试、死信与对账

可靠链路应设置:

- 指数退避和随机抖动;

- 最大重试次数和总时间;

- 可重试错误与永久错误分类;

- 死信队列和人工处理入口;

- 业务对账任务;

- 可按事件 ID 回放;

- 全链路追踪和状态查询。

对账不是失败后的临时脚本,而是最终一致性系统的一部分。对账应比较业务事实,例如支付平台成功流水与本地订单状态,而不是只比较消息是否发送。

## 八、方案选择建议

- 同库内操作:优先本地事务;

- 业务成功后可靠发事件:Outbox + CDC/轮询;

- 多步骤、执行时间长、允许补偿:Saga;

- 核心资源必须预留:TCC;

- 基础设施受控且必须强一致:谨慎评估 2PC。

## 九、上线检查清单

1. 每一步的业务不变量是什么?

2. 请求和消息是否都有全局唯一 ID?

3. 所有重试操作是否幂等?

4. 补偿失败是否还能继续重试?

5. 状态机是否拒绝逆序和非法操作?

6. 消息乱序、重复、延迟和丢失是否演练?

7. 能否从业务 ID 查到完整执行轨迹?

8. 是否有对账、回放和人工修复工具?

分布式事务真正的难点不在“调用哪个框架”,而在于把状态、失败和恢复设计成业务的一部分。做到可重试、可补偿、可对账和可审计,系统才具备生产可用性。

原创

分布式事务实战:Outbox、Saga、TCC、幂等与补偿

本文链接: 分布式事务实战:Outbox、Saga、TCC、幂等与补偿

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录