源码解读 - Tcc-Transaction框架(三)dubbo支持

前言

回到master-1.2.x分支,继续浏览git log,这次选择update the version to 1.2.0 这次提交,注意这个分支编译会报错,调整dubbo版本为2.5.3才行。

正文

项目整体结构:

这个版本中,增加了tcc-transaction-dubbo模块。同时也调整了一些类所在模块、增加了更多的扩展。

tcc-transaction-api

迁移:将@Compensable迁移进来。

增加:

  • TransactionContextEditor:内部管理TransactionContext,对其get和set。具体实现有
  • MethodTransactionContextEditor:从方法的参数列表中获取TransactionContext
  • DubboTransactionContextEditor:从dubbo上下文中获取TransactionContext
  • NullableTransactionContextEditor:有TransactionContext为空的情况,做空值处理
  • Propagation:定义传播行为,上篇文章我们提到过。

1.1.0版本将分支事务以多线程方式进行隔离,在这个版本中,我们会看到作者对事务的传播行为做了明确定义,以及处理。目前支持REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW四种传播行为。

tcc-transaction-core

核心代码越来越充实。

其中common包、recover包、repository包、serializer包、support包以及utils包变化不大,略过。

  • context包:定义MethodTransactionContextEditor,前面提到过,本地服务获取TransactionContext的实现。
  • interceptor包:将切面方法抽象,迁移一部分功能到core中。

并未增加新类,但部分类中有调整,后面涉及到再细讲。

tcc-transaction-dubbo

为集成dubbo做的适配。

配置文件,注册ProxyFactory、定义了<dubbo:provider proxy="tccJavassist"/>,那么默认使用框架中自定义的javassist方式的实现。这里的实现粗略的来讲,是消费端在生成Proxy时,加入TCC相关信息 ;由proxy.javassist包可研究具体实现过程,这部分有时间会结合dubbo的Proxy、动态代理、SPI等知识点单独分析。

context包:前面提到过,dubbo服务如何获取TransactionContext。

tcc-transaction-server

里面实现了一个后台管理的服务,可以监控事务处理信息,这部分有时间再分析。

tcc-transaction-spring

变化不大,配置文件就是一些bean的注入。两个aop类,继承了core模块的抽象类,提供bean的实例和注入。

tcc-transaction-tutorial-sample

还是dubbo的使用例子,这里会和tcc-transaction-dubbo模块一起,单独分析。

tcc-transaction-unit-test

多了一个性能测试的类,有兴趣可以运行一下。

核心功能测试还是集中在TransferServiceTest类。

从testTransfer()方法追进去,发现org.mengyun.tcctransaction.unittest.client.AccountServiceProxy#transferTo(...)方法取消了多线程的方式,改为同步调用,具体原因我们结合core模块细看下。

细看AccountServiceProxy类,所有的异步调用都被改为同步调用,直接进入AccountService类,里面的方法实现没有变化,不过@Compensable注释中,增加了propagation属性的设置,那么我们进入aop的具体实现看一下。

CompensableTransactionInterceptor

它的主要作用是:初始化事务信息,控制各阶段事务状态,控制各阶段操作。

变化:

1、从@Compensable中获取了配置的传播行为,transactionManager中定义了isActive属性,标识事务是否开启。

2、根据isActive、propagation、transactionContext来判断事务是否合法。

3、再根据propagation, isTransactionActive, transactionContext获取方法类型MethodType(注意这里与1.1.0中区别);传播行为的定义与Spring中的概念保持一致。

4、根据不同methodType,进行不同的处理,ROOT事务则管控流程数据及阶段方法调用。

5、PROVIDER分支事务则处理相应事务信息变更propagationNewBegin(),在第confirm和cancel阶段,则会执行分支事务的对应commit()和rollback()方法。

我们进入TransactionManager看一下具体实现。

首先,之前的ThreadLocal<Transaction>变更为队列结构,相应的也增加了入队、出队方法。

private static final ThreadLocal<Deque<Transaction>> CURRENT = new ThreadLocal<Deque<Transaction>>();

而判定事务是否开启的逻辑,则是CURRENT队列不为空。 

其次,commit()方法中,流程依然是获取当前线程的事务信息transaction,更新状态、更新持久化信息、调用transaction.commit(),完成后删除transaction。

这里有个疑问,现在的流程是分支事务处理,既然这个版本放弃使用多线程,那么获取当前线程的transaction,会不会获取到Root的Transaction,来进行commit、delete等操作?

看一下begin方法,流程不变,变化的是存储介质,将transaction存入了队列。

下面直接贴出入队、出队的实现。

