Seata 源码篇之核心思想 - 01

news2024/10/7 0:11:26

Seata 源码篇之核心思想 - 01

  • 引言
  • 基础架构
  • 数据源代理
  • 分支事务提交和回滚
  • 隔离级别
    • 解决脏写
    • 读未提交
    • 读已提交
  • 小结


笔者个人项目中使用到了seata来做分布式事务管理,面试过程中也经常被问到seata的原理,seata源码本身也不是很复杂,所以准备出一个Seata源码大白话解读系列。

本系列文章编撰过程主要参考Seata官网提供的相关源码解读文章,附加笔者个人理解,如有不正确,欢迎各位大佬在评论区指出。

参考资料:

  • Seata官网提供的系列源码解析文章

对Seata框架不了解的可以先阅读一下下面这篇文章:

  • Seata框架使用和基本原理

引言

分布式事务目前的解决方案主要分为侵入式和无侵入式的方案:

  • 无侵入式方案主要是基于XA的二阶段提交协议,缺点就是资源锁定周期长,还存在单点故障等问题。
  • 侵入式方案的典型有TCC,SAGA 和 基于消息队列事务消息实现的最终一致性

Seata同时支持无侵入式和侵入式两种实现方式,本系列前半部分主要聚焦于无侵入式的实现;


基础架构

Seata的设计思路就是将一个分布式事务理解成一个全局事务,下面挂了若干个分支事务,而每个分支事务又是一个满足ACID的本地事务,即每个分支事务的执行过程是原子性的,而全局事务需要确保所有分支事务执行过程的原子性;

与之相对的就是每个本地事务包含若干SQL语句,每个SQL语句执行过程是原子性的,本地事务需要确保其所包含的所有sql语句执行过程的原子性。

具体如下面所示:
在这里插入图片描述
Seata 内部定义了3个模块来处理全局事务和分支事务的关系和处理过程 :

  • Transaction Coordinator ( TC ) : 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或者回滚。
  • Transaction Manager ( TM ) : 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决定。
  • Resource Manager ( RM ):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。

在这里插入图片描述
整个全局事务的执行分为以下几步:

  1. TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播;
  2. RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务;
  3. TM 向 TC 发起全局提交或回滚;
  4. TC 调度 XID 下的分支事务完成提交或者回滚。

数据源代理

传统的XA协议依赖的是数据库层面来保障事务的一致性,也就是说XA的各个分支事务是在数据库层面上驱动的,这会导致数据库与XA驱动耦合,同样也会导致各个分支事务资源锁定周期长,因此性能很低,一般不采用。

Seata 默认的AT模式则是基于业务层面实现的补偿机制,通过对RM进行改造,在应用层中添加一层对数据源的代理,如下图所示:

在这里插入图片描述
Seata 在数据源做了一层代理层,所以我们使用 Seata 时,我们使用的数据源实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,主要是解析 SQL,把业务数据在更新前后的数据镜像组织成回滚日志,并将 undo log 日志插入 undo_log 表中,保证每条更新数据的业务 sql 都有对应的回滚日志存在。

这样做的好处就是,本地事务执行完可以立即释放本地事务锁定的资源,然后向 TC 上报分支状态。当 TM 决议全局提交时,就不需要同步协调处理了,TC 会异步调度各个 RM 分支事务删除对应的 undo log 日志即可,这个步骤非常快速地可以完成;当 TM 决议全局回滚时,RM 收到 TC 发送的回滚请求,RM 通过 XID 找到对应的 undo log 回滚日志,然后执行回滚日志完成回滚操作。

传统的XA协议与Seata提供的AT模式实现区别如下图所示:
在这里插入图片描述

  • XA 方案的 RM 是放在数据库层的,它依赖了数据库的 XA 驱动程序
  • Seata 的 RM 实际上是已中间件的形式放在应用层,不用依赖数据库对协议的支持,完全剥离了分布式事务方案对数据库在协议支持上的要求

分支事务提交和回滚

分支事务提交和回滚分为三个阶段,如下所示:

  • 第一阶段: 分支注册

分支事务利用 RM 模块 中对 JDBC 数据源的代理,加入了若干流程,对业务 SQL 进行解释,把业务数据在更新前后的数据镜像组织成回滚日志,并生成 undo log 日志,对全局事务锁的检查以及分支事务的注册等,利用本地事务 ACID 特性,将业务 SQL 和 undo log 写入同一个事物中一同提交到数据库中,保证业务 SQL 必定存在相应的回滚日志,最后对分支事务状态向 TC 进行上报。

