Spring Cloud系列—Seata分布式事务解决方案AT模式

上篇文章:

Spring Cloud系列---Seata分布式事务解决方案XA模式https://blog.csdn.net/sniper_fandc/article/details/149947291?fromshare=blogdetail&sharetype=blogdetail&sharerId=149947291&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

[1 AT模式原理](#1 AT模式原理)

[1.1 整体机制](#1.1 整体机制)

[1.2 写隔离](#1.2 写隔离)

[1.3 读隔离](#1.3 读隔离)

[2 AT模式使用](#2 AT模式使用)

[3 AT模式 VS XA模式](#3 AT模式 VS XA模式)


1 AT模式原理

1.1 整体机制

AT模式是Seata针对两阶段提交2PC的改进模式,是一种非侵入式的分布式事务解决方案。通过引入数据源代理DataSourceProxy,在代理中添加undo_log日志和检查全局锁等逻辑,从而让RM不再需要数据库实现XA协议。改进后的2PC如下:

一阶段:

1.注册分支事务:TM开启全局事务,RM向TC注册分支事务;

2.记录undo_log:执行SQL前首先会解析SQL ,根据解析信息记录更新前镜像到undo_log ,然后执行SQL记录更新后镜像到undo_log。记录undo_log的目的是因为一阶段会提交本地事务,因此如果要回滚就需要undo_log的支持(2PC需要实现XA协议,如果数据库没有实现该协议,就可能没有写undo日志和redo日志的行为)

3.提交本地事务和undo_log:提交前获取全局锁 (全局事务结束才会释放),然后直接提交本地事务(数据库数据会真实发生变化)。而2PC的一阶段只执行SQL不提交事务,因此2PC一阶段资源还会被锁定,到二阶段执行结束才会释放资源;AT模式在一阶段执行结束就会释放资源。

4.汇报事务状态:RM向TC汇报本地事务(分支事务)的执行状态。

二阶段:

1.提交全局事务:如果所有分支事务都执行成功,则异步执行全局事务的提交和undo_log记录的清除。结束后RM向TC汇报状态,并释放全局锁。

2.回滚全局事务:如果有分支事务执行失败,则开启本地事务进行回滚。回滚需要查询undo_log并比较当前数据和undo_log更新后镜像数据是否一致(如果不一致说明当前数据被其它全局事务修改,会根据配置策略来处理),如果一致就根据更新前镜像来回滚数据。结束后RM向TC汇报状态,并释放全局锁。

注意:AT模式其实就是在代理层面实现了没有在RM层面实现XA协议的回滚操作相关支持(即undo日志),同时引入全局锁来确保写隔离(不会出现赃写问题)。

1.2 写隔离

当多线程并发操作同一个数据,就可能出现脏写问题。这是因为AT模式是一阶段就会提交本地事务,在全局事务未提交前,如果其它全局事务的分支事务对该数据修改,最终第一个全局事务提交后,该数据就变为第一个全局事务的值,从而丢失了其它全局事务的值。

AT模式通过引入全局锁来解决脏写问题,实现写隔离:

1.一阶段本地事务提交前,需要确保先拿到全局锁。

2.拿不到全局锁,不能提交本地事务。

3.拿全局锁的尝试被限制在一定范围内(超时时间或重试次数),超出范围将放弃,并回滚本地事务,释放本地锁。

tx1全局事务先对m=1000的数据进行修改,本地事务首先要获取该数据的本地锁,执行m-100后为m=900,在提交本地事务前需要先获取全局锁,然后执行本地事务提交,提交成功后释放本地锁。

tx2全局事务在tx1全局事务的二阶段前开启并执行,执行m-100后m=800(由于tx1一阶段会提交本地事务,因此tx2全局事务会读取m修改后的值),在提交本地事务前需要先获取全局锁,但是由于全局锁在tx1手里,因此就会不断重试获取全局锁。直到tx1事务提交全局事务后,才会释放全局锁,此时tx2才能获取全局锁进行本地事务提交。

在事务执行成功的情况下,没有全局锁貌似没有问题。但是一旦tx1事务执行失败进行回滚,就会出现脏写。即tx2事务读取的是更新后回滚前的脏数据,tx1一旦回滚,tx2所做的任何操作都会无效。回滚情况下的工作模式如下:

由于tx2更新后一直无法获取到tx1未释放的全局锁,但是tx2持有本地锁。而tx1想要回滚需要本地锁,但是其未释放全局锁。tx1和tx2就会相互等待,直到tx2获取全局锁超时(或超过重试次数),tx2本地事务就执行失败,进行回滚,并释放本地锁。tx1就能获取全局锁,从而进行回滚。

****总结:****全局锁强制将两个阶段原子性执行,从而防止其它全局事务插入到该全局事务的两个阶段之间执行,避免了脏写问题。

1.3 读隔离

多线程并发读/写,就可能出现脏读问题:即一个事务读到另一个事务读未提交的数据。要解决脏读问题,就需要事务隔离级别是读已提交。

在数据库本地事务隔离级别读已提交(Read Committed)或以上的基础上,Seata(AT模式)的默认全局隔离级别是读未提交(Read Uncommitted)。

如果应用在特定场景下,必需要求全局的读已提交,目前Seata实现读已提交事务隔离级别的方式是通过SELECT FOR UPDATE语句的代理。

通过SELECT FOR UPDATE语句的代理查询tx1事务提交后的数据,该语句会获取全局锁,如果未获取到全局锁,说明事务未提交,就释放本地锁并阻塞等待,阻塞期间不断重试获取全局锁,此时读取的数据就是读已提交的数据,从而避免脏读问题。

目前,Seata只对SELECT FOR UPDATE语句进行代理,并没有对其它查询语句代理,这是出于性能角度的考虑。

2 AT模式使用

由于AT模式需要用到undo_log日志,因此需要创建undo_log表:

sql 复制代码
-- 注意此处0.7.0+ 增加字段 context

CREATE TABLE `undo_log` (

  `id` bigint(20) NOT NULL AUTO_INCREMENT,

  `branch_id` bigint(20) NOT NULL,

  `xid` varchar(100) NOT NULL,

  `context` varchar(128) NOT NULL,

  `rollback_info` longblob NOT NULL,

  `log_status` int(11) NOT NULL,

  `log_created` datetime NOT NULL,

  `log_modified` datetime NOT NULL,

  PRIMARY KEY (`id`),

  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)

) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

使用AT模式仅需要修改配置文件:

bash 复制代码
seata:

  data-source-proxy-mode: AT

当库存不足时,全局事务就会执行失败:

3 AT模式 VS XA模式

|-------|-----------------------------------------------------------------|----------------------------------------------------------|
| | AT模式 | XA模式 |
| 实现方式 | 1.通过记录数据镜像到undo_log日志表的方式来实现回滚 2.不需要数据库支持XA协议,但是需要创建undo_log日志表 | 1.需要数据库实现XA协议,依赖XA协议实现提交和回滚 2.不需要undo_log表,但是需要数据库支持XA协议 |
| 一致性 | 最终一致性:数据可能处于两阶段之间的中间状态 | 强一致性:数据的中间状态对用户不可见 |
| 性能 | 性能较好:一阶段直接提交,不锁定资源 | 性能较差:一阶段锁定资源,二阶段结束才释放 |
| 代码入侵 | 无代码入侵 | 无代码入侵 |
| 数据库支持 | 适用于几乎所有支持SQL的数据库 | 适用于实现XA协议的数据库 |
| 适用场景 | 性能要求高并且接受最终一致性场景 | 要求强一致性并且性能要求不高场景 |

下篇文章:

Spring Cloud系列---Seata分布式事务解决方案TCC模式和Saga模式https://blog.csdn.net/sniper_fandc/article/details/149947829?fromshare=blogdetail&sharetype=blogdetail&sharerId=149947829&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

相关推荐
BUG指挥官13 小时前
Redis已接入AI
数据库·人工智能·spring boot·redis·spring cloud
wno7041 天前
Spring Cloud Hystrix服务容错
spring·spring cloud·hystrix
苏渡苇1 天前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
java·后端·spring·spring cloud·springboot·性能监控
YOU OU2 天前
Spring Cloud Consul
spring cloud·consul·java-consul
重生之我是Java开发战士2 天前
【Spring Cloud】Eureka服务注册发现与LoadBalancer负载均衡
spring cloud·eureka·负载均衡
谢亮_vipxieliang3 天前
ValidX vs Google Guava Preconditions:验证 vs 断言
java·spring boot·后端·spring cloud·hibernate·guava
夏天拐跑了西瓜3 天前
Spring Cloud 微服务实战(八):Hystrix熔断降级——从服务雪崩到断路器模式
spring cloud·hystrix·微服务
重生之我是Java开发战士4 天前
【Spring Cloud】认识微服务与工程搭建
spring·spring cloud·微服务
墨着染霜华5 天前
SpringCloud Gateway线上Nginx代理后自定义下划线请求头丢失(app_name/app_version获取不到)完美解决
nginx·spring cloud·gateway
Rain的Java大神之路6 天前
高并发下的热点账户余额扣减:Redis+Lua脚本实现无锁记账
java·spring boot·redis·后端·spring cloud·缓存·lua