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

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. 这里聊聊扫地机的 IOT 开发

    以下内容为本人的著作,如需要转载,请声明原文链接微信公众号「englyf」https://www.cnblogs.com/englyf/p/16663833.html 消费者使用扫地机通常的方式是通过 ...

  2. Docker 入门指南

    Docker 入门指南 目录 基础概念 安装教程 基本操作 常用安装 构建操作 容器编排 壹.基础概念 什么是Docker? Docker是基于Go开发的应用容器引擎,属于 Linux 容器的一种封装 ...

  3. 群晖-使用docker套件部署Prometheus+Grafana

    Docker 部署 Prometheus 说明: 先在群辉管理界面安装好docker套件,修改一下镜像源(更快一点) 所需容器如下 Prometheus Server(普罗米修斯监控主服务器 ) No ...

  4. 【设计模式】Java设计模式 - 组合模式

    Java设计模式 - 组合模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自己 ...

  5. 数据仓库与hive

    数据仓库与hive hive--数据仓库建模工具之一 一.数据库.数据仓库 1.1 数据库 关系数据库本质上是一个二元关系,说的简单一些,就是一个二维表格,对普通人来说,最简单的理解就是一个Excel ...

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

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

  7. 【ProxySQL】ProxySQL Cluster的搭建

    文章转载自:https://blog.51cto.com/l0vesql/2104643 背景 早期的ProxySQL若需要做高可用,需要搭建两个实例,进行冗余.但两个ProxySQL实例之间的数据并 ...

  8. Beats:Beats 入门教程 (一)

  9. 第二章:视图层 - 6:QueryDict对象

    类的原型:class QueryDict[source] 在HttpRequest对象中,GET和POST属性都是一个django.http.QueryDict的实例.也就是说你可以按本文下面提供的方 ...

  10. 结构体struct知识

    2022-10-12 08:52:03 //    结构体知识#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h>#include<m ...