用途
当业务事务跨越多个服务或多个聚合,并且它们无法共享单个事务时,使用此技能。适用于以下情况:
- 一个业务流程横跨多个服务或多个聚合,且无法共享一个事务。
- 某些步骤是外部的(第三方 API、支付处理器),无法参与事务。
- 长时运行的工作流,持有分布式锁不现实。
- 业务对“如果步骤 X 失败,就运行步骤 X' 来撤销”有明确概念。
不适用的情况
- 一切都在单个服务或单个聚合内完成——应使用本地数据库事务。
- 各步骤是幂等且与顺序无关的——可能根本不需要 Saga 的协调。
- 确实需要“原子”回滚语义(在分布式系统中罕见)——Saga 不提供这种语义;它提供的是带补偿的最终一致性。
主要能力
两种风格,不同的协调形态
编排式 Saga。 每个服务监听事件并决定何时行动。服务 A 完成步骤 1 后发出 Step1Completed;服务 B 接收后完成步骤 2 并发出 Step2Completed,依此类推。补偿同样以事件形式发出(Step1Compensated)。
- 优点:去中心化、无单点故障、可横向扩展。
- 缺点:整个工作流是隐式的——无人拥有它;调试卡住的 Saga 很困难;新增一个步骤会触及多个服务。
协同式 Saga(流程管理器)。 由中央协调器驱动工作流。它依次向每个服务发送命令,监听它们的响应,并决定下一步(继续前进或执行补偿)。
- 优点:工作流显式、集中在一处,易于推理和测试。
- 缺点:协调器是一个必须高度可用的协调组件;集中化是实实在在的成本。
补偿动作,而非回滚
回滚(数据库事务)会把状态恢复到好像什么都没发生。补偿动作则撤销业务效果。退款是一种补偿;如果支付已经完成,回滚支付是不存在的。
- 补偿必须是幂等的,并且在部分完成后调用也安全。
- 有些动作无法补偿(发送了邮件、调用了外部不可逆的 API)。应把这类步骤设计在最后,这样 Saga 中更早的失败永远不会触及它们。
幂等性与至少一次投递
Saga 生活在事件驱动的世界里。消息可能被重新投递。每个步骤和每个补偿都必须是幂等的。
Saga 状态
协调器(或在编排式中,分散在各服务中)追踪每个 Saga 实例所处的位置。需要持久化这一状态——Saga 崩溃会发生,你需要能够恢复。
使用方式(决策启发式)
- ≤3 步且参与者稳定 → 编排式。
- 4+ 步,或以后可能新增步骤,或补偿逻辑不平凡 → 协同式。
- 对步骤排序,使不可逆的步骤排在最后。
- 从第一天起就把幂等性构建进每个步骤。
- 持久化协调器状态;重启时重新加载。
- 当复杂度需要时,使用工作流引擎(Temporal、Camunda、Conductor)而非自己手写协调器——但要审慎使用;它们是实实在在的依赖。
反模式
- 名为“Saga”实为链式同步 RPC。服务 A 调用 B,B 调用 C,没有事件代理。那是分布式调用链,不是 Saga;失败不会补偿,只会向上传播。
- 并不是真正补偿的“补偿动作”。把记录标记为“已取消”而不是给用户退款。只撤销一半。
- 非幂等的步骤。同一消息被重新投递 → 步骤运行两次 → 用户被扣款两次。
- 不可逆步骤放在中间。步骤 3 发送了一封无法撤回的邮件;步骤 4-5 失败;你无法撤销那封邮件。应把它放在最后。
- 20 步的 Saga。那大概率应该是工作流引擎,而不是手写的 Saga。
适用场景
分布式系统中的跨服务业务事务,尤其是微服务架构下“如何跨服务进行事务”的标准答案。事件驱动架构、事件溯源常与 Saga 配合使用。
限制与相关技能
- 不提供原子回滚,只提供带补偿的最终一致性。
- 相关:arch-event-driven(Saga 事件流经代理)、arch-microservices(Saga 是跨服务事务的标准答案)、arch-event-sourcing(Saga 状态常被事件溯源)、arch-circuit-breaker(调用其他服务的 Saga 步骤应自我保护)、ddd-domain-event(Saga 步骤产生/消费领域事件)。
参考文献
- Hector Garcia-Molina & Kenneth Salem, "Sagas" (1987) —— 原始论文。
- Khononov, Learning DDD,微服务/事件驱动章节中关于 Saga 的部分。
- Bellemare, Building Event-Driven Microservices,第 8 章(Building Workflows with Microservices)。