源码解读 - Tcc-Transaction框架(一)初探实现思路

前言

一个成熟的开源项目,其代码量还是较为客观的,拿到手通常不知如何下手。那么一套清洗的分析思路尤为重要,帮助自己快速理解作者思路。

1、首要工作是fork一份源码,在这个基础上做一些诸如,添加注释、debug等操作比较方便。

2、查看作者的提交历史,也就是通过git历史来查看这个项目的进化过程。

第一步以及clone到本地,可自行百度如何操作。我们从第二步开始。


正文

1、找到感兴趣的历史版本

以IDEA为例,通过Version Control-->Log,找到git历史记录。翻到最后,看到较早一次带有版本号的提交,既然作者在备注中特意创建了版本号,那么这版应该较为重要。

那么久以这次提交创建一个分支出来,右键New Branch,自定义一个分支名(updateTo1.0.3),然后切到这个分支上。

2、分析1.0.3版本

先看一下项目结构,一共包含四个模块:

2.1、tcc-transaction-api模块

只定义了TransactionContext、TransactionStatus、TransactionXid 三个类,作者编码习惯不错,基本上可以根据代码简单理解含义。

  • TransactionXid:实现java-transaction-xa(java事务规范)中的Xid,用于生成全局唯一标识。
    • globalTransactionId( 表示 全局事务的 唯一标识)
    • branchQualifier( 表示 当前本地事务在 全局事务中的一个分支号)
  • TransactionStatus:事务状态,预置了三个TRYING准备、CONFIRMING提交、CANCELLING取消回滚
  • TransactionContext:成员包含xid和status,另外还有一个ConcurrentHashMap类型的attachments,看名字是附加信息。

2.2、tcc-transaction-core模块

这个模块就是核心代码了,定义的内容较多,咱们一点点看。

  • utils包,肯定是工具集,一个是序列化工具SerializationUtils、一个是字符串工具StringUtils,比较简单。
  • BeanFactory:定一个了一个getBean方法,根据类型获取Bean。
  • BeanFactoryAdapter:持有静态BeanFactory对象,代理了BeanFactory类。set方法给BeanFactory赋值、代理了getBean()方法。
  • InvocationContext:调用上下文,成员包含Class targetClass目标类型、String methodName方法名、Class[] parameterTypes参数类型数组、Object[] args参数,作用肯定是记录信息,供其他位置调用,看情形tcc框架里应用了不少反射。
  • MethodType:方法类型,定义了四种ROOT, CONSUMER, PROVIDER, NORMAL,如何分类的,后面再看。
  • Terminator:终结者,里面持有两个InvocationContext类型成员,看名字是confirm和cancel的两个上下文信息;
    • commit()和rollback()两个方法,内部都调用了invoke()方法,传参则是对应的InvocationContext类型成员。
    • invoke()方法,根据InvocationContext中包含的信息,反射方式调用method.invoke(target, invocationContext.getArgs())。
  • Participant:参与者,里面包含一个xid、一个Terminator。commit()和rollback()方法是代理了Terminator对应的方法。
  • Transaction:名字叫事务,应该比较重要,看内部的定义,是记录信息的。
    • 成员有xid、status、transactionType、retriedCount(肯定有重试逻辑)、List<Participant> participants参与者列表。
    • commit()和rollback()方法,内部是遍历participants列表,执行commit和rollback方法。
  • TransactionConfigurator:接口,定义获取TransactionManager和TransactionRepository的方法。
  • TransactionManager:成员有TransactionConfigurator和transactionMap(按线程记录transaction对象)
    • 方法有begin()\commit()\rollback()、propagationNewBegin(...)\propagationExistBegin(...)
    • 方法内部情况看来,有持久化、缓存信息的操作。
    • 那么TransactionManager就是用于在TCC的各个阶段,管理事务信息的。
  • TransactionRepository:接口,事务仓库。定义CRUD操作。
  • TransactionType:枚举,定义了两个类型ROOT、BRANCH