在这里插入图片描述

  • 第二阶段: TM决议全局提交

当 TM 决议提交时,就不需要同步协调处理了,TC 会异步调度各个 RM 分支事务删除对应的 undo log 日志即可,这个步骤非常快速地可以完成。这个机制对于性能提升非常关键,我们知道正常的业务运行过程中,事务执行的成功率是非常高的,因此可以直接在本地事务中提交,这步对于提升性能非常显著。

在这里插入图片描述

  • 第三阶段: TM决议全局回滚

当 TM 决议回滚时,RM 收到 TC 发送的回滚请求,RM 通过 XID 找到对应的 undo log 回滚日志,然后利用本地事务 ACID 特性,执行回滚日志完成回滚操作并删除 undo log 日志,最后向 TC 进行回滚结果上报。

在这里插入图片描述
业务对以上所有的流程都无感知,业务完全不关心全局事务的具体提交和回滚,而且最重要的一点是 Seata 将两段式提交的同步协调分解到各个分支事务中了,分支事务与普通的本地事务无任何差异,这意味着我们使用 Seata 后,分布式事务就像使用本地事务一样,完全将数据库层的事务协调机制交给了中间件层 Seata 去做了,这样虽然事务协调搬到应用层了,但是依然可以做到对业务的零侵入,从而剥离了分布式事务方案对数据库在协议支持上的要求,且 Seata 在分支事务完成之后直接释放资源,极大减少了分支事务对资源的锁定时间,完美避免了 XA 协议需要同步协调导致资源锁定时间过长的问题。


隔离级别

Seata为我们提供一个全局事务,谈到事务就离不开事务隔离级别这个问题,Seata AT模式的事务隔离级别是建立在分支事务的本地隔离级别基础上的。在数据库本地隔离级别读已提交或以上的前提下,Seata 设计了由事务协调器维护的全局排它锁,来保证事务间的写隔离,同时,将全局事务默认定义在读未提交的隔离级别上。

在讲 Seata 事务隔离级之前,我们先来回顾一下数据库事务的隔离级别,目前数据库事务的隔离级别一共有 4 种,由低到高分别为:

  • Read uncommitted:读未提交
  • Read committed:读已提交
  • Repeatable read:可重复读
  • Serializable:序列化

数据库一般默认的隔离级别为读已提交,比如 Oracle,也有一些数据的默认隔离级别为可重复读,比如 Mysql,一般而言,数据库的读已提交能够满足业务绝大部分场景了。

我们知道 Seata 的事务是一个全局事务,它包含了若干个分支本地事务,在全局事务执行过程中(全局事务还没执行完),某个本地事务提交了,如果 Seata 没有采取任务措施,则会导致已提交的本地事务被读取,造成脏读;如果数据在全局事务提交前已提交的本地事务被修改,则会造成脏写。

由此可以看出,传统意义的脏读是读到了未提交的数据,Seata 脏读是读到了全局事务下未提交的数据,全局事务可能包含多个本地事务,某个本地事务提交了不代表全局事务提交了。

绝大部分应用在读已提交的隔离级别下工作是没有问题的,而实际上,这当中又有绝大多数的应用场景,实际上工作在读未提交的隔离级别下同样没有问题。

在极端场景下,应用如果需要达到全局的读已提交,Seata 也提供了全局锁机制实现全局事务读已提交。但是默认情况下,Seata 的全局事务是工作在读未提交隔离级别的,保证绝大多数场景的高效性。

Seata AT模式下写操作都必须加锁,但是读操作默认不加锁,因此可以读到还未提交的全局事务做出的修改,可以自行选择加锁,使其工作在读已提交隔离级别下。


解决脏写

假设按照我们目前已知流程运行,那么看看会遇到什么问题呢?

在这里插入图片描述

业务一开启全局事务,其中包含分支事务A(修改 A)和分支事务 B(修改 B),业务二修改 A,其中业务一执行分支事务 A 先获取本地锁,业务二则等待业务一执行完分支事务 A 之后,获得本地锁修改 A 并入库,业务一在执行分支事务时发生异常了,由于分支事务 A 的数据被业务二修改,导致业务一的全局事务无法回滚。

业务一回滚分支事务A的时候,会通过after-image判断当前自己自己更新过的数据,是否又被其他人修改了,如果是说明发生了脏写问题,这里会抛出异常。

