> 文档中心 > SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

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工程详细理解其操作
  • 总结

前言

数据库事务是什么我们就不多做介绍,分布式事务指的是在微服务中跨服务、跨数据源的操作如何保证事务的一致性等问题。

单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

一、Seata简介

1. Seata是什么?

  • Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
  • 官网地址:http://seata.io/zh-cn/
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

2. ID+三组件模型

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

3. 处理过程

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  2. XID 在微服务调用链路的上下文中传播;
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  5. TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

二、Seata下载安装

1. 下载

下载地址:https://github.com/seata/seata/tags
参照博客:https://blog.csdn.net/weixin_43464964/article/details/122557207?spm=1001.2014.3001.5501
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
百度网盘快速下载

链接:https://pan.baidu.com/s/1KXR-Uh7YmQ7AomquPO53Zw 提取码:bi22

2. 安装

  1. 将下载好的压缩包进行解压
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

3. 修改file.conf

  1. 修改conf目录下的file.conf配置文件
    先将出厂的配置备份一下,再进行修改。
    主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
  2. 修改service模块
    以文本打开file.conf文件
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
    修改默认分组名(自己起一个)
 vgroup_mapping.my_test_tx_group = "fsp_tx_group"

SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
3. 修改事务日志存储模块store
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
seata日志默认存储在文件中,我们修改为存储到数据库
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
4. 在数据库中新建seata库
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
5. 创建表
建表语句在conf下
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
右键打开,复制建表语句,执行

4. 修改registry.conf

  1. 备份registry.conf
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
  2. 将注册信息改为注册进入nacos
    SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

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
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
启动成功

5. 开发中使用

我们知道Spring中有本地事务控制的方式,是注解声明式的,给方法添加上@Transactional即可实现同一方法自动开启事务管理。
那么同样的,分布式事务也是声明式的,只需换一个注解:@GlobalTransactional,加在业务方法上即可开启分布式事务。
开发使用起来十分简单(●’◡’●)

三、开发实战

  1. 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。
  2. 当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存,再通过远程调用账户服务来扣减用户账户里面的余额,最后在订单服务中修改订单状态为已完成。
  3. 该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。
  4. 下订单—>扣库存—>减账户(余额)

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表。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
建表的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_logdrop 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
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
application.yml,注意事务组的名称要与我们前面修改的seata配置文件file.conf中的事务组相对应
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
file.conf修改对应信息
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
registry.conf修改注册进入nacos
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
主要的业务逻辑是OrderServiceImpl,下单时要下订单->减库存->扣余额->改(订单)状态
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

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

看一下数据库,成功了
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

3. 测试模拟报错

先把2001的Service上的@GlobalTransactional注解去掉
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
再给2003的service添加上一个休眠的方法,模拟他出现了超时异常
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }

然后重启程序,再次访问测试接口

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

理所当然的,2003报错了,这时我们查看数据库发现了一个大问题,订单也创建了,库存也减少了,但是扣用户钱时出错了,用户钱没扣。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

4. 添加上@GlobalTransactional注解

SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
重启下,再次访问接口测试

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

同样的,扣钱时也报错了
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
但是我们查询数据库发现,订单也没有,钱也没扣,库存也没减,这就说明操作被分布式事务管理起来了,当其中一步出错时,其他的步骤如果已经成功就会被回滚。

五、原理介绍及补充

1. 原理

SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
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框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

二阶段回滚

二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
回滚方式便是用“before image”还原业务数据;
但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,
如果不一致就说明有脏写,出现脏写就需要转人工处理。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理

4. debug工程详细理解其操作

去掉2003工程的超时异常,打上断点
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
启动nacos、seata,debug启动2001、2002、2003三个工程。
访问接口,进入断点

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

查看seata库的branch_table表,记录了事务提交的信息
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
再看下订单库的undo_log表
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
我们将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的更新语句,恢复到之前状态(逆向补偿)。
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
我们再看下seata库的global_table表,这个表存储的是分布式事务的发起者的信息(加了@GlobalTransactional注解的方法).
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
再请看lock_table,发现三个表都被加锁了,并且也可以看到全局的id和分支id
SpringCloud Alibaba实战--第八篇:Seata分布式事务处理
好,接下来我们请放行debug,再看所有的seata表和undo_log表,发现所有的数据都被删除了。
这也表示分布式事务结束,成功保障了分布式操作的原子性!!!


总结

Seata分布式事务处理过程

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID;
  2. XID 在微服务调用链路的上下文中传播;
  3. RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. TM 向 TC 发起针对 XID 的全局提交或回滚决议;
  5. 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,解析出前置镜像和后置镜像,二阶段提交或回滚事务,完成后删除相关分布式事务日志。

松山湖网站