TCC 补偿型事务
用途:需要在跨服务场景获得接近强一致、又不愿承受 2PC 持锁代价时查阅
定义
「定义」小节链接**TCC(Try-Confirm-Cancel)**是应用层的两阶段协议,把每个参与服务显式拆成三个接口:
| 阶段 | 语义 | 要求 |
|---|---|---|
| Try | 预留资源并校验可行性,不真正生效 | 资源可被冻结/预占,且预留不阻塞他人 |
| Confirm | 使用 Try 阶段预留的资源完成业务 | 幂等,不需再校验业务条件 |
| Cancel | 释放 Try 阶段的预留 | 幂等,且能处理空回滚 |
协调由事务管理器或业务编排代码驱动:全部 Try 成功 → 逐个 Confirm;任一 Try 失败 → 对已成功的分支执行 Cancel。
由来与要解决的问题
「由来与要解决的问题」小节链接2PC 的问题在于资源锁持有到全局提交,吞吐低且协调者单点。TCC 把“锁定”上移到业务语义层(冻结额度而非锁行),因此持锁时间由业务自己控制,可支持长流程与跨异构系统的一致性。
常见于资金、库存、额度、席位等“资源必须预留”的场景。
必须处理的三类异常
「必须处理的三类异常」小节链接- 空回滚:Try 请求因网络超时未到达,但协调方已决定 Cancel。Cancel 必须识别“没有 Try 记录”并直接成功。
- 悬挂:Try 迟到,在 Cancel 之后才到达,导致资源被永久冻结。需在 Try 中检查事务是否已终止。
- 幂等: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 章(补偿事务的一般形式)