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

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还在没提交,这个时候另一个全局事务B是可以读取到A1已经提交的数据的,也就是在全局事务B中读到了还未提交的全局事务A的数据,这就是脏读。

那么,如何避免脏读呢?

思路是这样的:首先要让Seata意识到这个SQL语句执行时锁,光知道需要锁还不行,还得让它在执行的时候检查是否获取到锁了。一个SELECT语句需要锁就是将其改写成SELECT ... FOR UPDATE的形式,检查锁的话@GlobalTransactional或@GlobalLock都可以办到。于是,解决版本就有两个:

  • SELECT ... FOR UPDATE  +  @GlobalTransactional
  • SELECT ... FOR UPDATE  +  @GlobalLock

综上所述,分支事务在提交前先进行分支注册获取全局锁,在全局事务提交成功后释放全局锁。此时,其它全局事务可以读取到已提交的分支事务的数据,但这是当前全局事务还未提交,于是出现脏读。办法也很简单,首先select加for update,其次业务方法加@GlobalTransactional或@GlobalLock注解。

同理,默认是可能出现幻读和不可重复读的,它俩属于是脏写,究其原因还是因为跨数据库了,seata搞了个全局锁,这就相当于将业务中几个不同的数据库看成一个数据库,全局锁就相当于这个大数据库中的行级锁,因此解决办法还是一样

不得不说,Seata真的是一个优秀的分布式事务框架

3、AT模式、TCC模式、Saga模式、XA模式的区别

AT模式是基于支持本地事务的关系型数据库

TCC模式不依赖于数据库的事务支持,另外TCC没有全局锁,也就没有锁竞争,故而效率比AT模式高

Saga模式是seata提供的长事务解决方案

XA模式以 XA 协议的机制来管理分支事务的一种事务模式

Seata 1.5.2 源码学习(事务执行)的更多相关文章

  1. Seata 1.5.2 源码学习

    文章有点长,我决定用半个小时来给您分享~ 基于Seata 1.5.2,项目中用 seata-spring-boot-starter 1. SeataDataSourceAutoConfiguratio ...

  2. ThinkPHP5.0源码学习之执行应用

    一.应用启动 在/thinkphp/start.php文件中,用一句代码App::run()->send();实现应用的启动. // 执行应用 App::run()->send();   ...

  3. Seata Server 1.5.2 源码学习

    Seata 包括 Server端和Client端.Seata中有三种角色:TC.TM.RM,其中,Server端就是TC,TM和RM属Client端.Client端的源码学习上一篇已讲过,详见 < ...

  4. 框架源码系列十一:事务管理(Spring事务管理的特点、事务概念学习、Spring事务使用学习、Spring事务管理API学习、Spring事务源码学习)

    一.Spring事务管理的特点 Spring框架为事务管理提供一套统一的抽象,带来的好处有:1. 跨不同事务API的统一的编程模型,无论你使用的是jdbc.jta.jpa.hibernate.2. 支 ...

  5. Spring5.0源码学习系列之事务管理概述

    Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述 1.什么是事务? 事务就是一组原子性的SQL操作 ...

  6. Spring 源码学习笔记11——Spring事务

    Spring 源码学习笔记11--Spring事务 Spring事务是基于Spring Aop的扩展 AOP的知识参见<Spring 源码学习笔记10--Spring AOP> 图片参考了 ...

  7. 源码学习之ASP.NET MVC Application Using Entity Framework

    源码学习的重要性,再一次让人信服. ASP.NET MVC Application Using Entity Framework Code First 做MVC已经有段时间了,但看了一些CodePle ...

  8. Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件

    写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...

  9. mongo源码学习(三)请求接收传输层

    在上一篇博客中(mongo源码学习(二)db.cpp之mongoDbMain方法分析),我们把db.cpp中的mongoDbMain的执行过程分析了一下,最后会调用initAndListen(serv ...

  10. spring源码学习——spring整体架构和设计理念

    Spring是在Rod Johnson的<Expert One-On-One J2EE Development and Design >的基础上衍生而来的.主要目的是通过使用基本的java ...

随机推荐

  1. 【读书笔记】C#高级编程 第一章 .NET体系结构

    (一) C#与.NET的关系 1) C#编写的所有代码都使用.NET Framework运行. 2) C#是一种编程语言,.NET Framework是一个平台(可开发.部署和执行分布式应用程序). ...

  2. 常用MySQL语句(持续更新)

    1. 客户端登录 在终端输入 mysql -u[用户名] -p[密码] 2. 数据库级别操作 // 创建数据库 create database [db name]; // 查看数据库列表 show d ...

  3. 清源正本,鉴往知来,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中引用类型是否进行引用传递EP18

    开篇明义,Go lang中从来就不存在所谓的"引用传递",从来就只有一种变量传递方式,那就是值传递.因为引用传递的前提是存在"引用变量",但是Go lang中从 ...

  4. 《Java基础——循环语句》

    Java基础--循环语句       1. while语句: 规则: 1. 首先计算表达式的值. 2. 若表达式为真,则执行循环语法,直至表达式为假,循环结束.   格式: while(表达式) 语句 ...

  5. 使用 Loki 收集 nginx 日志

    转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247492075&idx=1&sn=ba63984111 ...

  6. Kubernetes 监控--Prometheus 高可用: Thanos

    前面我们已经学习了 Prometheus 的使用,了解了基本的 PromQL 语句以及结合 Grafana 来进行监控图表展示,通过 AlertManager 来进行报警,这些工具结合起来已经可以帮助 ...

  7. 使用KVM的图形化界面安装centos7虚拟机

    前提条件 1.宿主机上已经安装KVM的图形化管理软件,参考网址:https://www.cnblogs.com/sanduzxcvbnm/p/15538881.html 2.宿主机上安装vnc服务器, ...

  8. 0-mysql数据库下载及安装

    1 下载mysql源安装包 wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 2 安装mysql源 yu ...

  9. 如何使用netlify部署vue应用程序

    什么是Netlify? Netlify是一个现代网站自动化系统,其JAM架构代表了现代网站的发展趋势.所谓JAM,就是指基于客户端JavaScript.可重用API和预构建Markup标记语言的三者结 ...

  10. 前端程序员学习 Golang gin 框架实战笔记之一开始玩 gin

    原文链接 我是一名五六年经验的前端程序员,现在准备学习一下 Golang 的后端框架 gin. 以下是我的学习实战经验,记录下来,供大家参考. https://github.com/gin-gonic ...