分布式系统架构中,数据一致性是一个永恒的话题。相较于强一致的 XA 模式,TCC 模式以其高性能和最终一致性保障,成为互联网分布式事务的经典解决方案。本文将深入剖析TCC事务的原理,并通过 Seata 框架演示其实现过程。
在微服务架构中,一个业务操作往往需要调用多个服务,这些服务有自己独立的数据库,这就构成了一个分布式事务。传统的 XA 协议基于 两阶段提交(2PC) 实现强一致性,但其存在 同步阻塞、资源锁定时间长、性能低下 等致命问题,难以应对高并发场景。
TCC(Try-Confirm-Cancel)事务模型应运而生。它是一种 业务层面 的分布式事务解决方案,通过 最终一致性来保障数据一致性,释放了数据库层的资源锁定,从而获得了更高的性能,非常适合互联网业务。
TCC 的本质是将一个完整的业务逻辑拆分为三个操作,由业务代码本身实现。
TCC 模式对应的三个操作就是将分布式事务拆分为三个阶段,每个阶段都有明确的职责
目的:完成所有业务的检查,并预留必需的资源,也就是不提交事务,仅进行资源预留
操作:是一种预备操作,而非最终操作。例如:
特点:Try 阶段成功,意味着后续的 Confirm 阶段基本一定会成功。
目的:真正执行业务,使用Try阶段预留的资源。Confirm操作需要保证幂等性。
操作:在Try成功的基础上,执行真正的业务操作。例如:
库存服务:将冻结库存扣减(即清零),生成出库记录。
账户服务:将冻结金额扣减(即清零),生成支付流水。
优惠券服务:将优惠券状态更新为“已使用”。
执行条件:只有当所有参与者的Try操作都成功时,事务协调者才会调用Confirm。
目的:释放Try阶段预留的资源。Cancel操作同样需要保证幂等性。
操作:在Try阶段失败或任何参与者执行失败时,用于回滚。例如:
库存服务:将预扣的库存释放回可售库存(库存数量加回,冻结库存减去)。
账户服务:将冻结的金额解冻(余额加回,冻结金额减去)。
优惠券服务:将优惠券状态恢复为“未使用”。
执行条件:只要有一个参与者的Try操作失败,事务协调者就会调用Cancel。
提示
TCC 是一种 侵入式 的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。

全局事务开始:由事务发起方(如订单服务)调用 @GlobalTransactional 标记的入口方法
Try阶段:各参与服务执行 Try 操作,预留资源
Confirm/Cancel阶段:
优点:
高性能:一阶段Try操作直接提交,释放数据库资源,性能远高于XA。
最终一致性:基于补偿机制,实现数据的最终一致。
灵活性:事务的粒度由业务控制,可以做得非常细。
缺点:
代码侵入性强:需要业务系统自行实现Try、Confirm、Cancel三个接口,开发量大。
业务逻辑复杂:设计“预留资源”的逻辑通常比较反直觉,增加了设计和开发的复杂度。
幂等性要求:必须保证Confirm和Cancel操作的幂等性,以应对网络重试等问题。
空回滚和防悬挂:需要处理一些特殊异常场景(下文会详解)。
Seata 框架将 TCC 模式融入到其全局事务模型中,极大地简化了 TCC 模式的使用复杂度。
在Seata TCC模式中,三个角色依然存在:
TM (Transaction Manager):定义事务边界,通过@GlobalTransactional注解开启全局事务。
RM (Resource Manager):每一个参与 TCC 事务的微服务都是一个 RM,它们需要实现 TCC 的三个接口。
TC (Transaction Coordinator):Seata 服务器,负责维护全局事务和分支事务的状态,驱动全局提交或回滚。
Seata 通过注解将普通接口标识为 TCC 资源。核心注解是 @LocalTCC 和 @TwoPhaseBusinessAction。
@LocalTCC:应用于接口上,表明该接口是一个TCC模式的RM。
@TwoPhaseBusinessAction(name= "...", commitMethod= "...", rollbackMethod= "..."):标注在Try方法上,用于将 Try 方法与 Confirm/Cancel 方法关联起来。
name:TCC事务的actionName,必须全局唯一。
commitMethod:Confirm阶段执行的方法名。
rollbackMethod:Cancel阶段执行的方法名。
这是 TCC 模式实现中最需要谨慎处理的部分,seata 框架也是在1.5.1版本解决了此类问题,有兴趣的小伙伴可以阅读下 阿里 Seata 新版本终于解决了 TCC 模式的幂等、悬挂和空回滚问题
场景:Try 方法未执行(可能因为网络超时),但 Cancel 方法却被 TC 调用了。
原因:TM 认为 Try 超时失败,触发全局回滚。但 RM 实际上并未执行 ry,资源未被预留。
解决方案:在 Cancel 方法中,需要检查当前事务 ID(XID)对应的业务记录是否存在。如果不存在(即 Try 没执行),则需要执行一次“空回滚”操作(记录一条回滚日志即可),避免释放了未冻结的资源。
场景:Cancel 方法比 Try 方法先执行(空回滚发生后,Try 请求才到达 RM)。
原因:网络拥堵,导致二阶段的 Cancel 指令比一阶段的 Try 指令更早到达 RM。
解决方案:在 Try 方法中,需要检查当前XID是否已经执行过空回滚(即检查空回滚日志)。如果已执行过,则拒绝执行 Try 业务逻辑,实现“防悬挂”。
场景:TC 因未收到 RM 的响应,会重复调用 Confirm 或 Cancel 方法。
解决方案:在 Confirm 和 Cancel 方法中,需要根据事务状态表或业务数据的状态来判断当前操作是否已经执行过。如果已执行,则直接返回成功。
适用场景
最佳实践
TCC分布式事务模型通过 业务拆解 和 补偿机制,巧妙地避开了数据库资源锁定的瓶颈,实现了高性能的最终一致性。虽然它带来了 代码侵入性 和 设计复杂性,但其优越的性能表现使其成为互联网分布式事务解决方案的首选之一。
Seata 通过简洁的 API 和配置,大大降低了 TCC 模式的使用门槛。在实际项目中,建议根据业务特点选择合适的事务模式:对于对一致性要求极高的核心业务,TCC 模式是理想选择;而对于一致性要求相对宽松的场景,AT 模式可能更为合适。
本文作者:柳始恭
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!