当前位置: 首页 > news >正文

@transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节

TCC分布式事务解决方案在开源界的主要实现为Byte-TCC、TCC-Transaction等。其中笔者了解较多并且业界使用率较高的为TCC-Transaction这一实现。

本文,我将带领读者对TCC-Transaction这一分布式事务框架进行一次源码解析,提高自己的阅读源码的能力,也希望能够对读者深入了解TCC-Transaction有所帮助。

源码下载

源码地址为 https://github.com/changmingxie/tcc-transaction,我们关注最新版本1.2.x。

源码下载后导入IDEA中,项目目录结构如下图:

495bc3e84961ef1d1041ccd528511946.png

模块及其对应职责说明如下:

102be70d99da6f88ea58bffc857731d9.png

项目核心模块为 tcc-transaction-core,它实现了TCC核心业务逻辑,也是本次源码解析的重点对象。

我们从Dubbo使用样例入手进行分析,关于如何使用TCC-Transaction的更多说明,请参照官方文档: 使用指南1.2.x[1]

从一个简单的样例入手

我们从一个调用案例入手开始进行分析,样例路径为

org.mengyun.tcctransaction.sample.dubbo.order.service.PaymentServiceImpl

b0db6f86d120102ef61b3087aefb83db.png

这段代码为模拟支付扣款操作,可以看到在方法上添加了@Compensable注解,它是TCC-Transaction框架的核心注解,作用为:开启tcc事务支持,注解可以设置一下参数

参数名描述
propagation事务传播属性,REQUIRED(必须存在事务,不存在则进行创建),SUPPORTS(如果有事务则在事务内运行),MANDATORY(必须存在事务),REQUIRES_NEW(不管是否存在是否都创建新的事务)
confirmMethodconfirm阶段方法实现
cancelMethodcancel阶段方法实现
transactionContextEditor设置transactionContextEditor
asyncConfirm是否使用异步confirm
asyncCancel是否使用异步cancel

解析注解@Compensable

看到了@Compensable注解以及对应的confirm、cancle方法,处于技术敏感,我们可以猜测在框架中一定存在切面逻辑对@Compensable进行拦截并处理;在切面逻辑中一定有对confirm、cancel方法的调用。从这个猜想出发,我们通过阅读相关代码去验证自己的猜想。

我们进入tcc-transaction-core模块的代码目录,目录结构如下:

ee0f2d2582ceee2b9d7c5efc437dfdd1.png

我们主要关注interceptor目录,该目录下的interceptor实现了对注解@Compensable的解析以及对事务的代理逻辑。

CompensableTransactionAspect

CompensableTransactionAspect切面主要实现了对@Compensable的解析以及对事务的代理。

276405759778204b05322b1f716a1f6c.png

CompensableTransactionAspect的实现类为ConfigurableTransactionAspect.java, 加载顺序order= Ordered.HIGHEST_PRECEDENCE(-2147483648)。

该切面对标注了@Compensable的方法进行拦截,通过@Around为业务方法添加环绕增强。可以看到具体的增强方法实现为CompensableTransactionInterceptor.interceptCompensableMethod(pjp);

CompensableTransactionInterceptor.interceptCompensableMethod(pjp);

接着上述的分析,我们看一下CompensableTransactionInterceptor.interceptCompensableMethod(pjp)的逻辑。

c74d671f0f46abd30bd7479e80da1d38.png

我们主要关注switch代码段

39255923d05384626615be19e475686c.png

当事务方法为ROOT方法(即分布式事务的主方法)时,执行rootMethodProceed(compensableMethodContext);方法为PROVIDER(提供者)方法时,执行providerMethodProceed(compensableMethodContext)。默认为消费者事务,则直接执行。

我们以此看一下这几种事务切面的执行逻辑。

rootMethodProceed(compensableMethodContext)

对于事务的Root方法,执行rootMethodProceed逻辑,代码逻辑:

d2092c8d3e854470a7b567a137d77cca.png

注意关注这段代码

// 执行完成之后会马上进到另外一个切面中去returnValue = compensableMethodContext.proceed();

当所有的切面都执行完成之后才会执行后续的逻辑,也就是真正执行业务方法。

该方法为一个典型的模板方法,对事务通过begin、commit、rollback进行了抽象。

我们进入三个方法详细的分析。