2.3、tcc-transaction-spring模块

针对spring的模块,看看里面做了什么。

  • 这里包含一个sql脚本:定义了一个表,自己配个数据库,创建一下肯定用得到。
  • recover包:恢复job和具体实现,恢复逻辑肯定是复用主逻辑,这里先略过。
  • support包:
    • TccApplicationContext:@Component、实现BeanFactory, ApplicationContextAware。
    • TccBeanPostProcessor:@Component、实现ApplicationListener<ContextRefreshedEvent>
    • TccTransactionConfigurator:@Component、实现TransactionConfigurator
  • utils包:
    • CompensableMethodUtils:
      • calculateMethodType()根据参数获取方法类型MethodType
      • getTransactionContextParamPosition()获取TransactionContext类型的参数位置
      • getTransactionContextFromArgs()获取TransactionContext类型参数
    • ReflectionUtils:
      • getDeclaringType()根据参数信息,找到类型对象
  • @Compensable:注解类,“可补偿的”。定义confirmMethod和cancelMethod两个属性
  • JdbcTransactionRepository:jdbc事务仓库,应该是用于数据库记录的(继承JdbcDaoSupport、实现TransactionRepository,内部是操作数据库的)。

下面是重点

  • TccCompensableAspect:切面,针对@Compensable注解的方法。切面方法为interceptCompensableMethod().
  • TccTransactionContextAspect:切面,针对 第一个参数为TransactionContext类型的方法 或 @Compensable注解的方法。切面方法为interceptTransactionContextMethod()

两个切面都实现了Ordered接口,并定义了order属性,spring中order值标识了切面执行顺序,order越小,执行越靠前

  • tccCompensableAspect.order= Ordered.HIGHEST_PRECEDENCE
  • tccTransactionContextAspect.order = Ordered.HIGHEST_PRECEDENCE + 1

因此TccCompensableAspect的优先级高于TccTransactionContextAspect。

2.3.1、TccCompensableAspect

我们分析下TccCompensableAspect都做了什么,直接看一下interceptCompensableMethod方法。

整体思路清晰:前面获取methodType,后面switch根据methodType进行不同的流程处理。

rootMethodProceed()方法处理流程:

  1. begin()方法中,创建Transaction对象,记录事务类型、状态,缓存、持久化Transaction信息对象。
  2. pjp.proceed();正常业务逻辑。
  3. 正常执行,进行commit()提交事务

看一下org.mengyun.tcctransaction.TransactionManager#commit内部做了什么。

除了更新持久化数据的状态,重点调用了transaction.commit();并在调用完成后,删除了持久化信息。

org.mengyun.tcctransaction.Transaction#commit,实际是调用了Participant“参与者”内的commit方法。

而Participant是代理了Terminator。

org.mengyun.tcctransaction.Terminator#commit,最终是调用InvocationContext confirmInvocationContext中记录的方法。

整理下调用链:

aop 在执行完业务方法后--> 执行transactionManager.commit()-->执行transaction.commit()-->执行participant.commit()-->执行terminator.commit()-->执行invoke(confirmInvocationContext)。

一步步,最终执行的是@Compensable注解中定义的confirmMethod

异常捕获后执行rollback()回滚事务,其内部流程与commit相似,不再赘述。

 

看过了Root类型方法的处理,我们在看一下PROVIDER类型方法的处理。

根据org.mengyun.tcctransaction.spring.utils.CompensableMethodUtils#calculateMethodType方法的描述,PROVIDER类型的方法,是事务开启后,再次调用的事务方法,属于调用链中的一个节点,类似于分支事务的概念。

