[Spring] Seata详解
🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(97平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(95平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 1. 认识Seata
- 2. 微服务集成Seata
- 2.1 引入依赖
- 2.2 改造配置
- 2.3 添加数据库表
- 2.4 测试
- 3. XA模式
- 3.1 两阶段提交(常见面试题)
- 3.2 Seata的XA模型
- 3.3 优缺点
- 3.4 实现步骤
- 4. AT模式
- 4.1 Seata的AT模型
- 4.2 流程梳理
- 4.3 AT与XA的区别(常见面试题)
首先我们看看项目中的下单业务流程:
由于订单,购物车,商品分别在三个不同的微服务中,而每个微服务都有自己独立的数据库,因此下单的过程中就会跨多个数据库完成业务,而每个微服务都会执行自己的本地事务:
- 交易服务: 下单事务
- 购物车服务: 清理购物车事务
- 库存服务: 扣减库存事务
整个业务中,各个本地事务是有关联的,因此每个微服务的本地事务,也可以称为分支事务,多个有关联的分支事务就一起组成了全局事务,我们必须保证整个全局事务同时成功或者失败.
我们知道每个分支事务就是传统的单体事务,都可以满足ACID特性,但全局事务跨过多个服务,多个数据库,是否还满足呢?
我们来做一个测试,先进入购物车页面:
目前购物车中有4个商品,然后结算下单,进入订单结算页面:
然后将购物车中某个商品的库存修改为0:
然后,提交订单,最终因库存不足导致下单失败:
然后我们去查看购物车列表,发现购物车数据依然被清空了,并为回滚:
事务并未遵循ACID原则,归其原因就是参与事务的多个子业务在不同的微服务,跨越了不同的数据库,虽然每个单独的业务都能在本地遵循ACID,但是他们互相之间没有感知,不知道有人失败了,无法保证最终结果的统一,也就无法遵循ACID的事务特性了.
这就是分布式事务问题,出现一下的情况可能产生分布式事务的问题:
- 业务跨过多个服务实现
- 业务跨过多个数据源的实现
接下来这一章我们就一起来探讨一下如何解决分布式事务的问题:
1. 认识Seata
解决分布式事务的方案有很多,但实现起来都比较复杂,因此我们一般会使用开源的框架来解决分布式问题,子啊众多的开源分布式事务框架中,功能最完善,使用最多的就是阿里巴巴在2019年开源的Seata.
网站: 官方文档
其实分布式事务产生的一个重要的原因,就是参与事务的多个分支事务相互无感知,不知道彼此的执行状态,因此解决分布式事务的思想非常简单:
就是找一个统一的事务协调者,与多个分支事务通信,检测每个分支事务的执行状态,保证全局事务下的每个分支事务同时成功或者是失败即可,大多数的分布式事务框架基本都是基于这个理论来实现的.
Seata也不例外,在Seata的事务管理中有三个重要的角色:
- TC(Transaction Coordinator) - 事务协调者: 维护全局和分支事务的状态,协调全局事务提交或回滚.
- TM(Transaction Manager) - 事务管理器: 定义全局事务的范围,开始全局事务,提交或回滚全局事务.
- RM(Resource Manager) - 资源管理器: 管理事务分支,与TC交谈以注册分支事务和报告事务分支的状态,并驱动分支事务提交或者是回滚.
Seata的工作架构如图所示:
其中,TM和RM可以理解为Seata的客户端部分,引入到参与事务的微服务依赖中即可,将来TM和RM就会协助微服务,实现本地分支事务与TC之间的交互,实现事务的提交或回滚.
而TC服务则是事务协调中心,是一个独立的微服务,需要单独部署.
2. 微服务集成Seata
2.1 引入依赖
为了方便各个微服务模块集成Seata,我们需要把Seata配置共享到Nacos,因此我们需要在需要分布式事务的模块中不仅仅要引入Seata依赖,还要一如Nacos依赖.
<!--统一配置管理--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!--读取bootstrap文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency><!--seata--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId></dependency>
2.2 改造配置
首先在Nacos上添加一个共享的Seata配置,命名为shared-seata.yaml
;
内容如下:
seata:registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址type: nacos # 注册中心类型 nacosnacos:server-addr: 192.168.150.101:8848 # nacos地址namespace: "" # namespace,默认为空group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUPapplication: seata-server # seata服务名称username: nacospassword: nacostx-service-group: hmall # 事务组名称service:vgroup-mapping: # 事务组与tc集群的映射关系hmall: "default"
为想要添加分布式事务的模块添加bootstrap.yml
内容如下:
spring:application:name: trade-service # 服务名称profiles:active: devcloud:nacos:server-addr: 192.168.150.101 # nacos地址config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置- dataId: shared-seata.yaml # 共享seata配置
可以看到这里加载了共享的Seata配置:
然后改造application.yml文件,内容如下;
server:port: 8085
feign:okhttp:enabled: true # 开启OKHttp连接池支持sentinel:enabled: true # 开启Feign对Sentinel的整合
2.3 添加数据库表
Seata的客户端在解决分布式事务的时候需要记录一些中间数据,保存在数据库中,因此我们要先准备一个这样的表.
这里我们提前写好了sql脚本
结果:
到此为止,微服务的整合工作就完成了.
2.4 测试
接下来就是测试分布式事务的时候了.
我们找到trade-service模块下的com.hmall.trade.service.impl.OrderServiceImpl
类中的createOrder
方法,也就是下单业务方法.
将其上的@Transactional
注解修改为Seata提供的@GlobalTransactional
:
@GlobalTransactional
注解就是在标记事务的起点,将来TM就会基于这个方法判断全局的事务范围,初始化全局事务.
我们重启trade-service、item-service、cart-service三个服务.再次测试,发现分布式问题已经解决.
那么Seata是如何解决分布式事务的呢?
3. XA模式
Seata支持四种不同的分布式事务解决方案:
- XA
- TCC
- AT
- SAGA
这里我们以XA模式和AT模式来讲解实现原理:
XA规范是X/Open组织定义的分布式事务处理标准,XA规范描述了全局的TM与局部的RM之间的接口,几乎所有的主流数据库都对XA规范提供了支持.
3.1 两阶段提交(常见面试题)
目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交:
正常情况:
异常情况:
一阶段:
- 事务协调者通过每个分支事务执行本地事务(你们该执行事务了)
- 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁.(执行好了队长/不行队长,我还没完事)
二阶段:
- 事务协调者基于一阶段的报告来判断下一步的操作(嗯,让我看看你们都是什么情况)
- 如果一阶段都成功,则通知所有事务参与者提交事务(好的,提交)
- 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务(不行,有人没准备好,剩下的人都滚回去)
3.2 Seata的XA模型
Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:
RM一阶段的工作:
- 注册分支事务到TC
- 执行分支业务sql但不提交
- 报告执行状态到TC
TC二阶段的工作:
4. TC检测各个分支事务执行状态,如果都成功,同坐所有的RM提交事务,如果有失败的,通知所有RM回滚事务
RM二阶段的工作:
5. 接收指令,提交或回滚事务
3.3 优缺点
XA模式的优点是什么?
- 事务的一致性,满足ACID原则
- 常用数据库都支持,简单实现,并且没有代码侵入
XA模式的缺点是什么?
- 因为一阶段需要锁定数据库的资源,等待二阶段提交才能释放,性能较差.
- 依赖关系型数据库实现事务
3.4 实现步骤
首先,我们要在配置文件中指定要采用的分布式事务的模式,我们可以在Nacos中的共享的shared-seata.yaml
配置文件中设置:
seata:data-source-proxy-mode: XA
其次,我们要利用@GlobalTransactional
标记分布式事务的入口方法:
4. AT模式
AT模式同样是分阶段提交事务模型,不过弥补了XA模式中资源锁定周期过长的缺陷.
4.1 Seata的AT模型
基本流程图:
阶段一RM的工作:
- 注册分支事务
- 记录undo-log日志(数据快照)
- 执行业务sql并提交
- 报告事务状态
阶段二提交时RM的工作:
- 删除undo-log即可
阶段二回滚时RM的工作:
- 根据undo-log回复数据到更新前
4.2 流程梳理
我们用一个真实的业务来梳理一下AT模式的原理,比如现在有一个数据库表,记录用户余额:
id | money |
---|---|
1 | 100 |
流程图:
4.3 AT与XA的区别(常见面试题)
简述AT模式与XA模式最大的区别是什么
- XA模式一阶段不提交事务,锁定资源,AT模式一阶段直接提交,不锁定资源.
- XA模式依赖数据库机制实现回滚,AT模式利用数据快照的方式实现回滚
- XA模式强一致,AT模式最终一致.
课件AT模式使用起来更加简单,五业务侵入,性能更好,因此90%的分布式事物都可以用AT模式来解决.