Seata中的TCC模式
约 2529 字大约 8 分钟
2025-09-07
对于分布式的全局事务来将,整体上都是遵循两阶段提交模型。全局事务是由多个分支事务组成的,分支事务要满足两阶段提交的模型要求,即需要每个分支事务都具备自己的:
- 一阶段Prepare行为;
- 二阶段commit或rollback行为;
AT模式与TCC模式之间的二阶段行为上的区别
| 阶段 | AT模式 | TCC模式 |
|---|---|---|
| 一阶段(Prepare) | 在本地事务中,一并提交业务数据更新和相应的回滚日志记录 | 调用自定义的Prepare逻辑 |
| 二阶段(Commit) | 马上成功结束,自动异步批量清理回滚日志 | 调用自定义的Commit逻辑 |
| 二阶段(Rollback) | 通过回滚日志,自动生成补偿操作,完成数据回滚 | 调用自定义的Rollback逻辑 |
所以很容易理解就是:TCC模式是指把支持自定义的分支事务纳入到全局事务管理中。
在AT模式下的回滚是通过Prepare阶段生成的undo日志进行回滚,但是在AT模式下undo日志的生成是依赖于对本地事务的中的SQL语句进行解析,那么如果:在分支事务中需要通过RPC调用三方平台的接口,那么在Prepare阶段AT模式下就无法生成undo日志,因此在二阶段的Rollback时无法回滚了。
因此Seata提供了一种基于业务层面的分布式解决方案,它将一个完整的业务操作拆分为三个阶段:
- Try(预留资源 —— Prepare阶段):尝试执行,完成所有业务检查,预留必要的业务资源(如冻结库存,预占账户金额等);
- Cofirm(确认执行 —— Commit阶段):确认执行真正的业务操作,使用Try阶段预留的资源,完成实际提交;
- Cancel(取消执行 —— Rollback阶段):如果发生异常,则取消之前预留的操作,释放资源;
所以与AT模式相比:TCC模式更适合高并发、对性能要求极高的业务系统,而AT模式在极高的并发下可能成为瓶颈。
TCC的工作模式
相对于AT模式而言,它的回滚操作是对通过解析本地的SQL语句来实现的,但是在TCC模式下,Try-Commit-Cancel三个阶段都是需要手动实现的。
TCC是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的侵入性,设计相对复杂,但优点是TCC完全不依赖与数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决在各种复杂业务场景下的分布式事务问题。
TCC的工作流程
快速实现
假设现在有一个业务需要同时使用服务A和服务B完成一个事务操作,其步骤如下:
1️⃣在服务A定义一个该服务的TCC接口
public interface TccActionOne {
@TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}2️⃣在服务B定义一个该服务的TCC接口
public interface TccActionTwo {
@TwoPhaseBusinessAction(name = "DubboTccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")
public void prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "b") String b);
public void commit(BusinessActionContext actionContext);
public void rollback(BusinessActionContext actionContext);
}3️⃣在业务系统中开启全局事务并执行服务A和服务B的TCC预留资源方法
@GlobalTransactional
public String doTransactionCommit(){
//服务A事务参与者
tccActionOne.prepare(null,"one");
//服务B事务参与者
tccActionTwo.prepare(null,"two");
}以上就是使用Seata TCC模式实现一个全局事务的例子。
TCC模式同样使用@GlobalTransaction注解来开启全局事务,而服务A和服务B的TCC接口为事务参与者,Seata会把TCC接口当成一个Resource,也叫TCC Resource。

