Seata AT模式学习

news/2024/10/14 6:23:30

官方文档

Seata是目前国内最流行的一个分布式事务的组件,支持以下4种模式

AT模式:对业务代码无侵入,只要在业务的数据库加上一个UNDO_LOG表,在配置文件中配置好Seata的服务端,在需要开启全局事务的地方加上注解就行

TCC模式:即Try-Commit-Cancel,自定义prepare逻辑、commit逻辑及回滚的逻辑,代码侵入性大、灵活、对开发要求高

SAGA模式:主要用于分布式长事务

XA模式:即XA协议的实现,经典的二阶段提交

 

这里我主要学习一下最常用的AT模式

 

大致工作流程:

由两阶段提交协议演化而来,也是分为两个阶段,如下

一阶段:解析update SQL和执行业务的UPDATE语句,将回滚的补偿放入undo_log,直接提交本地事务

二阶段(成功):清理undo_log相关的补偿信息

二阶段(失败):根据undo_log种的补偿信息对数据进行反向补偿

 

三种角色:

TC(Transaction Manager):事务协调者,维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM(Transaction Manager): 事务管理器,定义全局事务的范围,开始全局事务、提交或回滚全局事务。相当于加了@GlobalTransaction注解那个才是TM。

RM(Resource Manager ) :资源管理器,管理分支事务处理的资源( Resource ),与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。被TM调用的服务会有RM。

其中,TC工作在Seata服务端,TM和RM工作在Seata的客户端即业务的微服务端

 

 

 

 

使用方式:

1. 部署Seata服务端,单机用于学习,生产上一般是要以集群的形式,并配置一个数据库(保存全局的事务id),并将自身注册到服务注册中心,被其他微服务使用

2. 业务端改造,

    在每个相关的业务端的数据库中加上一个undo_log的表,

    

使用方式:只需要将注解@GlobalTransactional加在需要开启全局事务的那个方法上,而被调用的微服务的方法上只需要加上本地事务注解 @Transactional

public class BusinessServiceImpl implements BusinessService {private StorageService storageService;private OrderService orderService;

@GlobalTransactionalpublic void purchase(String userId, String commodityCode, int orderCount) {storageService.deduct(commodityCode, orderCount);orderService.create(userId, commodityCode, orderCount);} }

 

@GlobalTransactional这个直接会标识开启全局事务,这个应该就是TM,开启全局事务、提交全局事务和回滚全局事务

那为什么被调用的微服务的方法不需要加全局事务的注解呢?

    我大胆猜测一下,因为被调用的微服务方法是具体的干活的,是RM,会将DataSource进行一层代理,在执行UPDATE SQL的前后会进行解析UPDATE SQL和生成补偿信息到undo_log中

那RM怎么知道某个方法被全局事务的进行管理了呢?

    我再来猜测一下,微服务间的调用肯定也做了手脚,会在HTTP调用或者RPC调用的时候将全局事务的事务xid作为参数或者header传过来      

 

关于数据源的代理做了什么事参考官方文档

关于如何传递全局事务id,需要使用改造过的feign/rpc

 

两个主要的注解:

@GlobalTransactional   声明全局事务(会隐式的获取、持有、释放全局锁)

@GlobalLock                 检查是否有全局锁(配合select for update 能进行多次尝试获取全局锁,否则监测到有全局锁就抛出异常)

 

以下是一阶段和二阶段的详细过程

一阶段

  1. 解析 SQL:得到 SQL 的类型(UPDATE),表(product),条件(where name = 'TXC')等相关的信息。
  2. 查询前镜像:根据解析得到的条件信息,生成查询语句,定位数据。
  3. 执行业务 SQL:更新这条记录的 name 为 'GTS'。
  4. 查询后镜像:根据前镜像的结果,通过 主键 定位数据。
  5. 插入回滚日志:把前后镜像数据以及业务 SQL 相关的信息组成一条回滚日志记录,插入到 UNDO_LOG 表中。
  6. 提交前,向 TC 注册分支:申请 product 表中,主键值等于 1 的记录的 全局锁 。
  7. 本地事务提交:业务数据的更新和前面步骤中生成的 UNDO LOG 一并提交。
  8. 将本地事务提交的结果上报给 TC。

二阶段-提交​

  1. 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
  2. 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。

二阶段-回滚​

  1. 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
  2. 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
  3. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍。
  4. 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
  5. 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。

 

 

 

 

关于脏读问题

Seata中的分布式事务,都有各自的 XID,每个 XID 都会把 “行锁”(也叫全局锁)注册到 TC 里面

 

问题原因:由于AT模式是一阶段就直接提交,所以如果另一个不相关的方法去查询对应的那行数据,是有可能读到脏数据(即还未完成全局事务的数据)

解决办法:使用select for update和@GlobalLock注解,如下图,

select for update会等待全局事务中的一阶段结束后才能拿到本地锁,然后去获取全局锁,此时全局锁被全局事务给占用了(TC处有记录),导致全局锁获取不到,从而无法继续下去进行读取操作,直到全局事务二阶段提交或者回滚

 

 

关于脏写问题

问题原因:同样的,由于AT模式是一阶段就直接提交,所以如果另一个不相关的方法(只带了@Transactional,甚至不带)去修改对应的那行数据,是可以在一阶段结束后,二阶段提交回滚前将数据改掉,导致如果全局事务失败,无法正确回滚(补偿)

解决办法一:在其他的update请求的方法上也加上@GlobalTransactional,同样开启全局事务,确保修改的时候能够因为全局锁而被挡住

 