Seata采用加互斥锁的方式来解决脏写问题:

1、业务二执行时加 @GlobalTransactional注解:

在这里插入图片描述
业务二在执行全局事务过程中,分支事务 A 提交前,注册分支事务获取全局锁时,发现业务一全局锁还没执行完,因此业务二提交不了,抛异常回滚,所以不会发生脏写。

这里其实是发生了死锁现象,但是由于两方都存在锁超时机制,最终会因为全局锁超时较早,抛出超时异常,提前打破死锁局面。

  1. 业务二执行时加 @GlobalLock注解:

在这里插入图片描述
与 @GlobalTransactional注解效果类似,只不过不需要开启全局事务,只在本地事务提交前,检查全局锁是否存在。

  1. 业务二执行时加 @GlobalLock 注解 + select for update语句:

在这里插入图片描述
如果加了select for update语句,则会在 update 前检查全局锁是否存在,只有当全局锁释放之后,业务二才能开始执行 updateA 操作。

如果单单是 transactional,那么就有可能会出现脏写,根本原因是没有 Globallock 注解时,不会检查全局锁,这可能会导致另外一个全局事务回滚时,发现某个分支事务被脏写了。所以加 select for update 也有个好处,就是可以重试。


读未提交

Seata AT模式下,默认为读未提交隔离级别,即不会对select请求做任何拦截处理,如下图所示:
在这里插入图片描述
由于业务一最终进行了全局事务回滚,所以业务二读取到的值就变成了脏值,此时就产生了脏读问题。


读已提交

Seata AT 模式默认为读未提交隔离级别,该隔离级别下可能会产生脏读问题,这里是指在全局事务未提交前,被其它业务读到已提交的分支事务的数据,本质上是Seata默认的全局事务是读未提交。

可以把这里分支事务提交理解为执行完事务中一条sql更新语句。

那么怎么避免脏读现象呢?

  • 业务二查询 A 时加 @GlobalLock 注解 + select for update语句:

在这里插入图片描述
加select for update语句会在执行 SQL 前检查全局锁是否存在,只有当全局锁完成之后,才能继续执行 SQL,这样就防止了脏读。


小结

本文所讲的是Seata的AT模式,也是Seata默认情况下的启用的模式,其类似于XA的两阶段提交方案,同时对业务无侵入,但是这种模式依然需要依赖数据库本地事务的ACID特性。

对于不支持ACID特性的数据库而言,可以考虑Seata提供的TCC方案,其是一种对业务有入侵的方案,通过手工编码指定提交和回滚逻辑,在业务层面实现补偿逻辑,关于TCC部分源码解析将在本系列后半部分给出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1015157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FWT小结

核心思想:把 a , b a,b a,b 化成 f w t ( a ) , f w t ( b ) fwt(a),fwt(b) fwt(a),fwt(b),相乘后再化为 a a a 化的过程用的是分治 所以和FFT其实一模一样 OR / AND 卷积 不需要什么技巧,暴力分治转移即可 每次分治下去,…

瑞萨MCU入门教程(非常详细的瑞萨单片机入门教程)

瑞萨MCU零基础入门系列教程 前言 得益于瑞萨强大的MCU、强大的软件开发工具(e studio),也得益于瑞萨和RA生态工作室提供的支持,我们团队编写了《ARM嵌入式系统中面向对象的模块编程方法》,全书37章,将近500页: 讲解面向对象编程…

硬件笔记:组装“固态 U 盘”的八年,从 100 块到 1000 块

这篇文章,聊聊自从 2015 年开始,到目前为止,我使用固态硬盘组装的高速 U 盘,以及它们的使用体验,以及一些明显的坑。 写在前面 2015 年的 8 月,我剁手下单了一块 32G 大小,NGFF接口的三星 22x…

关于 C/C++ 中在指针前加 const 关键字的作用说明

1. 作用说明: 在指针前加 const 的用途为:不可改变指针指向的内存的值,即将该指向指向的内存中的变量置为只读(read-only) 变量。 但是,可以给 const 的指针赋值,即将具有 const 属性的指针指向别的内存地…

Linux 内核镜像分析

文章目录 前言一、概述二、bzImage2.1 镜像分析 三、zImage3.1 镜像分析参考链接 前言 介绍了vmlinux的来源,以及分析方法; 一、概述 在linux系统中,vmlinux(vmlinuz)是一个包含linux kernel的静态链接的可执行文件…

