跳转到内容

TCC 补偿型事务

用途:需要在跨服务场景获得接近强一致、又不愿承受 2PC 持锁代价时查阅

**TCC(Try-Confirm-Cancel)**是应用层的两阶段协议,把每个参与服务显式拆成三个接口:

阶段 语义 要求
Try 预留资源并校验可行性,不真正生效 资源可被冻结/预占,且预留不阻塞他人
Confirm 使用 Try 阶段预留的资源完成业务 幂等,不需再校验业务条件
Cancel 释放 Try 阶段的预留 幂等,且能处理空回滚

协调由事务管理器或业务编排代码驱动:全部 Try 成功 → 逐个 Confirm;任一 Try 失败 → 对已成功的分支执行 Cancel。

2PC 的问题在于资源锁持有到全局提交,吞吐低且协调者单点。TCC 把“锁定”上移到业务语义层(冻结额度而非锁行),因此持锁时间由业务自己控制,可支持长流程与跨异构系统的一致性。

常见于资金、库存、额度、席位等“资源必须预留”的场景。

  1. 空回滚:Try 请求因网络超时未到达,但协调方已决定 Cancel。Cancel 必须识别“没有 Try 记录”并直接成功。
  2. 悬挂:Try 迟到,在 Cancel 之后才到达,导致资源被永久冻结。需在 Try 中检查事务是否已终止。
  3. 幂等:Confirm/Cancel 都可能被重复调用(重试驱动),必须可重入且不重复扣减。

工程做法:为每个分支维护事务控制表(记录分支状态),Try/Confirm/Cancel 前先查表判断,配合唯一业务键。

优点 代价
无长时间资源锁,吞吐高 每个服务要写三个接口,业务代码侵入严重
可跨异构系统与外部服务 补偿逻辑的正确性难验证(失败路径比正向更复杂)
一致性接近强一致(Confirm 阶段不再失败的前提下) 冻结资源对用户可见性需产品设计(“占用中”额度)
支持较长流程 部分 Confirm 失败仍需重试与兜底对账

Seata 的 TCC 模式、DTX 类商业方案、以及大量团队自研的“冻结-扣减-解冻”表结构。自研时的最小要件:分支事务表 + 幂等键 + 空回滚标记 + 悬挂检测 + 超时补偿任务。

  • 业务本身允许最终一致 → 用本地消息表或事务消息,成本低得多。
  • 无法定义清晰的“资源预留”语义(纯记录写入类操作)→ TCC 没有收益。
  • 参与方不可控(第三方只给一个提交接口)→ 只能走最大努力通知 + 对账。
  • 2PC/XA:TCC 是业务层两阶段,锁粒度与时长由业务控制。
  • Saga:Saga 无 Try 阶段,直接执行再补偿;TCC 预留后再确认,冲突更早暴露。
  • 幂等设计:TCC 的正确性几乎完全依赖幂等与状态表。
误区 修正
Confirm 里再做业务校验 Confirm 必须只做“用已预留资源完成”,否则可能永久失败
忽略空回滚与悬挂 用分支状态表显式判定
Cancel 非幂等 重试会造成资源被重复释放
把 TCC 当“零不一致”方案 Confirm 阶段失败仍需重试与对账兜底
  • Pat Helland《Life beyond Distributed Transactions: an Apostate’s Opinion》
  • 各分布式事务框架的 TCC 模式文档(Seata 等)
  • Chris Richardson《微服务架构设计模式》第 4 章(补偿事务的一般形式)