「一旦邮件发出,你无法撤回。」——这句话戳中了分布式事务最痛的点。
凌晨2点14分,用户的卡被扣款。3秒后,物流系统返回502。6秒后,重试机制再次扣款,货物却依然没发出。客服现在要向用户解释:为什么为一笔不存在的订单付了148.50美元。
![]()
这就是缺乏补偿机制的 Saga。四个步骤在流程图上看着像直线,生产环境里却步步踩雷:每一步都可能失败,每一步都可能"成功但超时",而修复第三步的重试,如果没有幂等补偿,会直接搞砸第二步。
正方:编排派——支付场景必须用集中控制
1987年,Hector Garcia-Molina 和 Kenneth Salem 提出 Saga 模式:一系列本地事务,每步都有语义上的"撤销动作"。但怎么把这些步骤串起来?两种架构选择。
协作式(Choreography):各服务监听事件、触发事件。库存预留完成,自动触发扣款;扣款成功,自动触发发货。流程藏在事件拓扑里,没人能一眼看清全貌。
编排式(Orchestration):协调器服务掌握状态机,主动命令每个服务执行,等待响应,再决定下一步或回滚。
Chris Richardson 在 microservices.io 上的判断很直接:协作式"适合简单 Saga",步骤多了就该切编排。支付不是简单 Saga——四个正向步骤、四个补偿动作、三种终态(成功/已补偿/卡住需人工),这种复杂度下,编排式在第一周跑真实流量时就会显出优势。
编排派的核心论据有三点,都是血泪教训换来的:
第一,状态可见性。协调器持久化存储 Saga 状态,任何时候都能回答"这笔订单现在在哪一步"。协作式的事件链断了,你得去各个服务的日志里拼图。
第二,补偿顺序可控。支付失败要回滚时,必须先退款再释放库存,还是反过来?编排器按预定义顺序执行;协作式靠事件触发,时序可能乱。
第三,幂等性强制执行。同一笔补偿请求被重试两次,协调器能识别"这已经做过了";协作式里每个服务得自己实现幂等,漏一个就是重复退款或重复放库存。
反方:协作派——简单场景别过度设计
协作派并非没有道理。他们的反驳集中在三个层面:
首先,编排器是单点。所有流量过协调器,它挂了怎么办?高可用方案又增加复杂度。协作式没有中心节点,一个服务崩了不影响其他。
其次,事件驱动更"自然"。库存服务预留成功就发事件,扣款服务订阅了自然响应,不需要被"指挥"。这种松耦合符合微服务的原教旨。
最后,编排器的代码会变成"上帝对象"。每一步新增都要改协调器,团队多了容易成为瓶颈。协作式各服务自治,迭代更快。
这些反驳在简单场景成立。三步以内的流程,事件拓扑一眼能画完,协作式的隐式流程反而好维护。但支付场景的典型反驳是:你的"简单"能持续多久?
通知步骤就是试金石。邮件一旦发出,技术上无法撤销——这是物理限制,不是设计选择。协作式里,通知服务可能并发收到"发货成功"和"订单取消"两个事件,怎么处理?编排器可以在状态机里明确:进入 COMPENSATING 后,通知步骤跳过补偿,只记录"已发送,需人工跟进"。
判断:支付场景,编排式是底线而非优选
两派争论的实质,是对"复杂度"的定义不同。协作派说的复杂是代码量、部署单元数;编排派说的复杂是状态空间、失败模式数。支付属于后者爆炸的场景。
具体看四个正向步骤的补偿设计:
预留库存 → 释放库存。幂等的关键是库存服务记录"这笔预留已释放",重复请求直接返回成功。
扣款 → 退款。信用卡退款有网络延迟和银行侧幂等问题,补偿动作本身可能失败,需要进入 STUCK 态人工处理。
发货 → 拦截 shipment。物流系统可能已出库,补偿变成"发起退货流程",语义上不是真正的"撤销"。
通知 → 无补偿。这是唯一不补偿的步骤,也是编排式价值最显性的地方:状态机明确标记 notify 为" fire-and-forget ",避免团队假装能撤回邮件而写一堆无效代码。
三种终态的设计同样关键。NOTIFIED 是成功终点;COMPENSATED 意味着所有副作用已清理,可以安全重试整笔订单或告诉用户"已取消";STUCK 则是运营系统的入口——补偿本身失败超限时,必须有人工介入的明确状态,而不是卡在"不知道哪步漏了"的混沌里。
协作式能否实现同等效果?理论上可以,但每个服务都要重复实现:状态持久化、幂等判断、补偿顺序协调、终态上报。这些横切关注点分散在各处,最终你会写出一个"分布式编排器"——只是更难调试。
所以判断很明确:支付流程的 Saga,编排式不是"更好",而是"必须"。不是因为协作式不够优雅,而是因为支付场景的失败模式太脏——超时、重复扣款、物流系统502、银行侧幂等——需要一个能强制执行业务规则的中心点。
那个中心点不必知道库存怎么算、邮件怎么发,但必须知道"现在该谁动"和"出错了该往哪退"。状态机是这笔知识的最佳载体。
最后提一个实现细节:补偿的幂等性。原文强调"补偿本身必须是幂等的",这是生产事故的高发区。第一次补偿请求发出后,协调器崩溃重启,它怎么知道"这步做过了"?答案只能是持久化状态——补偿请求已发出,等待确认。没有持久化的编排,比协作式更危险。
那么,你的系统里有没有"假装能补偿"的步骤?通知邮件、短信推送、第三方回调——这些物理上不可逆的操作,你的状态机是明确标记为"无需补偿",还是藏着没处理的 bug?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.