TCC相关原理以及Seata中TCC例子demo的使用,在之前文章中已经说明,有兴趣的可以看看
https://blog.csdn.net/hosaos/article/details/89136666
本文会介绍下Seata中TCC模式设计思路及原理,源码版本0.5.1
资源预留、提交、回滚都由业务方编码控制写在try()、confirm()、cancle()中
关键点就是: TCC事务协调器如何知道在业务方调用各个微服务的try()后,是该调用confirm还是cancle方法,以及具体调用哪个资源管理器(RM)的方法
Demo中的截图都取自seata-samples中的transfer-tcc-sample,地址如下
https://github.com/seata/seata-samples
业务调用方,通过GlobalTransactional
注解开启事务,并调用各个服务提供方的try()方法
服务提供方
TwoPhaseBusinessAction
注解标记这是个TCC接口,同时指定commitMethod,rollbackMethod的名称BusinessActionContext
是TCC事务中的上下文对象BusinessActionContextParameter
注解标记的参数会在上下文中传播,即能通过BusinessActionContext对象在commit方法及cancle方法中取到该参数值对原理及Demo有个大致的回顾,下面开始盘源码
GlobalTransactionScanner
继承了AbstractAutoProxyCreator
抽象类,并重新实现了wrapIfNecessary接口(该接口用来在spring启动时,生成代理类)
看下重写的wrapIfNecessary方法
可以看到这段逻辑中,判断了bean如果是个TCC的接口实现,则将拦截器初始化为TccActionInterceptor
,TccActionInterceptor是TCC方法的核心拦截器,后面会具体介绍,先跟到TCCBeanParserUtils.isTccAutoProxy()中看下源码
TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)){
//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
}
isTccAutoProxy()中又会调用io.seata.rm.tcc.remoting.parser.DefaultRemotingParser#parserRemotingServiceInfo来进行TCC资源注册
可以看到,通过反射拿到了TwoPhaseBusinessAction
注解中声明的Commit方法和Rollback方法并封装成TCCResource对象,最终调用ResourceManager的registerResource方法
那么这里ResourceManager又是对应哪个实现呢?还是通过SPI机制找,如下
TCC模式下ResourceManger的实现为TCCResourceManager
AbstractRMHandler的实现为RMHandlerTCC
跟到TCCResourceManager中看registerResource方法
看到将TCCResource对象存储在本地Map中,方便后续通过ResourceId找到对应Resource来进行提交,回滚操作
super.registerResource代码如下,通过RmRpcClient发送rpc请求给Seata-server进行资源注册
至此,本地内存中会有个TCCResourceCache,注册完成后,Seata-server端也会有个TCC的资源列表
服务端接收RM注册信息的接口在io.seata.core.rpc.DefaultServerMessageListenerImpl#onRegRmMessage中,看下代码
最终调用了ChannelManager.registerRMChannel方法
服务端也会对RpcContext进行缓存,缓存Map嵌套层次较多,最外层key为resourceId,往内一次是applicationId,clientIIp,port
至此,TCC资源管理器RM已完成注册,本地及服务端均有以resourceId为key的缓存Map。
TCC模式业务调用方和AT模式一样,需要使用GlobalTransactional
注解来开启全局事务,如图
业务方法执行时,最终会被AT模式源码分析中提到过的拦截器GlobalTransactionalInterceptor
拦截,开启一个全局事务,获得全局事务id,即xid
具体代码是TransactionalTemplate
的execute方法,execute方法如下
分为三步
DefaultCore
的begin方法中,可以看到创建了一个GlabalSession这里就解决了第一个问题,TC是怎么知道该调用confirm方法还是rollback方法的?根据业务方调用的结果而定
无异常-TM向TC发出提交请求
有异常-TM向TC发出回滚请求
TCC注册过程分析时,如果bean是个TCC的bean(即bean中方法包含TwoPhaseBusinessAction
注解),会初始出TccActionInterceptor
拦截器,其实现了MethodInterceptor
,这也是TCC接口的方法级别核心拦截器
看下源码中的invoke方法
方法调用了actionInterceptorHandler.proceed方法,看下源码
看下doTccActionLogStore方法
服务端接收分支注册的代码也在DefaultCore中,代码如下
至此,TCC分支事务注册完毕
TransactionalTemplate
的execute方法中,若业务执行无异常,则会调用commitTransaction方法
最终调用的DefaultGlobalTransaction的commit方法
调用TM的commit方法,来通知TC对全局事务进行提交
TC收到commit消息的处理在DefaultCore的commit方法中,
看下代码
分为几步
看下同步提交代码doGlobalCommit
遍历每个branchSession,对每个分支事务进行提交,失败会无限重试
resourceManagerInbound.branchCommit方法会调用DefaultCoordinator中branchCommit方法来与TCC资源管理器通信,发送分支事务提交消息,这里sendSyncRequest方法中就会根据resourceId去找到第一步(TCC资源管理器注册)中RpcContext的缓存,并得到对应Channel来建立Netty通信
每个TCC资源管理器接收到分支事务提交的请求后,会调用io.seata.rm.tcc.TCCResourceManager#branchCommit方法实际对事务进行提交
自此客户端收到Seata-server提交信息后,完成了对分支事务的提交
至此第二个问题,TC如何找到该调用哪些机器上服务的confirm方法,rollback方法也解决了
基本思路就是
总结一下全局事务提交的大致流程
全局事务回滚思路与全局事务提交过程基本一致,不再赘述