分布式事务 Saga 模式怎么实现
更新时间:2026-09-18。跨服务长流程(下单=扣库存+扣余额+生成物流)无法用单机 ACID。Saga 把它拆成一串带补偿的本地事务,走最终一致。对比与选型见 Seata AT/TCC 与本地消息表,理论见 CAP 与 BASE。
一、Saga 的核心思想
一个大业务事务 T 被拆为有序的本地事务 T1, T2, …, Tn,每个 Ti 都有对应的补偿动作 Ci(语义上撤销 Ti 的影响)。
正向: T1 ──> T2 ──> T3 (都成功 → 整体完成)
任一步失败(如 T3 失败),逆序补偿:
补偿: C1 <── C2 <── (C3)- 每步都是本地 ACID 短事务,不持有跨服务锁,适合长流程。
- 不保证强一致,保证最终一致:要么全部完成,要么通过补偿回到(业务上的)初始状态。
二、两种驱动方式
| 方式 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 事件编排 Choreography | 各服务订阅事件、做完发下一事件 | 松耦合、无中心单点 | 流程隐含在订阅关系里,难追踪、易循环依赖 |
| 协调器 Orchestration | 中央编排器按状态机依次调用、失败触发补偿 | 流程显式、易监控/重放 | 多一个组件,编排逻辑集中 |
复杂业务、需要清晰可观测时多用协调器 + 状态机(如 Seata Saga、Camunda/工作流引擎):状态机里声明每个节点的 Do(正向)与 Compensate(补偿)、状态持久化、崩溃后可从断点重放。
三、补偿不是数据库回滚
补偿是业务级反向操作,往往不是“恢复原值”:
- 「扣库存」补偿为「加回库存」,但加回后库存数不等于绝对回滚(期间可能有其它交易)。
- 「发了短信/推送」无法真正撤回,补偿只能发一条更正通知或记账冲正。 因此 Saga 要求业务操作语义上可补偿;天然不可撤销的步骤(对外不可逆通知)尽量放在流程末尾。
四、必须处理的异常语义
分布式下网络会丢、会重,三个经典问题:
- 空补偿:原动作因网络问题实际没执行,补偿却先到/被调用。补偿要能识别「无对应正向记录」并正常返回成功(记补偿流水),不能报错或误加库存。
- 悬挂:补偿先到并执行了,迟到的正向请求之后才执行,导致「补偿完又被扣款」。正向执行前要检查该事务是否已补偿/已终态,若是则拒绝。
- 幂等:正向和补偿都可能被重试多次,必须靠全局事务 ID + 去重表/唯一键保证多次执行效果一致。
配套:每步带超时 + 重试 + 持久化状态,补偿本身失败要能不断重试(补偿队列/对账任务兜底),不能停在半程。
五、隔离性问题(最大短板)
Saga 全程没有全局锁,会出现:
- 脏读/脏写:T2 已提交、T3 失败后补偿 C2,期间别的事务读到了「将被撤销」的中间结果。
- 缓解手段:
- 语义隔离锁:业务层对资源加「处理中」标记(如订单冻结、库存预留而非真扣),提交才转正式、补偿则释放。
- 交换更新:把可能回滚的操作设计成预留/挂账,最后一步批量生效。
- 版本号/乐观锁防止覆盖更新。
六、与其他方案对比
| 方案 | 一致性 | 锁/侵入 | 适用 |
|---|---|---|---|
| 2PC/XA | 强一致 | 全局锁、性能差、阻塞 | 老系统、短事务 |
| Saga | 最终一致 | 无长锁、要写补偿 | 长流程、多步骤、可补偿 |
| TCC | 最终一致,较强隔离 | Try-Confirm-Cancel 三段、侵入大 | 资金等高一致、需预留资源 |
| 本地消息表/事务消息 | 最终一致 | 解耦、异步 | 上下游单向通知型 |
选型速记见分布式事务方案:可补偿的长编排用 Saga,需要资源预留和较强隔离用 TCC,单纯「做完可靠通知下游」用消息表。
七、落地清单
- 全局唯一
txId贯穿所有步骤与补偿,状态持久化(可重放)。 - 每个正向操作幂等、可补偿;补偿也幂等,并处理空补偿与悬挂。
- 不可逆步骤后置;用预留/冻结代替直接生效来补隔离。
- 编排器记录步骤状态,失败逆序补偿,补偿失败进重试/对账。
- 监控「长时间停在中间态」的事务并告警,必要时人工介入。
- 接口具备幂等设计,前端配合状态查询而非重复提交。
八、常见坑
- 把补偿当 DB 回滚:业务值已被其它事务改变,应做语义反向而非设旧值。
- 不处理空补偿/悬挂:重试与乱序下必然产生资损。
- 无全局状态、崩溃无法续跑:编排状态必须持久化。
- 忽略中间态可见性:没有预留/冻结导致用户看到并消费了将被撤销的资源。
- 补偿也可能失败却只试一次:需可靠重试 + 对账兜底。
- 同步 RPC 串长链:长流程应结合异步事件与超时,避免上游长时间阻塞。
总结
Saga 把无法单机 ACID 的跨服务长事务拆成一串本地短事务,每步配一个业务级补偿,全部成功则完成、任一步失败则逆序执行补偿达成最终一致,驱动上可用松耦合但流程隐蔽的事件编排或可监控、崩溃可重放的中央协调器状态机;关键认知是补偿不是数据库回滚而是语义反向操作(加回库存不等于恢复原值、发出去的通知无法真撤),且全程无全局锁、隔离性弱,要用资源预留/冻结和版本号缓解脏读;工程实现必须用全局事务 ID 贯穿并持久化状态、让正向与补偿都幂等、正确处理空补偿与悬挂、对补偿失败做可靠重试与对账,并把不可逆步骤后置——相比 TCC(资源预留、隔离强、侵入大)和本地消息表(单向可靠通知),Saga 最适合步骤多、可补偿、耗时长的业务编排。