2.1 TM的执行流程

208 阅读7分钟

TM(TransactionalManager)的作用是:

1.定义全局事务的边界

2.开启全局事务

3.提交/回滚全局事务

我们来看一下TM的实现逻辑。

TM的核心类是 GlobalTransactionalInterceptor 他是在GlobalTransactionScanner 初始化的时候创建的。看一下他的继承结构:

实现了MethodInterceptor 接口, 代表了他拥有了拦截方法的能力

实现了 ConfigurationChangeListener 接口,代表了他可以实现配置动态变更

我们看一下MethodInterceptor 接口的方法:

实现逻辑很简单, 判断方法上是不是有全局事务注解或全局锁注解,如果有就执行相应的代理方法, 如果没有执行原方法。全局锁适用于那些不想纳入全局事务管理, 但需要和seata进行读写隔离的操作,使用全局锁注解就可以避免全局事务的提交注册等开销。由于本文是描述的全局事务的执行类型,暂时不分析全局锁的执行,后续有文章单独描述此类型。

本文具体分析全局事务的执行流程: 很显然这是一个典型的模仿方法, 如果内部报错,在catch中统一处理了。针对不同类型的异常有具体的实现措施,这个异常处理是可以自定义的,实现FailureHandler接口, 在创建GlobalTransactionScanner 的时候,当做参数传进去就行。后续有文章来分析默认的错误处理器。

先简单看一下transactionTemplate 的执行流程(隐藏掉了事务传播级别,否则截不全代码): 注释写的挺详细了,大致描述一下执行流程:

  1. 获取当前的事务对象,对象是在GlobalTransactionInterceptor 中使用匿名内部类传入的。
  2. 创建或获取全局事务对象
  3. 针对事务传播级别单独处理
  4. 开启一个全局事务
  5. 执行业务代码
  6. 如果报错回滚事务,并终止操作
  7. 业务正常执行结束,提交全局事务
  8. 收拾尾声

1 获取当前事务对象

看一下实现 都是一些基础代码,获取注解中的信息。 TransactionInfo 有几个关键属性, 事务超时时间,传播级别,回滚策略。

2 创建或获取全局事务对象

为什么说创建或获取全局事务对象,因为有可能在服务参与者的方法上也有这个注解。看一下具体的实现逻辑: 逻辑写的很简单,主要是根据Context中的XID来判断当前事务是否在全局事务中,关于服务链路中的XID传递,我们有单独的文章来叙述, XID是放在THreadLocal中的,随时取就可以。 多说一句, seata全局事务有两个角色, 事务发起者和参与者, 参与者有很多事不用干,新创建的就是发起者, 有XID的就是参与者, 这无须多言。

3. 针对事务传播级别单独处理

源码献上 先看几个方法的实现逻辑: 此方法的作用是将此次事务从全局事务的链路中暂停调, 实现方式是解绑XID。

此方法作用很简单,判断是否存在XID,即判断是否在全局事务中。

3.1 NOT_SUPPORTED : 不支持事务, 处理方式为暂停事务,执行原方法 3.2 REQUIRES_NEW : 新开启一个事务,独立于当前事务之外,处理方式为暂停事务,解绑XID 意味着 将此服务从本次全局事务链路中剔除,我们项目中的生成单号应走此逻辑(可跳号,不回滚) 3.3 SUPPORTS:如果未被纳入全局事务,执行原方法 3.4 REQUIRED: 默认事务传播级别, 啥也不干 3.5 NEVER:如果在全局事务中,直接抛异常 3.6 MANDATORY:强制要求增加事务,校验XID,即本事务必须被全局事务调用

4. 开启一个全局事务

1.如果当前角色不是发起者,不开启全局事务,校验一下XID即可 2. 校验当前全局事务对象以及threadlocal中的xid是否有值,有值抛异常, 因为若当前线程已存在全局事务不可重复开启 3.TM开启全局事务 4. 将全局事务状态置为开始状态 5. 将xid绑定至本地线程

我们着重看一下第三部 TM开启全局事务 传入参数为 name(注解中设置的name值),超时时间,返回数据为xid(String) 1.创建一个GlobalSession, 2.设置生命周期监听器, 3.开启全局事务 4.使用了guava的事件处理机制,发布了一个事件,一种观察者模式的优雅实现,有兴趣可以看一下。

关于GlobalSession的分析我们单独起文章描述,这里可能描述不开,而且也会使读者分心。 我们只需要知道,GLobalSession 是seata 协调器 维护的很重要的部分, 它实现SessionLifecycle接口,提供begin,changeStatus,changeBranchStatus,addBranch,removeBranch等操作session和branchSession的方法。 1.创建GlobalSession,主要是创建了XID,我们看一下XID的创建方式。 通过ip+:+端口+:+随机数的方式生成。 我们主要关注一下这个随机数的生成 此处实现参考了雪花算法。使用了序列号来保障同一毫秒内的生成顺序。好像1.3算法就彻底引入雪花算法了。workerId 为机器id,通过ip运算得来。 开启全局事务,主要是初始化了几个属性 并且调用SessionLifecycleListener中的onBegin方法, onBegin 方法主要是调用了writeSession方法, 将全局事务持久化。 目前支持 文件, 数据库,缓存三种方式, 我们采用的是数据库 我们看一下实现: 此方法支持,全局事务和分支事务的 增删改。

注册的事件主要是给metrics 用的,一个监控,后续我们有专门的文章来介绍他,这里先不写了,防止诸君分心。

先简单总结一下开启全局事务的内容:

  1. 生成xid
  2. 将全局事务持久化到数据库
  3. 发布全局事务事件

5.执行业务代码

这没啥说的

6.如果报错回滚事务,并终止操作

看一下这块代码,首先判断 当前异常是否符合回滚规则, 符合触发回滚操作,否则提交事务。 我们继续追一下回滚的代码:io.seata.tm.api.DefaultGlobalTransaction#rollback

首先会判断当前角色是否为参与者,若是参与者是不执行TM的回滚操作的。 回滚失败重试次数 关于事务的回滚流程, 我们单起文章描述

7. 业务正常执行结束,提交全局事务

提交的代码和回滚逻辑是一样的,都是transactionManager 来触发的。我们单起文章描述。

8. 收拾尾声

1.执行钩子方法, 2.清楚钩子方法 3.恢复全局事务,这里主要是针对事务传播级别的,内部把xid再绑定上

总结:

TM是全局事务的核心,内部主要依赖了XId,GlobalSession,以及transactionManager 协助完成的操作。

  1. 获取当前的事务对象,对象是在GlobalTransactionInterceptor 中使用匿名内部类传入的。
  2. 创建或获取全局事务对象,主要是根据XID来创建,内部关键是生成XId的逻辑
  3. 针对事务传播级别单独处理,主要是通过绑定/解绑xid, 使RM失去XID实现的
  4. 开启一个全局事务 使用transactionManager来操作,并持久化
  5. 执行业务代码
  6. 如果报错回滚事务,并终止操作, 使用transactionManager来操作,有重试次数限制
  7. 业务正常执行结束,提交全局事务,使用transactionManager来操作,有重试次数限制
  8. 收拾尾声,恢复第三步解绑的XId