Seata启动时会对TCC接口进行扫描并解析,如果TCC接口是一个发布方,则在Seata启动时会向TCC注册TCC Resource,每个Tcc Resource都有一个资源ID;如果TCC接口是一个调用方,Seata代理调用方,与AT模式一样,代理会拦截TCC接口的调用,即每次调用Try方法,会向TCC注册一个分支事务,接着才执行原来的RPC调用。
TCC的核心步骤
在Seata的TCC模式中,TCC Resource是框架对实现了@TwoPhaseBusinessAction注解的业务接口的抽象封装,用于标识是一个可参与分布式事务的资源(如RPC服务、本地方法)。其注册过程是围绕着:注解解析、资源对象苟江、缓存与远程注册展开,核心目标是让TC识别并管理该资源的二阶段的提交与回滚操作。
注册的前提
注册的前提是,业务接口必须满足如下两个条件:
接口标记:使用
@TwoPhaseBusinessAction注解标注解饿口,明确资源名称(name)、二阶段方法(commitMethod/rollbackMethod)及参数配置(如commitArgsClasses指定提交方法的参数类型)。@TwoPhaseBusinessAction(name = "order-tcc-action", commitMethod = "confirmOrder", rollbackMethod = "cancelOrder", commitArgsClasses = {BusinessActionContext.class, String.class}, rollbackArgsClasses = {BusinessActionContext.class, String.class}) public interface OrderTccAction { void tryOrder(BusinessActionContext context, String orderId, int amount); boolean confirmOrder(BusinessActionContext context, String orderId); boolean cancelOrder(BusinessActionContext context, String orderId); }代理目标:接口的实现按类需要被Seata代理(如通过@LocalTCC标记的本地实现,或通过Dubbo、HSF等RPC框架暴露的远程服务)
注册的触发
Seata通过GlobalTransactionScanner在应用启动时扫描都有的Bean,识别需要代理的TCC资源:
- Bean过滤:TCCBeanParserUtils.isTccAutoProxy方法判断Bean是否需要代替,条件包括:
- 是LocalTCC标记的本地方法
- 是RPC服务提供方
- 是RPC服务消费方
- 代理创建:符合条件的Bean会被TransactionInterceptor代理,拦截器负责处理业务方法的环绕逻辑(如一阶段的Try的调用、上下文传递);
- 资源解析:DefaultRemotingParser(远程通信解析器)解析被代理Bean的@TwoPhaseBusinessAction注解,提取name、commietMethod和rollbackMethod。
资源缓存与远程上报
- 本地缓存:TCCResource对象会被放入TCCResourceManager(TCC资源管理器)的本地缓存(tccResourceCache)中,键为resourceId(即@TwoPhaseBusinessAction.name的值),值为TCCResource对象。
- 远程注册:TCCResourceManage.registerResource方法会将TCCResource注册到TC(事务协调器)。具体流程为:
- 调用DefaultResourceManager.registerBean(tccResource),将资源信息同步到TC;
- TC接收到注册请求后,会将
resourceId(资源名称)、branchType(分支类型,如TCC)、clientId(客户端标识)等信息存入branch_table(分支事务表),用于后续二阶段操作的寻址。
二阶段操作的寻址与执行
注册完成后,TC会通过resourceId(即@TwoPhasheBusinessAction.name)识别TCCResource。当全局事务需要提交或回滚时,TC会向对应的RM发送BranchCommitRequest或BranchRollbackRequest,RM通过TCCResourceManager从缓存中获取TCCResource,调用其commitMethod或rollbackMethod执行二阶段操作。
TCC中的问题
TCC模式下Try-Confirm-Cancel三个阶段都需要用户来手动实现,所以在实际应用中仍然会出现多种数据不一致的场景,需要针对性的解决。
空回滚问题
场景:Try阶段因为网络超时或服务器宕机等原因未执行,但全局事务超时触发了Cancel阶段,导致Cancel方法对未预留的资源执行回滚,引发数据不一致;
举例:在try阶段需要调用库存服务对库存进行冻结,但是因为网络超时执行失败了,导致全局事务的Cancel阶段被触发了,因此会对没有被冻结的库存进行解冻。
解决方案:
- 状态标记法:在业务表中增加
frozen_amount冻结金额字段,try阶段成功时更新该字段,Cancel阶段执行前查询frozen_amout,若为0则跳过回滚(说明Try阶段未执行); - 事务状态法:新增
tcc_fence_log表,Try阶段插入status='trying'记录,Cancel阶段查询该表,若无记录则判定为空回滚,跳过业务回滚并插入suspended状态记录;
Seata 1.5+版本通过
@TwoPhaseBusinessAction(useTCCFence=true)注解启动事务状态表机制,自动处理空回滚;
幂等问题
场景:Confirm/Concel阶段因网络抖动、TC未收到相应等原因重复调用,导致重复操作,破坏数据一致性。
解决方案:
- 状态校验法:在业务表中增加status字段,Confirm/Cancel阶段执行前查询状态,若为目标状态则直接返回成功,避免重复操作;
- 事务状态表法:通过tcc_fence_log表的status字段记录二阶段执行状态,重复调用时校验状态,确保仅执行一次;
Seata的
@TwoPhaseBusinessAction注解会自动注入幂等性校验逻辑,开发者只需要关注业务状态的定义;
悬挂问题
场景:Try阶段因网络延迟超时,TC触发Cancel阶段并成功执行,但后续的Try阶段的请求延迟到达并成功执行,导致Try预留的资源无法提交,引发数据不一致。
解决方案:
- 前置状态检查法:Try阶段执行前查询tcc_fence_log表,若存爱suspended(空回滚标记)或cancelled记录,则拒绝执行Try阶段。避免滞后的请求破坏数据一致性;
- 状态闭环设计法:Confirm/Cancel阶段成功后,在tcc_fence_log表中插入commited/cancelled终态记录,阻断滞后Try请求的执行。
Seata 1.5+版本通过
useTCCFence=true启用悬挂防护,自动拦截滞后Try请求。