微服务拆分后,最直接的代价之一就是数据一致性不能再依赖单个数据库的本地事务。一个下单流程可能涉及订单服务、库存服务、账户服务,各自拥有独立数据库。当库存扣减成功而账户扣款失败时,如何让已成功的操作按业务规则回退,成为架构师必须回答的问题。Saga 和 TCC 是当前后端系统中最常用的两种方案,但它们的一致性模型、侵入程度和异常处理方式差异很大,不能只凭概念选型。
两种方案的机制差异
Saga 把一个全局事务拆成一系列本地事务。每个本地事务提交后,后续步骤继续执行;如果某个步骤失败,协调器按逆序调用之前步骤的补偿操作。补偿不是数据库回滚,而是业务层反向动作,例如取消订单、恢复库存、发起退款。Saga 的优势是服务只需提供正常执行和补偿两个方法,对已有系统改造相对较小;代价是中间状态对外可见,协调器需要保存事务日志才能在任意节点失败后继续推进。
TCC 则把资源操作分成 Try、Confirm、Cancel 三个阶段。Try 阶段并不真正完成业务,而是预留资源或做前置检查,例如冻结库存、预扣额度。协调器收集所有参与者的 Try 结果,如果全部成功,再逐个调用 Confirm;如果任一失败,则对所有已成功 Try 的参与者调用 Cancel,释放预留资源。TCC 的一致性级别更接近强一致,因为它可以在 Confirm 之前保持业务上的中间状态可控,但要求业务方提供三段接口,侵入性明显更高。
一致性、性能与复杂度对比
从一致性角度看,TCC 比 Saga 更严格。TCC 通过资源预留把冲突窗口提前到 Try 阶段,Confirm 阶段多数情况下只需确认预留结果;Saga 则先提交真实操作,失败后再补偿,所以中间状态真实存在,且补偿本身可能失败。性能开销方面,TCC 对资源锁定时间更长,高并发下容易出现竞争和超时;Saga 的本地事务较短,吞吐更高,但需要持久化事务日志以支持重试和恢复。
| 对比维度 | Saga | TCC |
|---|---|---|
| 一致性模型 | 最终一致,中间状态可见 | 接近强一致,资源预留阶段可控 |
| 业务侵入 | 需要正常方法与补偿方法 | 需要 Try/Confirm/Cancel 三接口 |
| 性能开销 | 本地事务短,吞吐较高 | 资源锁定时间长,竞争成本高 |
| 实现复杂度 | 补偿逻辑与状态机需要维护 | 三段接口、幂等、空回滚等控制复杂 |
| 异常处理重点 | 补偿失败、重复补偿、悬挂 | 空回滚、幂等、防悬挂 |
| 事务协调 | 以协调器驱动或事件编排 | 协调器统一决策 Confirm/Cancel |
这个对比不是绝对优劣,而是反映适用边界。不能简单说 Saga 比 TCC 快,或者 TCC 比 Saga 好,因为业务操作类型决定瓶颈。比如库存冻结操作本身可能很快,但账户额度冻结在高并发下可能成为竞争热点;订单取消这种补偿操作也可能因为外部支付状态变化而无法立刻完成。
异常处理是两种方案真正的分水岭
Saga 的困难集中在补偿路径。补偿逻辑必须幂等,因为协调器重试可能让同一补偿执行多次。如果某个补偿方法执行不了,例如原订单状态已经变化,需要人工介入或进入死信流程。还会出现“悬挂”问题:补偿先于正常操作到达,服务必须识别并拒绝或暂存。TCC 则必须处理空回滚:Cancel 调用时 Try 可能根本没执行成功,如果 Cancel 盲目释放资源会导致数据错乱;同时 Confirm 和 Cancel 也必须幂等。防悬挂同样关键。两种方案都需要本地事务记录或唯一业务标识来保证操作幂等,否则分布式环境下的重复投递会破坏数据。
选型依据:从业务容忍度出发
选择 Saga 还是 TCC,先看业务是否允许中间状态被其他流程读取。如果扣减库存后允许短暂不一致,用户稍后看到结果即可,Saga 更合适,长链路、多参与者时开发和维护成本更低。如果是资金转账、优惠券核销、高价值库存锁定等场景,业务不能接受已扣减后等几十秒才能确认,或需要严格防止超卖,TCC 更值得投入。参与服务较多、链路较长的流程,Saga 的维护成本优势更明显;TCC 在参与者较少、资源竞争明显的短流程里更容易控制。还要考虑团队对业务代码改造的承受度:TCC 要求业务开发同时维护三段语义,适合有统一规范和代码评审能力的团队;Saga 的补偿方法通常更贴近已有业务逻辑,容易从现有服务改造。
落地实践:以 Seata 为参考
Seata 同时提供 Saga 和 TCC 模式,因此不需要在框架层面二选一。落地时可以按业务一致性等级拆分:资金链路用 TCC,非核心长流程用 Saga。Saga 模式通常用状态机或编排描述服务调用顺序与补偿节点,协调器持久化全局事务状态,当节点失败时向已执行节点发起补偿。实现时要保证每个补偿方法可重复执行,并记录每次执行的状态,避免恢复时漏补偿或重复补偿。
TCC 模式在 Seata 中的实现重点是 Try、Confirm、Cancel 三个方法的事务性边界。Try 方法必须完成资源预留并登记业务标识,Confirm 只负责确认预留,不再重复检查额度;Cancel 必须按预留记录释放资源,并且要容忍 Try 未成功时的空回滚。业务表通常需要额外的冻结字段或流水记录,用来支撑幂等和防悬挂判断。框架会维护全局事务与分支事务状态,但资源预留和释放的正确性仍然落在业务代码上。
落地顺序比模式选择更关键
实际落地不建议一开始就把所有跨服务调用改成分布式事务。先梳理业务链路,标记必须具备原子性操作的部分,再对这部分引入 TCC;对可以接受短暂不一致的长流程,用 Saga 降低侵入。落地过程中需要把几件事前置确认:所有参与方是否具备幂等能力,补偿操作是否可以真正执行,事务日志是否完整记录,超时与重试策略是否明确。这些基础不做好,无论选哪种模式都会在异常时放大数据错误。
Saga 和 TCC 并不是替代关系,而是不同一致性要求下的两条路径。架构师需要根据业务容忍度、流程长度和团队维护能力做选择,而不是追求更强的一致性。把事务边界和补偿路径设计清楚,远比自己搭一套看起来完整的框架更重要。
发布者:jacky,转转请注明出处:https://kubiyun.com/archives/4291