Seata实战-TCC模式分布式事务原理、源码分析

原理简介

TCC相关原理以及Seata中TCC例子demo的使用,在之前文章中已经说明,有兴趣的可以看看

https://blog.csdn.net/hosaos/article/details/89136666

本文会介绍下Seata中TCC模式设计思路及原理,源码版本0.5.1

先附上一张TCC原理图,如图
在这里插入图片描述
总得来说就是分为如下几步

  1. 业务方调用各个微服务的try()方法,执行资源检查及预留操作
  2. 当所有try()方法均执行成功时,对全局事物进行提交,即由事物管理器调用每个微服务的confirm()方法
  3. 当任意一个方法try()失败(预留资源不足,抑或网络异常,代码异常等任何异常),由事物管理器调用每个微服务的cancle()方法对全局事务进行回滚

资源预留、提交、回滚都由业务方编码控制写在try()、confirm()、cancle()中

关键点就是: TCC事务协调器如何知道在业务方调用各个微服务的try()后,是该调用confirm还是cancle方法,以及具体调用哪个资源管理器(RM)的方法

Demo回顾

Demo中的截图都取自seata-samples中的transfer-tcc-sample,地址如下

https://github.com/seata/seata-samples

业务调用方,通过GlobalTransactional注解开启事务,并调用各个服务提供方的try()方法
在这里插入图片描述
服务提供方

  1. TwoPhaseBusinessAction注解标记这是个TCC接口,同时指定commitMethod,rollbackMethod的名称
  2. BusinessActionContext是TCC事务中的上下文对象
  3. BusinessActionContextParameter注解标记的参数会在上下文中传播,即能通过BusinessActionContext对象在commit方法及cancle方法中取到该参数值

在这里插入图片描述

对原理及Demo有个大致的回顾,下面开始盘源码

源码分析

TCC资源注册

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全局事务

TCC模式业务调用方和AT模式一样,需要使用GlobalTransactional注解来开启全局事务,如图
在这里插入图片描述
业务方法执行时,最终会被AT模式源码分析中提到过的拦截器GlobalTransactionalInterceptor拦截,开启一个全局事务,获得全局事务id,即xid

具体代码是TransactionalTemplate的execute方法,execute方法如下
在这里插入图片描述

分为三步

  1. 开启全局事务beginTransaction(TM与TC通信并获得Xid)
    服务端接收全局事务开启请求的方法在DefaultCore的begin方法中,可以看到创建了一个GlabalSession
    在这里插入图片描述
  2. 执行业务方法
  3. 提交事务commitTransaction(TM与TC通信,发起事务提交请求)
  4. 执行completeTransactionAfterThrowing回滚操作(TM与TC通信,发起事务回滚请求)

这里就解决了第一个问题,TC是怎么知道该调用confirm方法还是rollback方法的?根据业务方调用的结果而定

无异常-TM向TC发出提交请求
有异常-TM向TC发出回滚请求

TCC拦截器-注册分支事务

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方法中,
看下代码
在这里插入图片描述
分为几步

  1. 根据xid取出GlabalSession
  2. 关闭Session,防止再有分支注册尽量
  3. 修改状态为提交中
  4. 如果可以异步提交,则异步提交,否则同步提交

看下同步提交代码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方法也解决了
基本思路就是

  1. TM发起提交请求(带有xid)
  2. TC通过xid找到全局session,再取出所有的分支session
  3. 通过分支session里的resourceId,去缓存中找到缓存的rpcContext对象,取出对应channel,建立通信

总结一下全局事务提交的大致流程

  1. 业务方调用微服务无异常,通过TM发起事务提交请求
  2. TC接收到事务提交请求后,通过Xid找到全局事务,再取出所有的分支事务
  3. 遍历分支事务,发出分支事务提交请求
  4. TCC资源管理器RM接收到提交请求后,从本地TCCResource缓存中根据resourceId取出对应方法bean,反射调用commit方法

全局事务回滚

全局事务回滚思路与全局事务提交过程基本一致,不再赘述

发布了43 篇原创文章 · 获赞 134 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/hosaos/article/details/89847554