begin()

首先进入begin方法

80f0b8b15f21a144d3fe16752ad9ab13.png

0.首先声明并初始化一个分布式事务对象Transaction,标记为ROOT事务,事务初始状态为TRYING。这里采用了经典的状态机策略

39e916965a6b9e5414b4775542f4129c.png

1.将事务信息存储到数据源中,数据源可以是数据库、redis、zk等,可配置;TransactionRepository是具体的持久化策略的抽象

2.注册事务,在TransactionManager中,通过双向队列(Deque)实现事务栈功能,用来处理嵌套事务。通过对Deque声明为为ThreadLocal,所以对每个线程而言,事务栈都都是独立的

private static final ThreadLocal> CURRENT = new ThreadLocal>();

commit()

接着看一下commit()方法

02737cbf6f9bad71b19f01cbb1893e05.png

commit(boolean asyncCommit)方法执行事务的提交过程,具体提交逻辑在commitTransaction(transaction)中完成。

75704286a9a6bf9a5dd670349b352ea7.png

可以看到,在事务提交完成之后,对本地持久化的事务记录进行了物理删除,具体删除方式取决于持久化机制。感兴趣的同学可以自行查看 org.mengyun.tcctransaction.repository 目录下的实现。

rollback()

我们看一下方法rollback()是如何实现事务回滚逻辑的

3315d7cd464d0e4f72311d8e316ea0b2.png

和commit方法类似,在rollback(boolean asyncRollback)执行事务的回滚操作,具体的操作在rollbackTransaction(transaction)中执行:

f839aa4b8c8a0db032f7f6b38144a784.png

cleanAfterCompletion(transaction)

无论是否提交/回滚,最终都会执行cleanAfterCompletion(transaction)方法进行现场清理操作。

2223df8ab31f4ae76afe5117c42cae26.png

事务执行结束,从栈中弹出当前结束的事务。

providerMethodProceed(compensableMethodContext)

看完rootMethodProceed根事务切面逻辑,再来看提供者切面事务逻辑就好理解多了,方法逻辑如下:

926f1eeaf50db3b8a276384ce7399f50.png

这里进行小结,可以看到在provider类型的方法切面,对于远程的Participant,如果transaction的status为trying,则通过transactionManager.propagationNewBegin创建分支事务并执行被切方法逻辑;

如果是status为confirming或canceling,则会调用对应的confirm或cancel配置的方法,跳过被切方法
对于普通类型方法直接调用,normal类型的方法是封装了对远程dubbo接口方法调用逻辑的本地proxy方法,所以直接执行即可

ResourceCoordinatorAspect

ResourceCoordinatorAspect切面主要是为了执行资源协调,它的实现为ConfigurableCoordinatorAspect

50fd05c7fb727a5eabbef0e79296c421.png

ConfigurableCoordinatorAspect的职责为设置事务的参与者;在一个事务内,每个被@Compensable注解的方法都是事务参与者。

可以看到该切面的优先级为 Ordered.HIGHEST_PRECEDENCE + 1,order的数值大于CompensableTransactionAspect。由于 @Order中的值越小,优先级越高,因此切面ResourceCoordinatorAspect的优先级小于CompensableTransactionAspect。

从代码可以看出,设置事务参与者逻辑是通过ResourceCoordinatorInterceptor.interceptTransactionContextMethod方法执行的。

91045be6727a092375089117e7512aac.png

我们可以得知,在trying阶段,框架会把所有事务参与者加入到当前事务中去。

对于Root方法,先创建主事务,事务参与者包括Root方法对应的本地参与者及Normal方法对应的远程参与者;

对于Provider方法,首先通过主事务上下文创建分支事务,事务参与者包括Provider方法对应的本地参与者以及它所包含的Normal方法对应的远程参与者。而远程参与者又可以开启新的分支事务。

我们可以合理的猜想,如果事务嵌套的层级很多,一定会存在性能问题。

enlistParticipant(pjp)

我们详细看一下enlistParticipant(pjp)是如何生成的事务参与者对象。

05f4404e562867003255b5b55d5a8a65.png

从上述的代码逻辑中,我们可以得到结论,CompensableTransactionAspect开启事务,ResourceCoordinatorAspect对注解@Compensable进行解析,将confirm与cancel的具体逻辑设置到事务管理器中。