解决办法二:在其他的update请求的方法上加上@GlobalLock+@Transactional,并在update前使用selectForUpdate(这步可以不做,下面解释)。   会先去尝试拿本地锁(直到拿到),然后做修改,再去获取全局锁,此时另一个全局事务还未提交,则会霸占着全局锁,这里取不到全局锁,会释放本地锁,然后抛出获取全局锁失败的异常。

 

这里不使用select for update也能防止脏写,但是加了能带来以下的好处:

  • 锁冲突更“温柔”些。如果只有@GlobalLock,检查到全局锁,则立刻抛出异常,也许再“坚持”那么一下,全局锁就释放了,抛出异常岂不可惜了。
  • updateA()中可以通过select for update获得最新的A,接着再做更新。

 

关于脏读脏写的原因以及解决办法

 

 

写隔离以及是否会死锁

不会死锁,虽然是可能形成两个全局事务相互持有锁(比如一个事务持有本地锁并尝试获取全局锁,一个事务持有全局锁并尝试获取本地锁),不过拿全局锁是会进行有限次数的尝试,拿不到的话会放弃并释放另一个锁,所以并不会形成真正的死锁

如下图官网的例子:

两个全局事务tx1和tx2,分别对a表的一个字段m进行更新,

tx1先开始 -》 开启本地事务 -》 拿到本地锁 -》 更新操作 -》 拿到全局锁 -》 提交并释放本地锁 -》 等待二阶段的提交或者回滚

tx2后开始 -》 开启本地事务 -》 拿到本地锁(如果在tx1释放本地锁前尝试拿的话会等待)-》 更新操作 -》 尝试多次去拿全局锁(全局锁被tx1持有)

如果tx1最后执行的是二阶段提交,则tx1释放全局锁,tx2获取到全局锁,tx2也能完成一阶段的工作,并继续往下执行

 

 

 

如果tx1最后执行的是二阶段回滚,(tx1仍然持有全局锁)tx1还需要重新获取本地锁,但是本地锁已经被tx2持有了,这时候就是相互持有对方的锁(tx1持有全局锁并尝试获取本地锁,tx2持有本地锁并尝试获取全局锁),不过由于尝试获取全局锁是有尝试次数限制的(默认最多10次),所以tx2最终会获取全局锁超时失败,并释放本地锁,然后tx1得到本地锁从而完成二阶段的回滚(补偿)

 

 

读隔离

Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)

如果业务确实需要读已提交,就需要使用@GlobalLock注解并使用select for update,  Seata的AT模式对当前读(select for update)进行了代理,如果加了@GlobalLock注解和使用select for update, 会进行获取全局锁的获取的重试。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

 

 

 

 

 下面有待补充具体的实操

 

 

 

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

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

相关文章

最好用的AI换脸软件,rope下载介绍(支持cpu)

随着AI技术的广泛运用,市面上的换脸软件也多了起来,今天给各位介绍其中的王者Rope! 先上两个动图,给大伙看看效果rope是如何实现这种自然的效果呢?这得益于机器学习技术的不断发展,rope经过深度神经网络的无数次迭代优化,最终得出的模型可以自动学习和识别视频中的人脸特…

提高学生学习成绩和自我效能感:护理培训的移动聊天机器人方法

(Promoting students learning achievement and self-efficacy: A mobile chatbot approach for nursing training) DOI: 10.1111/bjet.13158 一、摘要 研究目的:护理培训的目的不仅在于掌握技能,更在于培养解决问题的决策能力。然而,产科疫苗接种知识等培训项目大多采用以讲…

ESXI上安装和使用MegaCli

一、下载安装包 目前官网找不到安装包了,这里提供一个MegaCli-8.04.07:https://www.lanzoub.com/iUzBn1tyhdxi 二、将安装包解压上传到esxi 可以通过sftp或者其他工具上传到esxi中,不太建议在esxi上直接下载,我提供的是rar压缩包。这里我使用WinSCP工具上传到esxi中,下载地…

装备购买

解释一下蓝书上的做法 按照数学归纳法证明这个贪心,假设当前在第\(i\)行,前面已经选出\(i-1\)个线性无关的向量了(非零行),那么对于这一行,如果最终的结果不选\(z[k]\),而是选了另一个\(z[l]\),那么最终的向量组加入\(z[k]\)后就线性相关了,\(z[k]\)可以被这个向量组唯…

基于SSM的酒店管理系统毕业设计论文【范文】

摘要 随着旅游业的蓬勃发展及商务活动的频繁,酒店行业作为其重要组成部分,对信息化管理的需求日益迫切。本研究课题针对现代酒店业的管理需求,设计并实现了一个基于Spring、Spring MVC和MyBatis(SSM)框架的酒店管理系统。该系统旨在提高酒店业务处理效率,优化客房管理流程…

用Rolle中值定理证明Lagrange中值定理

\(命题人知不知道高中生有多喜欢这个公式,竟然敢放19题doge\)

SGDMA与普通DMA

DMA(Direct memory access,内存直接存取),属于 Vectored I/O 方式。 区别 Scatter-gather DMA 与 Block DMA(即普通DMA) 方式不同, Block DMA: 一次只传输一块物理上连续的数据,完成后中断,主机收到中断后再行下一块物理上连续的数据传输。 Scatter-gather DMA: 使用一…

一些不错的语文题

金考卷绿色模拟 散文 1.《蝉自故乡来》与鲁迅《故乡》中“我”的思想变化截然相反 故乡中的我在童年时对故乡的人和事都是热爱的,没有渴望远离的想法,(如少年闰土),而成年后回到故乡感到物是人非,才想要逃离故乡(闰土后来的样子可知晓,以及祥林嫂等类似的素材)首先这个…