设为首页 加入收藏

TOP

Seata 1.5.2 源码学习(事务执行)(一)
2023-07-25 21:33:59 】 浏览:41
Tags:Seata 1.5.2

关于全局事务的执行,虽然之前的文章中也有所涉及,但不够细致,今天再深入的看一下事务的整个执行过程是怎样的。

1. TransactionManager

io.seata.core.model.TransactionManager是事务管理器,它定义了一个全局事务的相关操作

DefaultTransactionManager是TransactionManager的一个实现类

可以看到,所有操作(开启、提交、回滚、查询状态、上报)都是调用TmNettyRemotingClient#sendSyncRequest()方法向TC发请求

2. GlobalTransaction

DefaultGlobalTransaction实现了GlobalTransaction,它代表一个全局事务

有两件事情需要留意,一是transactionManager是什么? 二是GlobalTransactionRole又是什么?

采用静态内部类的形式来构造单例,还记得DefaultRMHandler和DefaultResourceManager也都是通过静态内部类的形式构造单例

3. TransactionalTemplate

TransactionalTemplate是全局事务执行模板,所有业务逻辑都在其定义的模板方法中执行

io.seata.tm.api.TransactionalTemplate#execute()

现在整个过程清楚了,首先根据事务传播特性来创建一个事务对象,然后开启事务,执行业务逻辑处理,最后提交事务,如果业务执行过程中抛异常,则回滚事务。

现在有一个问题,什么情况下会进入TransactionalTemplate#execute(),或者说什么时候调用该方法?

要回答这个问题,又得从io.seata.spring.annotation.GlobalTransactionScanner说起,这个前面已经说过了,想了解的可以再看看之前那篇 https://www.cnblogs.com/cjsblog/p/16866796.html

从GlobalTransactionScanner说起就太长了,直接快进到GlobalTransactionalInterceptor拦截器吧

当被调用的方法上有@GlobalTransactional注解时,就会被拦截,从而进入GlobalTransactionalInterceptor#invoke(),在invoke()里会调用GlobalTransactionalInterceptor#handleGlobalTransaction(),于是顺利进入TransactionalTemplate#execute()

也就是说,当进入第一个@GlobalTransactional方法时,此时全局事务为空,于是创建一个角色为“GlobalTransactionRole.Launcher”的DefaultGlobalTransaction。当方法内部又调用了另一个@GlobalTransactional方法,于是再创建一个角色为“GlobalTransactionRole.Participant”的DefaultGlobalTransaction。以此类推,后面的都是事务“参与者”。

好了,现在事务已经创建,接下来就可以开启事务并执行业务逻辑处理了

可以看到,只有角色为“GlobalTransactionRole.Launcher”的线程才可以执行事务的开启提交回滚操作,而且这些操作的底层都是调用TransactionManager中的方法,最终是调用TmNettyRemotingClient#sendSyncRequest()方法向TC发送同步请求

最后,看一下什么时候回滚

catch捕获到异常就回滚

以上这些说的都是TM,因为是TM在控制整个全局事务的执行,至于RM本地事务的执行要看io.seata.rm.datasource.ConnectionProxy,这个在之前都讲过了

4. GlobalLockTemplate

GlobalLockTemplate是全局锁模板,是需要全局锁的本地事务的一个执行器模板

那么,在哪里用这个"TX_LOCK"线程变量呢?在BaseTransactionalExecutor#execute()

默认ConnectionContext中isGlobalLockRequire为false

现在就很清晰了,当方法上加了@GlobalLock注解后,进入GlobalLockTemplate#execute(),在当前线程上绑定局部变量TX_LOCK=true。当本地事务提交的时候,上下文(ConnectionContext)中isGlobalLockRequire为true,于是给TC发请求查询锁,如果这些数据没有被任何事务加锁,或者被当前事务加锁,则都算获取到锁了,如果被别的事务加锁了,则算获取锁失败。

总结一下锁互斥,分这么几种情况:

  1. 两个@GlobalTransactional方法之间,会在注册分支事务的时候检查全局锁,注册成功(获取锁成功)才能提交
  2. 两个@GlobalLock方法之间,会在事务提交前检查全局锁,获取到锁才能提交
  3. @GlobalTransactional方法与@GlobalLock方法之间,都是在提交前,一个是分支注册检查锁,一个是直接检查锁

还有一个问题,哪些数据会被加锁呢?这就要从io.seata.rm.datasource.exec.ExecuteTemplate#execute()说起了

长话短说,什么样的数据加锁取决于数据库,以及SQL语句,自行理解一下吧

5. 总结

1、Seata到底是如何实现分布式事务的?

  • 首先,每个业务系统都要引入seata的jar包,因此每个业务系统都是一个seata client,于是数据源被seata代理,同时所有方法添加拦截器,对加了@GlobalTransactional的方法进行拦截处理;
  • 其次,进入事务方法后,按照模板方法定义,在try...catch...finally中先创建事务并开启,接着执行业务处理,如果抛异常则回滚,如果顺利执行完成,则提交;
  • 再次,被调用的远程服务在其本地开启事务并执行,将业务处理和undo_log放在同一个事务中,然后向TC注册分支事务,成功后提交本地事务并向TC报告分支状态
  • 最后,业务顺利执行完或抛异常后TM向TC发请求可以提交或回滚全局事务了,TC向所有已注册的分支事务发送提交或回滚请求

总之,数据源代理和全局事务扫描是seata实现分布式事务的基础,而TM做的事情就是控制事务的执行,RM做的事就是处理好本地事务的执行,TC是协调器

2、Seata实现的全局事务,它的事务隔离级别是怎样的?会不会出现脏读、幻读、不可重复读?

先看脏读,在全局事务提交之前,分支事务早已提交,因此,默认情况下,其它的事务是可以读取到当前未提交的全局事务的数据的,故而,默认情况下会发生脏读。

举个例子,假设现在有一个全局事务A还没提交,但是其中的分支事务A1已经提交,A2还在没提交,这个

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇day02-搭建微服务基础环境01 下一篇java -- Object类和String类

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目