也就是说,当执行ROOT事务处理流程时,创建了一个Transaction,而执行分支事务时,之前看过流程,依然会先创建一个Transaction,也就是有两次入队操作。两个Transaction都存储于同一个队列,而执行commit或rollback时会读取队列信息,最终执行完成除了删除持久化的Transaction,还会从队列中将Transaction弹出(注意providerMethodProceed方法中的finally块)。

从执行顺序角度看,分布式事务的T\C\C三个阶段,都是由ROOT事务位置发起的,属于FIFO,使用队列很合适。这样就可以舍弃多线程部分的逻辑,降低代码复杂度。

ResourceCoordinatorInterceptor

这里的作用,依然是封装Participant。我们看一下分支处理流程:

值得注意的是,这里舍弃了对于methodType的判断,改为事务状态判断,TRYING\CONFIRMING\CANCELLING.并且只有TRYING阶段进行了处理。

这样的变化也是合理的,这个切面的主要工作就是封装@Compensable注解中配置的信息,这种操作执行一次其实就行了,这里选择了再TRYING阶段处理。

其中,transaction只是用来获取xid.getGlobalTransactionId,由于不同事务的全局事务id相同,也就不用考虑transaction取到的是否为当前事务的对象了。

区别在FactoryBuilder的调用,这部分做了什么。

if (FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()) == null) {
    FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().set(new TransactionContext(xid, TransactionStatus.TRYING.getId()), pjp.getTarget(), ((MethodSignature) pjp.getSignature()).getMethod(), pjp.getArgs());
}

我们拆解一下这一串调用:

FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs())

先通过factoryOf方法需要参数为@Compensable注解的transactionContextEditor属性(它有三个实现method\dubbo\nullable),属性默认值为nullable实现。

而查看赋值的代码,都是dubbo实现。

回来看一下factoryOf(...)方法的实现:享元+单例模式获取beanFactory资源,再通过beanFactory来创建参数类型的单例工厂对象SingeltonFactory<T>。(beanFactory是通过spring模块org.mengyun.tcctransaction.spring.support.SpringPostProcessor#onApplicationEvent 方法中注册进去的。)

SingeltonFactory<T>是内部静态类,单例模式的工厂,通过它来获取T类的实例对象,也就是TransactionContextEditor的具体实现实例(代码就不贴了)。总而言之,通过getInstance()方法获取到了xxxTransactionContextEditor的实例。

由于unit-test模块的@Compensable注解中,并没有配置transactionContextEditor属性,也就是默认的NullableTransactionContextEditor。而Nullablexxx中并未做任何操作,我们就看一下MethodTransactionContextEditor的实现。

只有get和set方法,目的是从参数列表中获取、设置TransactionContext对象。

而DubboTransactionContextEditor的实现,则是从RpcContext中获取、设置TransactionContext对象。另外,对于dubbo的这种实现,是由于在定义dubbo服务时,如果要集成tcc,不需要修改api,框架通过RpcContxt来传递TransactionContext对象。(其实这种信息传递,有点类似日志的链式追踪,将xid、status传递给分支事务,分支事务本地创建transaction信息是携带上root事务信息,进而产生关联)。

但是,http模式的集成例子中,TransactionContext的传递则是通过方法参数。

 

我们刚刚分析了,ROOT事务和分支事务,都是通过TRYING阶段来封装信息的,由于分支事务必须包含TransactionContext信息才能创建transaction信息。

1、在Root事务阶段,第一个aop负责开启事务,并且此阶段不需要使用TransactionContext,第二个aop就除了封装当前事务的信息外,还封装了分支事务的必要信息,

2、在分支事务阶段,第一个aop负责开启分支事务,此时需要TransactionContext,并且在之前已经创建好了,这里还是使用FactoryBuilder.facotyOf()...get()方式获取。

这样,ResourceCoordinatorInterceptor这个切面方法的流程就清晰了。


结尾

总结一下1.2.0版本:

  1. 引入传播行为Propagation的概念,用于区分事务类型,进而根据事务类型来决定,是否要开启新事务、沿用当前事务、不做事务处理
  2. 在TransactionManager中使用队列QUEUE,来代替之前多线程方式隔离同一事务下不同Transaction信息对象的处理方式。
  3. 在封装Participant的切面中,只在TRYING阶段进行,ROOT事务阶段会影响分支事务阶段的参数信息。

可扩展解读:

  1. 分析tcc-transaction-dubbo模块、tcc-transaction-tutorial-sample模块的实现思路。
  2. 分析补偿逻辑、tcc-transaction-server模块的实现思路。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值