TM(TransactionalManager)的作用是:
1.定义全局事务的边界
2.开启全局事务
3.提交/回滚全局事务
我们来看一下TM的实现逻辑。
TM的核心类是 GlobalTransactionalInterceptor 他是在GlobalTransactionScanner 初始化的时候创建的。看一下他的继承结构:
实现了MethodInterceptor 接口, 代表了他拥有了拦截方法的能力
实现了 ConfigurationChangeListener 接口,代表了他可以实现配置动态变更
我们看一下MethodInterceptor 接口的方法:
实现逻辑很简单, 判断方法上是不是有全局事务注解或全局锁注解,如果有就执行相应的代理方法, 如果没有执行原方法。全局锁适用于那些不想纳入全局事务管理, 但需要和seata进行读写隔离的操作,使用全局锁注解就可以避免全局事务的提交注册等开销。由于本文是描述的全局事务的执行类型,暂时不分析全局锁的执行,后续有文章单独描述此类型。
本文具体分析全局事务的执行流程:
很显然这是一个典型的模仿方法, 如果内部报错,在catch中统一处理了。针对不同类型的异常有具体的实现措施,这个异常处理是可以自定义的,实现FailureHandler接口, 在创建GlobalTransactionScanner 的时候,当做参数传进去就行。后续有文章来分析默认的错误处理器。
先简单看一下transactionTemplate 的执行流程(隐藏掉了事务传播级别,否则截不全代码):
注释写的挺详细了,大致描述一下执行流程:
- 获取当前的事务对象,对象是在GlobalTransactionInterceptor 中使用匿名内部类传入的。
- 创建或获取全局事务对象
- 针对事务传播级别单独处理
- 开启一个全局事务
- 执行业务代码
- 如果报错回滚事务,并终止操作
- 业务正常执行结束,提交全局事务
- 收拾尾声
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 用的,一个监控,后续我们有专门的文章来介绍他,这里先不写了,防止诸君分心。
先简单总结一下开启全局事务的内容:
- 生成xid
- 将全局事务持久化到数据库
- 发布全局事务事件
5.执行业务代码
这没啥说的
6.如果报错回滚事务,并终止操作
看一下这块代码,首先判断 当前异常是否符合回滚规则, 符合触发回滚操作,否则提交事务。
我们继续追一下回滚的代码:io.seata.tm.api.DefaultGlobalTransaction#rollback
首先会判断当前角色是否为参与者,若是参与者是不执行TM的回滚操作的。
回滚失败重试次数
关于事务的回滚流程, 我们单起文章描述
7. 业务正常执行结束,提交全局事务
提交的代码和回滚逻辑是一样的,都是transactionManager 来触发的。我们单起文章描述。
8. 收拾尾声
1.执行钩子方法, 2.清楚钩子方法 3.恢复全局事务,这里主要是针对事务传播级别的,内部把xid再绑定上
总结:
TM是全局事务的核心,内部主要依赖了XId,GlobalSession,以及transactionManager 协助完成的操作。
- 获取当前的事务对象,对象是在GlobalTransactionInterceptor 中使用匿名内部类传入的。
- 创建或获取全局事务对象,主要是根据XID来创建,内部关键是生成XId的逻辑
- 针对事务传播级别单独处理,主要是通过绑定/解绑xid, 使RM失去XID实现的
- 开启一个全局事务 使用transactionManager来操作,并持久化
- 执行业务代码
- 如果报错回滚事务,并终止操作, 使用transactionManager来操作,有重试次数限制
- 业务正常执行结束,提交全局事务,使用transactionManager来操作,有重试次数限制
- 收拾尾声,恢复第三步解绑的XId