故事

乾隆年间,扬州回春堂做南北药材的批发生意。一单大买卖从辽东收人参起,要经漕运过淮安,再换船过江到扬州,最后分送苏杭两地的分号。全程两个月,涉及七个货栈、五艘船、十二道关卡。

早年回春堂用"整单押印"的做法——起运时总号押一枚玉印,货到各地逐一分销,全部销完才收回玉印,一单算结。可乾隆三十七年那趟人参,船到淮安遇了洪峰,整船货泡了汤。玉印还在船上,总号收不回印,这单买卖在账簿上悬了八个月,后面所有依赖这单资金的采购全被卡住。

这是锁太长。一枚玉印押出去,等于把整条资金链都冻在这单货上。

回春堂改了规矩, invented by 大掌柜沈仲和。他把一单大买卖拆成七段,每段只押一段的凭证,不叫"印",叫"条"——收条、运条、关卡条、分销条。每段完成后,下一段才启动;若某段失败,不追整条,只追这一段,而且每一段都配一张"回条"——写明若本段作废,该如何把已付出的钱、已发出的货、已缴纳的税,逐项退回来。

比如"淮安换船"这一段:若船未按期到埠,收条自动作废,辽东货栈按回条把人参原数退回山客,漕运局按回条退水脚银,已缴的关税凭回条去衙门申请退还。三段退款各走各的,互不等待——辽东的退完了,淮安的还在办,这不影响,因为每段有自己的凭证,不牵一发而动全身。

最难的是已经分送苏杭的人参怎么往回追。沈仲和的办法是"反向条"——分销条上写明,若总号发出召回,分号须在十五日内把未售出的货原路退回;已售出的,分号先垫银回购,再凭回购契约向总号结算。这相当于在正向流程里预埋了反向的钩子,正着走是卖货,倒着走是逐层回购。

乾隆四十二年,一趟从云南收三七的买卖在苏州段出了事。分号掌柜私自压货涨价,被总号查出来后,整段分销条作废。按回条,苏州分号要垫银回购已售三七,可那年三七价涨,回购价比原售价高出三成,分号银库不够。

沈仲和这时候面临一个选择:是让苏州分号硬撑着完成回购,还是承认这段回条本身也执行失败,再触发更高一层的补偿?

他选了后者。苏州分号的回购条作废,总号直接发"急调条"——从杭州分号调银补苏州的窟窿,同时把苏州分号未来半年的进货额度扣减,作为分期偿还。这等于在补偿链上又套了一层补偿,用未来的信用填现在的坑,而不是让某一段硬卡死。

回春堂的账簿因此多了一种颜色。黑字是正常流程,红字是回条执行,蓝字是回条的回条——补偿的补偿。沈仲和说:"只要蓝字不超过三层,这摊子就还能转。"

概念解析

Sagas(Garcia-Molina & Salem, 1987)是对长事务(Long-Lived Transaction)的分解策略。传统 ACID 事务([✓#5 两个抄经人])假设事务在毫秒级完成,锁可以短期持有;但跨越多个服务、多个地理节点的业务流程,锁持有时间从毫秒变成小时甚至天数,锁的代价超过了它保护的价值

Sagas 的核心操作是把长事务拆成一串本地事务(Local Transactions),每个本地事务独立提交,不等待全局终点。同时,每个本地事务配一个补偿事务(Compensating Transaction)——不是回滚(Rollback),因为已经提交的数据无法"回滚",而是用一笔新的、语义相反的交易来对冲

补偿 vs 回滚。数据库的回滚依赖 Undo Log,把物理页恢复到修改前;补偿是业务层的反向操作——发货的补偿是召回,扣款的补偿是退款,创建订单的补偿是取消订单。补偿本身也是一笔新事务,可能失败,可能需要再补偿,因此形成补偿链

与 2PC 的关系([✓#20 悬心])。2PC 追求全局原子性——所有参与者要么全提交、要么全回滚,代价是阻塞和单点脆弱。Sagas 放弃全局原子性,换取可用性和弹性:任何一段失败,系统不必卡住等协调者裁决,而是继续执行补偿,让业务在"最终不一致"的中间态里保持运转。

顺序补偿 vs 并行补偿。 Saga 的原始论文规定补偿必须按正向流程的逆序执行——先撤销最后一步,再撤销倒数第二步,以此类推。这保证了对冲的语义正确性(比如先取消订单再退款,而不是反过来)。工程实践中,无依赖的补偿可以并行,有依赖的必须串行,这由 Saga 编排器(Orchestrator)或事件链(Choreography)来调度。

遗留问题。补偿不是万能的:有些操作物理上不可逆(邮件已发出、火箭已发射、药品已被患者服用),这类操作只能采用可逆的业务设计(如邮件补发澄清函、火箭预留自毁指令、药品召回)——本质上是在正向流程里预埋"后悔权",这正是故事中的"反向条"。

Saga 也不保证隔离性。两段 Saga 并发执行时,中间状态彼此可见,可能产生脏读(读到尚未补偿的临时数据)。这需要业务层容忍,或通过语义锁(Semantic Lock)——在业务字段里标记"处理中"——来降低风险。

现代形态。2015 年后,Saga 模式被微服务架构重新发现。Seata、Temporal、Camunda 等框架把"条"和"回条"自动化:正向步骤成功即发事件,编排器监听失败事件并触发补偿,补偿失败则进入人工介入队列。沈仲和的三层蓝字规则,在系统里变成了补偿重试上限 + 死信队列(DLQ)——技术换了,问题没变:无限递归的补偿链和无限悬置的玉印,是同一类灾难的不同面貌。