SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
系列文章目录
微服务新王SpringCloudAlibaba
文章目录
- 系列文章目录
- 前言
- 一、Seata简介
- 1. Seata是什么?
- 2. ID+三组件模型
- 3. 处理过程
- 二、Seata下载安装
- 1. 下载
- 2. 安装
- 3. 修改file.conf
- 4. 修改registry.conf
- 4. 启动
- 5. 开发中使用
- 三、开发实战
- 1. 创建业务数据库
- 2. 新建订单模块Order-Module
- 3. 新建库存模块Storage-Module
- 4. 新建账户Account-Module
- 四、测试
- 1. 启动程序
- 2. 测试接口
- 3. 测试模拟报错
- 4. 添加上@GlobalTransactional注解
- 五、原理介绍及补充
- 1. 原理
- 2. Seata的模式
- 3. AT模式
- 一阶段加载
- 二阶段提交
- 二阶段回滚
- 4. debug工程详细理解其操作
- 总结
前言
数据库事务是什么我们就不多做介绍,分布式事务指的是在微服务中跨服务、跨数据源的操作如何保证事务的一致性等问题。
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
一、Seata简介
1. Seata是什么?
- Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
- 官网地址:http://seata.io/zh-cn/
2. ID+三组件模型
Transaction ID XID: 全局唯一的事务ID;
Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
Transaction Manager ™:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
Resource Manager (RM):控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚;
3. 处理过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
二、Seata下载安装
1. 下载
下载地址:https://github.com/seata/seata/tags
参照博客:https://blog.csdn.net/weixin_43464964/article/details/122557207?spm=1001.2014.3001.5501
百度网盘快速下载
链接:https://pan.baidu.com/s/1KXR-Uh7YmQ7AomquPO53Zw
提取码:bi22
2. 安装
- 将下载好的压缩包进行解压
3. 修改file.conf
- 修改conf目录下的file.conf配置文件
先将出厂的配置备份一下,再进行修改。
主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
- 修改service模块
以文本打开file.conf文件
修改默认分组名(自己起一个)
vgroup_mapping.my_test_tx_group = "fsp_tx_group"
3. 修改事务日志存储模块store
seata日志默认存储在文件中,我们修改为存储到数据库
4. 在数据库中新建seata库
5. 创建表
建表语句在conf下
右键打开,复制建表语句,执行
4. 修改registry.conf
- 备份registry.conf
- 将注册信息改为注册进入nacos
4. 启动
先启动nacos
再启动seata-server
注意:jdk要使用8版本,使用以上版本会报错。
环境变量是jdk1.8,但是使用命令查看java版本时却发现是1.9版本:https://blog.csdn.net/weixin_43464964/article/details/122700372?spm=1001.2014.3001.5501
启动成功
5. 开发中使用
我们知道Spring中有本地事务控制的方式,是注解声明式的,给方法添加上@Transactional即可实现同一方法自动开启事务管理。
那么同样的,分布式事务也是声明式的,只需换一个注解:@GlobalTransactional,加在业务方法上即可开启分布式事务。
开发使用起来十分简单(●’◡’●)
三、开发实战
- 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
- 当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
- 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
- 下订单—>扣库存—>减账户(余额)
1. 创建业务数据库
使用SQL创建三个数据库
CREATE DATABASE seata_order; CREATE DATABASE seata_storage; CREATE DATABASE seata_account;
seata_order库下建t_order表
CREATE TABLE t_order (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`count` INT (11) DEFAULT NULL COMMENT '数量',`money` DECIMAL (11, 0) DEFAULT NULL COMMENT '金额',`status` INT (1) DEFAULT NULL COMMENT '订单状态:0:创建中;1:已完结'
) ENGINE = INNODB AUTO_INCREMENT = 7 DEFAULT CHARSET = utf8;SELECT*
FROMt_order;
seata_storage库下建t_storage 表
CREATE TABLE t_storage (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY,`product_id` BIGINT (11) DEFAULT NULL COMMENT '产品id',`total` INT (11) DEFAULT NULL COMMENT '总库存',`used` INT (11) DEFAULT NULL COMMENT '已用库存',`residue` INT (11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;INSERT INTO seata_storage.t_storage (`id`,`product_id`,`total`,`used`,`residue`
)
VALUES('1', '1', '100', '0', '100');SELECT*
FROMt_storage;
seata_account库下建t_account 表
CREATE TABLE t_account (`id` BIGINT (11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',`user_id` BIGINT (11) DEFAULT NULL COMMENT '用户id',`total` DECIMAL (10, 0) DEFAULT NULL COMMENT '总额度',`used` DECIMAL (10, 0) DEFAULT NULL COMMENT '已用余额',`residue` DECIMAL (10, 0) DEFAULT '0' COMMENT '剩余可用额度'
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;INSERT INTO seata_account.t_account (`id`,`user_id`,`total`,`used`,`residue`
)
VALUES('1', '1', '1000', '0', '1000');SELECT*
FROMt_account;
订单-库存-账户3个库下都需要建各自的回滚日志表。
在seata的conf下找到db_undo_log.sql,分别在我们的三个业务库下创建undo_log表。
建表的sql
-- the table to store seata xid data
-- 0.7.0+ add context
-- you must to init this sql for you business databese. the seata server not need it.
-- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
drop table IF EXISTS `undo_log`;
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,`ext` varchar(100) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2. 新建订单模块Order-Module
这里不再进行代码的编写,只对需要注意的部分进行讲解,大家自行下载代码过一下流程。
https://gitee.com/xiaoZ1712/cloud2021/tree/study-alibaba/
模块名
seata-order-service2001
pom依赖
注意:要剔除spring-cloud-starter-alibaba-seata自带的io.seata,使用和我们的seata版本对应的io.seata
application.yml,注意事务组的名称要与我们前面修改的seata配置文件file.conf中的事务组相对应
file.conf修改对应信息
registry.conf修改注册进入nacos
主要的业务逻辑是OrderServiceImpl,下单时要下订单->减库存->扣余额->改(订单)状态
3. 新建库存模块Storage-Module
模块名
seata-storage-service2002
4. 新建账户Account-Module
模块名
seata-account-service2003
(创建完成记得修改配置文件)
四、测试
1. 启动程序
启动nacos,seata-server及三个后端工程
2. 测试接口
测试2001下单的接口
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
看一下数据库,成功了
3. 测试模拟报错
先把2001的Service上的@GlobalTransactional注解去掉
再给2003的service添加上一个休眠的方法,模拟他出现了超时异常
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
然后重启程序,再次访问测试接口
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
理所当然的,2003报错了,这时我们查看数据库发现了一个大问题,订单也创建了,库存也减少了,但是扣用户钱时出错了,用户钱没扣。
4. 添加上@GlobalTransactional注解
重启下,再次访问接口测试
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
同样的,扣钱时也报错了
但是我们查询数据库发现,订单也没有,钱也没扣,库存也没减,这就说明操作被分布式事务管理起来了,当其中一步出错时,其他的步骤如果已经成功就会被回滚。
五、原理介绍及补充
1. 原理
TM 开启分布式事务(TM 向 TC 注册全局事务记录);
按业务场景,编排数据库、服务等事务内资源(RM 向 TC 汇报资源准备状态 );
TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
TC 汇总事务信息,决定分布式事务是提交还是回滚;
TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。
2. Seata的模式
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata默认使用的是AT模式,关于各种模式官网有详尽的介绍。
官网说明:http://seata.io/zh-cn/docs/overview/what-is-seata.html
3. AT模式
一阶段加载
在一阶段,Seata 会拦截“业务 SQL”,
1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
3 其保存成“after image”,最后生成行锁。
以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交
二阶段如是顺利提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;
但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,
如果不一致就说明有脏写,出现脏写就需要转人工处理。
4. debug工程详细理解其操作
去掉2003工程的超时异常,打上断点
启动nacos、seata,debug启动2001、2002、2003三个工程。
访问接口,进入断点
http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
查看seata库的branch_table表,记录了事务提交的信息
再看下订单库的undo_log表
我们将rollback_info信息粘贴出来,用JSON格式解析工具解析一下。
可以看到,有前置快照和后置快照,当出现异常需要回滚时,seata回根据前置和后置快照进行数据回滚。
举个例子:
我们执行一条sql语句
update set age = 28 where id = 1
seata服务器发现我们要执行这条语句,给他做增强,解析这条语句,指定我们要修改的是id=1的信息
seata先把id=1的信息查出来,作为前置镜像(beforeImage),保存在undo_log中。
select age from t where id = 1 ; --> age = 25
然后解析出修改后的信息,作为后置镜像(afterImage),也保存
select age from t where id = 1 ; --> age = 28
在执行成功后,将记录的信息删除,当执行失败时
seata先拿库里id=1的信息和修改后的信息做比较(检测是否出现脏写),当出现脏写时(本事务未结束,但是age数据已经被修改)需要进行人工干预处理,当一切正常时就反解析出beforeImage的更新语句,恢复到之前状态(逆向补偿)。
我们再看下seata库的global_table表,这个表存储的是分布式事务的发起者的信息(加了@GlobalTransactional注解的方法).
再请看lock_table,发现三个表都被加锁了,并且也可以看到全局的id和分支id
好,接下来我们请放行debug,再看所有的seata表和undo_log表,发现所有的数据都被删除了。
这也表示分布式事务结束,成功保障了分布式操作的原子性!!!
总结
Seata分布式事务处理过程
- TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
- XID 在微服务调用链路的上下文中传播;
- RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
- TM 向 TC 发起针对 XID 的全局提交或回滚决议;
- TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
实体类存放包命名的含义:
entity、domain: 表示数据库映射实体类存放。
vo:vo对象通常用于前后端交互类。
dto:dto是service传给controller用的,为了和vo区分。
下载地址:https://github.com/seata/seata/tags
官网地址:http://seata.io/zh-cn/
商用推荐使用1.0以上
使用@GlobalTransactional即可发布开启分布式事务。
seata分布式事务原理是:两个阶段,第一阶段解析SQL,解析出前置镜像和后置镜像,二阶段提交或回滚事务,完成后删除相关分布式事务日志。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理相关推荐
- SpringCloud Alibaba实战第八课 缓存设计、网关认证、重构策略
19 性能为王:微服务架构中的多级缓存设计 前一讲我们学习了利用 Seata 构建微服务环境的分布式事务架构,通过完整的案例也了解了 Seata 的执行过程. 本讲咱们探讨缓存这个话题,看在微服务环境 ...
- SA实战 ·《SpringCloud Alibaba实战》第03章-微服务介绍
作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe001.github.io 文章汇总:https://binghe001.github.io/md/all ...
- SpringCloud Alibaba实战第九课 分布式事务理论、DevOps运维
22 一致性挑战:微服务架构下的数据一致性解决方案 上一讲我们介绍了如何在微服务架构中设计统一的用户认证方案.本讲咱们填之前埋下的一个坑,如何在微服务架构下有效保障数据一致性问题.本讲咱们涉及三方面内 ...
- SpringCloud Alibaba实战--第二篇:NacosⅠ服务注册和配置中心
系列文章目录 微服务新王SpringCloudAlibaba 文章目录 系列文章目录 前言 一.Nacos是什么?能干啥? 二.Nacos下载及安装 1. 下载 2. 安装并运行 3. 对比Eurek ...
- SpringCloud Alibaba实战第一课 架构演进之路
开篇词 Spring Cloud Alibaba 未来的微服务生态标准 你好,我是老齐,一名从业近 20 年的 IT 老兵,曾在京东.财政部.宜信.工商银行等机构从事架构设计与核心研发工作,有多个亿级 ...
- Spring Cloud Alibaba 实战(八)SkyWalking篇
1. SkyWalking 简介 Skywalking 是由国内开源爱好者吴晟(原 OneAPM 工程师,目前在华为)开源并提交到 Apache 孵化器的产品,它同时吸收了 Zipkin/Pinpoi ...
- SpringCloud Alibaba实战(12:引入Dubbo实现RPC调用)
源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 大家好,我是老三,断更了半年,我又滚回来继续写这个系列了,还有人看吗-- 在前面的章 ...
- SpringCloud Alibaba 之 Config配置中心,Redis分布式锁详解
目录 1.服务配置中心 1.1 服务配置中心介绍 1.2 Nacos Config 实践 1.2.1 Nacos config 入门案例 1.2.2 Nacos 配置动态刷新 1.2.3 配置共享 ...
- 从零开始SpringCloud Alibaba实战(32)——spring-cloud-starter-oauth2认证授权服务
OAuth2.0介绍 OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容.OAuth2. ...
最新文章
- Keil中找不到芯片,往Keil中添加不存在的芯片,Keil安装pack文件
- koa 接口返回数据_koa-api
- codeforces808G Anthem of Berland(kmp+自动机+dp)
- Python 运算符 if和while的使用
- win10上编译libharu库
- [渝粤教育] 西南科技大学 机械制造装备及工艺 在线考试复习资料
- easyui datagrid url不请求请求_Go Web编程--深入学习解析HTTP请求
- 监测到本计算机上装有sql,检测局域网电脑是否有安装SQL Server数据库
- 真彩色图像数据量 计算_免费深度学习实战:高效训练及加速推理,送英特尔神经计算棒 2 代 (报名·深圳)...
- 使用Visual Studio 2013编写Windows程序
- 利用python编写一个猜数游戏程序。由系统随机产成一个0~100之间的整数,玩家可以进行5次竞猜。如果猜对了,则提示“恭喜你,猜对了“,并结束游戏;如果猜错了,提示玩家一个方向提示,告诉玩家大了小了
- android uml建模工具 mac,UML建模工具Mac版
- 自动化运维原来如此简单之工具建设
- go[x]agent在windows和ubuntu下的安装步骤
- Python中最快的搜索引擎之一:ThreadSearch(自己开发)(abccdee1)
- 计算机加入域用户名,将客户端计算机加入到域中
- Windows 10 中的存储空间
- 【解决方案】基于国标GB28181协议EasyGBS平台搭建的交警执法综合管理视频监控方案
- 设计一个计算器,实现0-9数字间的加减乘除运算
- sql注入中的--+注释问题探索