providerMethodProceed()方法根据事务状态,三个阶段进行不同的处理:

  • TRYING阶段:事务第一阶段;propagationNewBegin(),需要开启分支事务,也就是定义分支事务信息newTransaction,继承ROOT事务的全局标识,设置事务类型为TransactionType.BRANCH。缓存、持久化。
  • CONFIRMING阶段:事务第二阶段;propagationExistBegin(),需要将分支事务的状态进行更新,通过xid找到分支事务信息,设置status,更新缓存、持久化。然后执行注册的confirm操作,也就是commit().
  • CANCELLING阶段:事务第二阶段;propagationExistBegin(),更新分支事务信息;执行注册的cancel操作,也就是rollback().

上面有个疑问,Participant“参与者”中的成员Terminator是如何封装进去的。

2.3.2、TccTransactionContextAspect

我们分析下TccTransactionContextAspect都做了什么,直接看一下interceptTransactionContextMethod方法。

切面方法中,在执行真正方法前,做了事务控制,进行了一系列的操作,目的是拿到methodType,根据methodType做不同处理,看方法名就是封装Participant。

看一下generateAndEnlistRootParticipant:(generateAndEnlistRootParticipant和generateAndEnlistProviderParticipant方法内容一样)

运用很多反射,拿到切面和compensable注解中的信息,封装为InvocationContext,最终都封装到Participant,并存入transaction中,还进行了持久化。

我们在看一下generateAndEnlistConsumerParticipant,这个是对CONSUMER类型的方法进行处理。

其逻辑与ROOT、PROVIDER类型的方法一致,只不过是CONSUMER类型的方法,没有@Compensable注解,因此需要将自身方法封装为confirm和cancel的上下文对象,最终也是封装为Participant,并存入transaction中,还进行了持久化。

2.4、tcc-transaction-unit-test模块

分析完TCC核心实现,接下来进入测试阶段,验证之前的猜测是否正确。

模块分为两部分,main中定义了业务流程,test定义了单元测试。配置文件需要调整下数据库信息,改成本地的(这里依赖spring模块的db.sql脚本)。

简单描述下测试模拟的业务流程,场景为经典的转账,A给B转账N元,A账户减少N元,B账户增加N元。

  • org.mengyun.tcctransaction.unittest.client.TransferService:定义了A账户的操作
  • org.mengyun.tcctransaction.unittest.service.AccountServiceImpl:定义了B账户的操作。
  • org.mengyun.tcctransaction.unittest.client.AccountServiceProxy:代理了AccountServiceImpl,可以理解为TransferService所在A的服务,需要远程调用AccountService所在B服务,中间通过A服务中的AccountServiceProxy代理调用B服务。
  • 其他的类都是模拟持久化层。org.mengyun.tcctransaction.unittest.utils.UnitTest还可以配置异常,来验证回滚,

以最简单的单元测试为例org.mengyun.tcctransaction.unit.test.TransferServiceTest#testTransfer

初始化数据,进行方法调用,验证结果。

org.mengyun.tcctransaction.unittest.client.TransferService#transfer 带有@Compensabel注解,方法调用入口,我们可以理解为ROOT事务的入口方法,执行流程:

  1. 执行transferService.transfer(1, 2, 50);后命中切面。
  2. 先进入TccCompensableAspect,通过begin封装事务信息,在调用pjp.proceed();
  3. 再进入TccTransactionContextAspect,在这里封装Participant“参与者”,也就是@Compensabel注解中配置的confirmMethod和cancelMethod信息
  4. 真正调用pjp.proceed()。
  5. 回到TccCompensableAspect中进行commit或cancel操作。

至此,集成了TCC-Transaction的服务执行完成,做到了TCC三个阶段信息、流程的管理。


遗留问题

org.mengyun.tcctransaction.spring.TccTransactionContextAspect#generateAndEnlistConsumerParticipant方法中,在单元测试过程中出现循环调用,这里原因未找到。不过我们还要继续往下分析源码,这里先不纠结。

原因:只是调用三次,由于补偿job干扰,执行try后,执行commit但是这里出现异常,再执行cancel,感觉上是不停的调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值