连载中 6/18

Saga:长事务的状态机与补偿

2026-06-16 · 5031 阅读 · 0 评论 · 0 赞

被退货流程逼疯的团队

老王接了个退货需求:申请受理、审核通过、原路退款、库存回仓、物流召回——五个环节跨五个服务,从发起到完结可能隔两三天。按 TCC 的路数,每个环节 Try/Confirm/Cancel 三个接口,十五个接口外加全套防坑逻辑,团队当场表示要请病假。问题的本质变了:这不是短事务的资源预留,是长流程的步进与回退——该 Saga 出场了。

Saga 的模型:一串本地事务加逆序补偿

Saga 把长事务拆成一串依次真实提交的本地事务 T1、T2、T3……每个 Ti 配一个补偿 Ci(Ci 能把 Ti 的效果撤销)。全链成功就一路提交完事;某步 Tj 失败,就逆序执行已成功步骤的补偿:C(j-1)、C(j-2)……C1,世界回到起点:

T1 受理入库  →  T2 审核通过  →  T3 发起退款 ✗
                              ↓ 失败,逆序补偿
              C2 审核作废  ←  C1 受理单关闭
(每步都是真实提交,补偿也是真实提交)

和 TCC 对照看差异一目了然:TCC 有 Try 预留,Saga 没有——每一步都是真刀真枪的提交,失败靠事后补偿撤回。接口数量减半以上(每环节一个正向一个补偿,不用预留),这就是长流程下的轻。

两种编排:谁来指挥

编排式(Orchestration):一个中央协调器持有整个流程的状态机,按图驱动各服务执行、失败时指挥逆序补偿。Seata 的 Saga 状态机、Temporal、Camunda 都是这类。流程图集中一处,改流程改一处,出问题看一张图——老王的选择。

协同式(Choreography):没有指挥官,服务间用事件串联——受理服务发布「已受理」事件,审核服务订阅并处理后发布「已审核」,退款服务订阅再发布……像多米诺。松耦合、无中心依赖,代价是流程散落在各订阅关系里:想回答「退货走到哪一步了」得跨五个代码库找事件链,环节一多就是灾难。

维度编排式协同式
流程定义集中状态机,一目了然散落在订阅关系,难追踪
耦合度服务依赖协调器只依赖事件通道
适用规模环节多、分支多环节少、线性流程

补偿的设计纪律

补偿接口的防御要求和 TCC 的 Cancel 一模一样,第 5 篇的三坑原样迁移:幂等(补偿被重试多次只生效一次)、空补偿(正向没执行成功,补偿先到——查无正向记录时幂等返回并落标记)、防悬挂(补偿处理完后迟到的正向请求要被拒绝)。另加一条 Saga 特有的:补偿必须可成功——补偿失败意味着流程卡死,所以补偿逻辑要尽量简单(改状态、退资源),复杂的反向业务要么提前保证可逆,要么转人工。

没有隔离性怎么办:语义锁

Saga 的软肋是隔离性缺失:每步真实提交,中间态对外全部可见。退款进行到一半,订单状态是什么?用户看到「退款中」的钱去哪了?如果不设防,别的业务可能基于半途状态做错误决策(财务把退款中的单计入已退款)。业界药方叫语义锁:给业务对象加一个流程状态字段(处理中 / 完成 / 补偿中),所有读方按状态分流——处理中的订单不发发货指令,补偿中的单不进财务口径,等于用业务规则把中间态「圈禁」起来。代价是每个参与方都要理解并尊重这个状态,纪律成本 again。

小结

Saga 一句话:一串真实提交的本地事务 + 逆序补偿,用「没有预留」换轻量,用「语义锁」补隔离;补偿幂等、空补偿、防悬挂三条纪律与 TCC 同源。老王的退货流程上了编排式 Saga,五个服务十五个接口缩成五个正向五个补偿。但注意——Saga 里的每一步都依赖「失败会被感知」,如果失败本身传不出来呢?下一步的可靠通信有自己的标配答案:本地消息表,下一篇见。

503

10 年全栈工程师 · 503咖啡馆主理人

#分布式事务#Saga#补偿#状态机#语义锁

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