JavaScript中的原型继承和类继承之间的区别

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 原型继承(Prototype Inheritance)⭐ 类继承(Class Inheritance)⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启…

MySQL开启安全审计日志,开启查询日志

MySQL 查询开启日志 在 MySQL 数据库中,开启查询日志是一个非常有用的技术,它能帮助你追踪每一个执行的查询语句,以便更好地优化 SQL 语句和性能。本文将介绍如何在 MySQL 数据库中开启查询日志。 开启查询日志 MySQL 中的查询日志是一种记…

Mysql开启binlog

本案例基于mysql5.7.16实验 1、在linux中进入mysql查询binlog是否打开,执行命令如下: mysql -u root -p 2、查询binlog是否开启命令如下,如果log_bin为OFF则证明mysql的binlog没有打开 show variables like %log_bin%; 3、退出mysql终端&…

OPC DA如何实现跨平台

目录 简介 EntireX DCOM Utgard OPC XML DA OPC UA 协议转换代理 简介 本文介绍OPC DA跨平台通讯的几种方案。 OPC官方说明文档 OPC(OLE for Process Control)是为过程控制专门设计的OLE 技术,基于COM/DCOM的数据访问的标准。常说的O…

vmware去虚拟化

路径:C:\Program Files (x86)\VMware\VMware Workstation\x64\vmware-vmx.exe ,复制一份备份 用16进制工具打开修改这个文件,如winhex 1、搜索 25 73 2E 65 6E 61 62 6C ,找到上面有两个"VMware"开头的 2、硬盘SCSI格…

JAVASE---String类

String类的重要性 在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面相对象的思想,而…

Java 类和对象

在面向对象语言中万物皆对象,一切都围绕对象来进行,找对象、建对象,用对象等。 类:把具有相同特征和行为的一组对象抽象为类,类是抽象概念,如人类、车类等,无法具体到每个实体。 对象&#xff…

71、Spring Data JPA 的 样本查询--参数作为样本去查询数据库的数据,也可以定义查询的匹配规则

★ 样本查询 给Spring Data传入一个样本数据,Spring Data就能从数据库中查询出和样本相同的数据。被查询的数据并不需要和样本是完全相同的,可能只需要和样本有几个属性是相同的。总结: 样本查询–就是把参数作为样本去查询数据库的数据&…

一、 计算机网络概论

一、计算机网络概论 1、计算机网络概述 1.1、概念 计算机网络是一个将分散的、具有独立功能的计算机系统,通过通信设备与线路连接起来,由功能完善的软件实现资源共享和信息传递的系统 是一些互连的、自治的计算机系统的集合 以能够相互共享资源的方…

【Git】Git 变基(rebase)以及rebase和merge之间的区别

Git 变基 1.变基 — rebase 在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。 在前面的文章中已经介绍了merge,这里我们来学习另一个指令rebase。 变基的基本操作 回顾之前在 分支的合并 中的一个例子,在该例子中&am…

go Gorm连接数据库,并实现增删改查操作

Gorm 1. 准备工作 首先进入终端下载我们需要的包(确保go和mysql安装完成,并设置了环境变量) go get -u gorm.io/driver/mysql go get -u gorm.io/gorm有两份官方文档有对 GORM 更详细的讲解。 创建 | GORM - The fantastic ORM library f…

EndNote21 | 账户同步问题

问题:无法同步,提示如下图所示。 原因:网络问题。 解决方法:国内网络无法实现同步,解决上网问题即可。

【数据结构】结构实现:顺序存储模式实现堆的相关操作

🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:数据结构 🔥该文章着重讲解了使用顺序结构实现堆的插入和删除等操作。 目录: 🌍二叉树的顺序结构&#x…

什么触控笔好用又便宜?ipad2022手写笔推荐

随着无纸化的广泛使用,和Apple pencil的出现,电容笔逐渐成为生产力的主要部分,像中性笔一样的电容笔,它不止具有小巧的身材,续航和功能都很在线,无论是在学习上还是工作上,电容笔逐渐成为人们缺…

Jenkis 配置钉钉通知

1、安装插件Ding Talk 2、钉钉上的配置 打开钉钉创建机器人,勾选加签,后面jenkins要用到 2.1 webhook -jenins界面要配置的地址:https://。。。。。。 2.2 jenkins 界面的加密地址 3、jenkins界面上的配置 在系统管理中找到安装好的插件&a…