当上述两个切面都执行完成之后,开始执行try中的方法。 如果try成功则执行commit否则执行rollback。
每个分支事务最终被封装到Transaction的participants中,每个分布式事务都有一个自己的  ThreadLocal

我们再次回顾commit的逻辑,查看Transaction.commit()方法

[Transaction.java]publicvoid commit() {    // 对每一个分支执行提交操作    for(Participant participant : participants) {            participant.commit();    }}

participant就是切面ResourceCoordinatorAspect 添加的。我们再看一下participant.commit()的逻辑:

[Transaction.java]publicvoid commit() {    terminator.invoke(newTransactionContext(xid,                        TransactionStatus.CONFIRMING.getId()),                       confirmInvocationContext,                       transactionContextEditorClass);}

可以看到最终事务提交是通过invoke反射实现的,我们进入invoke逻辑

4e055a039db3cbe82a447ab25ad6de8c.png

最终通过method.invoke(target, invocationContext.getArgs())方法完成了真实的事务提交操作。

小结

到此我们对TCC-TRANSACTION的事务提交主流程进行了完整的分析。

通过分析我们可以知道TCC-TRANSACTION的核心逻辑是通过两个切面CompensableTransactionAspect、ResourceCoordinatorAspect 实现的。通过对事务进行包装与代理,实现了类似二阶段提交的分布式事务解决方案。

实际上,TCC-TRANSACTION还有一个重要的补偿逻辑我们还没有分析,它是基于定时调度实现的。

限于本文的篇幅,就不再继续展开。我将单独用一篇文章来对TCC-TRANSACTION的补偿过程进行分析,我们下文再会。

References

[1] 使用指南1.2.x: https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x

相关文章:

  • mysql双主_MySQL双主配置
  • apache kylin mysql_Apache Kylin 部署之不完全指南
  • linux mysql备份表_Linux命令:MySQL系列之十三--MySQL备份与还原(针对单张表SELECT备份重要章节)...
  • salt mysql_centos7 使用salt部署mysql
  • ios 二进制数组转成16进制字符串_【模拟】P1143进制转换
  • 删除mysql多个实例_mysql多表删除实例
  • mysql笛卡尔积效率_SQL优化 MySQL版 -分析explain SQL执行计划与笛卡尔积
  • mysql truncate 失败_mysql存储过程中truncate失效,该如何解决
  • sqlalchemy mysql 乱码_使用mysql+sqlalchemy中文乱码问题(mysql5.7+)
  • mysql 订单号主键_mysql为什么不建议使用订单号或者其他形式的业务单号作为主键?...
  • 设置系统同步 mysql_mysql主从同步设置
  • mysql自动增长恢复_mysql自动增长的有关问题,怎么恢复从1开始
  • kettle mysql jdbc_kettle 连接 mysql8
  • .mysql secret在哪_MySQL如何使用索引
  • json to mysql_JsonToMysql(JSON导入MySQL工具) V1.6 官方版
  • @angular/forms 源码解析之双向绑定
  • Angular2开发踩坑系列-生产环境编译
  • angular组件开发
  • Apache的80端口被占用以及访问时报错403
  • JavaScript服务器推送技术之 WebSocket
  • Java到底能干嘛?
  • JSDuck 与 AngularJS 融合技巧
  • PHP的Ev教程三(Periodic watcher)
  • SQLServer之创建数据库快照
  • WePY 在小程序性能调优上做出的探究
  • 从输入URL到页面加载发生了什么
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 悄悄地说一个bug
  • 什么是Javascript函数节流?
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • ​低代码平台的核心价值与优势
  • !!java web学习笔记(一到五)
  • ###C语言程序设计-----C语言学习(6)#
  • #define
  • #if #elif #endif
  • #Linux(Source Insight安装及工程建立)
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (poj1.2.1)1970(筛选法模拟)
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (独孤九剑)--文件系统
  • (二)fiber的基本认识
  • (附源码)ssm码农论坛 毕业设计 231126
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (算法)Travel Information Center
  • (算法设计与分析)第一章算法概述-习题
  • (推荐)叮当——中文语音对话机器人
  • (转)大型网站的系统架构
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • .bat批处理(六):替换字符串中匹配的子串
  • .libPaths()设置包加载目录
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .net(C#)中String.Format如何使用
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args