ACID特性
分布式事务的引入
举例:分布式系统会把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务之间远程协作才能完成事务操 作,这种分布式系统环境下由不同的服务之间通过网络远程协作完成事务称之为分布式事务,例如用户注册送积分事务、创建订单减库存事务,银行转账事务等都是分布式事务。
通过以上的图中我们可以看出,其实只要涉及到操作多个数据源,就可能会产生事务问题,当然在实际开发中我们要尽量避免这种问题的出现,当然如果避免不了,我们就需要进行解决,在我们的微服务系统架构中,目前比较好,比较常用的解决方案就是Seata。
随着互联化的蔓延,各种项目都逐渐向分布式服务做转换。如今微服务已经普遍存在,本地事务已经无法满足分布式的要求,由此分布式事务问题诞生。 分布式事务被称为世界性的难题,目前分布式事务存在两大理论依据:
这个定理的内容是指:在一个分布式系统中、Consistency(一致性)、Availability(可用性)、Partitiontolerance(分区容错性),三者不可得兼。
CAP是无法同时存在的,通过下面的例子进行说明
总结:在分布式系统中,p是必然的存在的,所以我们只能在C和A之间进行取舍,在这种条件下就诞生了BASE理论
BASE是Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结, 是基于CAP定理逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
那这个位置我们依旧可以用我们刚才的例子来进行说明
基本可用:保证核心服务是可以使用的,至于其他的服务可以适当的降低响应时间,甚至是服务降级
软状态:存在中间状态,不影响整体系统使用,数据同步存在延时。
最终一致性:再过了流量高峰期以后,经过一段时间的同步,保持各服务数据的一致。
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
在我们的微服务系统中,对应业务被对应的拆分成独立模块,在官方提供的架构图中,我们可以看出当前是三个服务分别是:
在这套架构中,用户下单购买商品的业务,就需要三个服务来完成,每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题就没办法保证,Seata就是来进行解决这种问题的解决方案。
Seata中文文档
https://seata.io/zh-cn/docs/overview/what-is-seata.html
注:上述的TC (Transaction Coordinator) - 事务协调者 也就是Seata-Server服务端。
Seata提供了四种不同的分布式事务解决方案:
基本概念:AT模式是一种无侵入的分布式事务解决方案,在AT模式下,用户只需要关注自己的“业务SQL”,用户的“业务SQL”作为一阶段,Seata框架会自动的生成事务的二阶段提交和回滚。
整体机制
两阶段提交协议的演变:
一阶段:在一阶段中,Seata会拦截“业务SQL”,首先解析SQL的语义,找到要更新的业务数据,在数据更新前,保存下来“undo”,然后执行“业务SQL”更新数据,更新之后再次保存数据“redo”,最后生成行锁,这些操作都在本地数据库事务内完成,这样保证了一阶段的原子性。
二阶段:相对一阶段,二阶段比较简单,负责整体的回滚和提交,如果之前的一阶段中有本地事务没有通过,那么就执行全局回滚,否则执行全局提交,回滚用到的就是一阶段记录的“undo Log”,通过回滚记录生成反向更新SQL并执行,以完成分支的回滚。当然事务完成后会释放所有资源和删除所有的日志。
图解
注:关于AT模式的详细使用介绍见五、六章节!
Seata 1.2.0 版本重磅发布新的事务模式:XA 模式,实现对 XA 协议的支持。
我们从三个方面来深入分析:
首先我们需要先了解一下什么是XA?
XA 规范早在上世纪 90 年代初就被提出,用以解决分布式事务处理这个领域的问题。
注意:不存在某一种分布式事务机制可以完美适应所有场景,满足所有需求。
现在,无论 AT 模式、TCC 模式还是 Saga 模式,这些模式的提出,本质上都源自 XA 规范对某些场景需求的无法满足。
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准
XA 规范 描述了全局的事务管理器与局部的资源管理器之间的接口。 XA规范 的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使 ACID 属性跨越应用程序而保持有效。
XA 规范 使用两阶段提交(2PC,Two-Phase Commit)来保证所有资源同时提交或回滚任何特定的事务。
XA 规范 在上世纪 90 年代初就被提出。目前,几乎所有主流的数据库都对 XA 规范 提供了支持。
DTP模型定义如下角色:
案例解释:
XA协议的痛点
如果一个参与全局事务的资源 “失联” 了(收不到分支事务结束的命令),那么它锁定的数据,将一直被锁定。进而,甚至可能因此产生死锁。
这是 XA 协议的核心痛点,也是 Seata 引入 XA 模式要重点解决的问题。
Seata 定义了全局事务的框架。
全局事务 定义为若干 分支事务 的整体协调:
Seata 的 全局事务 处理过程,分为两个阶段:
Seata 的所谓事务模式是指:运行在 Seata 全局事务框架下的 分支事务 的行为模式。准确地讲,应该叫作 分支事务模式。
不同的 事务模式 区别在于 分支事务 使用不同的方式达到全局事务两个阶段的目标。即,回答以下两个问题:
我们以AT模式举例:
XA模式:
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务模式。
为什么要在 Seata 中增加 XA 模式呢?支持 XA 的意义在哪里呢?
本质上,Seata 已经支持的 3 大事务模式:AT、TCC、Saga 都是补偿型 的。
补偿型事务处理机制构建在事务资源之上(要么在中间件层面,要么在应用层面),事务资源 本身对分布式事务是无感知的。
事务资源对分布式事务的无感知存在一个根本性的问题:无法做到真正的全局一致性 。
比如,一条库存记录,处在 补偿型 事务处理过程中,由 100 扣减为 50。此时,仓库管理员连接数据库,查询统计库存,就看到当前的 50。之后,事务因为异外回滚,库存会被补偿回滚为 100。显然,仓库管理员查询统计到的 50 就是 脏 数据。所以补偿型事务是存在中间状态的(中途可能读到脏数据)。
与 补偿型 不同,XA 协议 要求 事务资源 本身提供对规范和协议的支持。
因为 事务资源 感知并参与分布式事务处理过程,所以 事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。
比如,刚才提到的库存更新场景,XA 事务处理过程中,中间状态数据库存 50 由数据库本身保证,是不会仓库管理员的查询统计看到的。
除了 全局一致性 这个根本性的价值外,支持 XA 还有如下几个方面的好处:
代码获取
下面使用官方的一个例子进行演示:
官方案例下载地址:https://github.com/seata/seata-samples.git
小编这里给大家分享了项目网盘下载地址:
初始化工程
XA案例所在工程seata-xa,如下图所示:
数据库初始化
将数据库文件脚本all_in_one.sql导出到数据库中,并修改项目配置文件application.properties数据库连接。
初始数据:
启动项目
配置文件修改完成后,分别启动以上四个基础服务即可。
代码测试
访问链接:
http://127.0.0.1:8084/purchase
调用成功后,返回SUCCESS
此时,数据如下:
注:以上是正常的业务处理流程,下面演示发生分布式事务问题的情况,并重启订单order服务。
将表中数据恢复为初始化的状态,再次调用http://127.0.0.1:8084/purchase
此时,查看表中数据无变化,即XA模式应用生效。
官方案例演示图:
案例解析:
整体运行机制:
总结
在当前的技术发展阶段,不存一个分布式事务处理机制可以完美满足所有场景的需求。
一致性、可靠性、易用性、性能等诸多方面的系统设计约束,需要用不同的事务处理机制去满足。
Seata 项目最核心的价值在于:构建一个全面解决分布式事务问题的 标准化 平台。
基于 Seata,上层应用架构可以根据实际场景的需求,灵活选择合适的分布式事务解决方案。
XA 模式的加入,补齐了 Seata 在 全局一致性 场景下的缺口,形成 AT、TCC、Saga、XA 四大事务模式 的版图,基本可以满足所有场景的分布式事务处理诉求。
切换XA模式,只需要修改数据源代理为XA模式即可。
@Configuration public class StockXADataSourceConfiguration { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource druidDataSource() { return new DruidDataSource(); } @Bean("dataSourceProxy") public DataSource dataSource(DruidDataSource druidDataSource) { // DataSourceProxy for AT mode // return new DataSourceProxy(druidDataSource); // DataSourceProxyXA for XA mode return new DataSourceProxyXA(druidDataSource); } @Bean("jdbcTemplate") public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) { return new JdbcTemplate(dataSourceProxy); } }
TCC 是分布式事务中的二阶段提交协议,它的全称为 Try-Confirm-Cancel,即资源预留(Try)、确认操作(Confirm)、取消操作(Cancel),他们的具体含义如下:
TCC 是一种侵入式的分布式事务解决方案,以上三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但优点是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
Seata TCC 模式跟通用型 TCC 模式原理一致。
TCC和AT区别
AT 模式基于 支持本地 ACID 事务 的 关系型数据库:
相应的,TCC 模式,不依赖于底层数据资源的事务支持:
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
特点
详细讲解
使用案例参考
https://seata.io/zh-cn/blog/integrate-seata-tcc-mode-with-spring-cloud.html
官方文档地址
https://seata.io/zh-cn/docs/user/saga.html
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务(执行处理时候出错了,给一个修复的机会)都由业务开发实现。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
之前我们学习的Seata分布式三种操作模型中所使用的的微服务全部可以根据开发者的需求进行修改,但是在一些特殊环境下,比如老系统,封闭的系统(无法修改,同时没有任何分布式事务引入),那么AT、XA、TCC模型将全部不能使用,为了解决这样的问题,才引用了Saga模型。
比如:事务参与者可能是其他公司的服务或者是遗留系统,无法改造,可以使用Saga模式。
Saga模式是Seata提供的长事务解决方案,提供了异构系统的事务统一处理模型。在Saga模式中,所有的子业务都不在直接参与整体事务的处理(只负责本地事务的处理),而是全部交由了最终调用端来负责实现,而在进行总业务逻辑处理时,在某一个子业务出现问题时,则自动补偿全面已经成功的其他参与者,这样一阶段的正向服务调用和二阶段的服务补偿处理全部由总业务开发实现。
目前Seata提供的Saga模式只能通过状态机引擎来实现,需要开发者手工的进行Saga业务流程绘制,并且将其转换为Json配置文件,而后在程序运行时,将依据子配置文件实现业务处理以及服务补偿处理,而要想进行Saga状态图的绘制,一般需要通过Saga状态机来实现。
基本原理:
Seata Saga 提供了一个可视化的状态机设计器方便用户使用,代码和运行指南请参考:
https://github.com/seata/seata/tree/develop/saga/seata-saga-statemachine-designer
状态机设计器演示地址
http://seata.io/saga_designer/index.html
状态机设计器视频教程
http://seata.io/saga_designer/vedio.html
AT模式,是我们常用的处理模式,也是使用最多的一种。
XA和AT,是无侵入式的,二者间的切换只需要更改数据源的代理对象即可。
TCC需要我们手动、自定义,try、Confirm、Cancel。
Saga是针对老项目,不可更改的项目,通过外部的形式解决调用的时候出现的分布式问题,通过Saga中的状态机进行总业务逻辑的设计。
下载地址:https://seata.io/zh-cn/blog/download.html
若下载比较慢,小编这里分享了网盘下载链接:
链接:https://pan.baidu.com/s/1wtUb7tpGGYGoIE_h10kjvQ?pwd=yyds 提取码:yyds
这里下载的是Seata的seata-server-1.5.2版本。
解压后的文件夹结构如下所示:
修改配置文件
配置文件所在位置:
seata—>conf目录—>application.yml文件
完整配置文件如下:
server: port: 7091 spring: application: name: seata-server # 日志配置 logging: config: classpath:logback-spring.xml file: path: ${user.home}/logs/seata # 不外接日志,故如下配置可暂不考虑 extend: logstash-appender: destination: 127.0.0.1:4560 kafka-appender: bootstrap-servers: 127.0.0.1:9092 topic: logback_to_logstash # 新增加的console控制台, # 可通过访问http://localhost:7091进行登录,账号如下seata/seata console: user: username: seata password: seata seata: # Seata接入Nacos配置中心 config: # support: file, nacos, consul, apollo, zk, etcd3 type: nacos nacos: server-addr: 127.0.0.1:8848 namespace: 0fff12f9-61ef-4c65-b48a-70150001c547 group: SEATA_GROUP username: nacos password: nacos ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" # Seata接入Nacos服务注册中心 registry: # support: file, nacos, eureka, redis, zk, consul, etcd3, sofa type: nacos nacos: application: seata-server server-addr: 127.0.0.1:8848 group: SEATA_GROUP namespace: 0fff12f9-61ef-4c65-b48a-70150001c547 cluster: default username: nacos password: nacos ##if use MSE Nacos with auth, mutex with username/password attribute #access-key: "" #secret-key: "" # 此处可不必配置,由于接入了nacos配置,以下store相关配置可直接通过seataServer.properties进行配置 # store: # support: file 、 db 、 redis # mode: db # server: # service-port: 8091 #If not configured, the default is '${server.port} + 1000' security: secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 tokenValidityInMilliseconds: 1800000 ignore: urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
注意:低版本的配置文件有两个,分别为registry.conf、file.conf两个文件。
比如:seata-server-1.4.2版本,关于以上两个文件的具体配置这里不再介绍。
新建数据库test_seata将seata解压目录script\server\db下面的mqsql.sql导入导入到test_seata中。
导入成功后,如下所示:
我们需要把Seata的一些配置上传到Nacos中,配置比较多,所以官方给我们提供了一个config.txt,我们下载并且修改其中参数,上传到Nacos中。
下载地址:https://github.com/seata/seata/tree/develop/script/config-center
主要修改以下的几处内容:
-- 修改1:配置事务分组 #Transaction routing rules configuration, only for the client service.vgroupMapping.dev_xxkfz_group=default #If you use a registry, you can ignore it service.default.grouplist=127.0.0.1:8091 -- 修改2:配置存储模式为db、数据库连接信息(4.3中初始化的数据库) store.mode=db store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://127.0.0.1:3306/test_seata?useUnicode=true&rewriteBatchedStatements=true store.db.user=xxkfz store.db.password=xxkfz
注:事务分组:用于防护机房停电,来启用备用机房,或者异地机房,容错机制,当然如果Seata-Server配置了对应的事务分组,Client也需要配置相同的事务分组。
配置事务分组: service.vgroupMapping.dev_xxkfz_group=default dev_xxkfz_group:需要与客户端保持一致 ,用户可以自定义。 default:需要跟客户端和application.yml中的cluster保持一致。
以上修改完成后,将config.txt文件放到seata目录下:
此时我们需要把这些配置一个个的加入到Nacos配置中,所以我们需要一个脚本来进行执行,官方已经提供好了,下载地址为:
https://github.com/seata/seata/tree/develop/script/config-center/nacos
将下载完成的nacos-config.sh脚本文件,同样放到seata目录下面。
执行脚本进行导入:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 命名空间 -u nacos -w nacos
参数说明:
-h:host,默认值localhost
-p:port,默认值8848
-g:配置分组,默认为SEATA_GROUP
-t:租户信息,对应Nacos的命名空间ID,默认为空
特此注意:
在执行naocs-config.sh文件脚本的时候,它默认寻找config.txt的路径和我们的路径不同,所以要打开naocs-config.sh文件进行修改,否则无法执行。
找到下方对应的位置,进行修改保存。
修改前:
修改后:
针对以上案例,小编这里执行的导入命令如下:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 0fff12f9-61ef-4c65-b48a-70150001c547 -u nacos -w nacos
开始执行导入了:
经过以上配置完成以后,我们就可以启动nacos和seata-server了,此时我们查看Nacos的配置中心,就会看到我们传入的所有配置信息。
进入到bin目录,双击运行 seata-server.bat即可启动。
启动成功后,浏览器访问nacos地址:http://127.0.0.1:8848/nacos/,然后进入服务列表页面,可以看到seata-server服务已经注册到Nacos上。
导入的配置如下:
seata-server注册到nacos:
用户购买商品的业务逻辑——>整个业务逻辑由3个微服务提供支持:
创建数据库xxkfz_test 执行以下脚本创建表。
DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) 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:已完结', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `t_storage`; CREATE TABLE `t_storage` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `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 '剩余库存', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '库存' ROW_FORMAT = Dynamic; INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100); CREATE TABLE `t_account` ( `id` bigint(11) NOT NULL 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 NULL COMMENT '剩余可用额度', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账户表' ROW_FORMAT = Dynamic; INSERT INTO `t_account` VALUES (1, 1, 1000, 0, 1000); -- Table structure for undo_log -- 注意此处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, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
代码基本工程涉及到三个微服务:
代码基本结构如下:
<dependency> <groupId>com.xxkfz.simplememory</groupId> <artifactId>xxkfz-service-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!-- seata --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>3.1.3</version> </dependency> <!-- MySql连接驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- Druid 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <!-- mybatis plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
注:下面配置文件为账户服务的配置,其他两个类似,这里不再展示。
account-service ——> application.yml文件
server: port: 8090 spring: application: name: account-service cloud: nacos: discovery: # 服务分组 group: SEATA_GROUP server-addr: http://localhost:8848 # 必须填命名空间的ID namespace: 0fff12f9-61ef-4c65-b48a-70150001c547 datasource: type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/xxkfz_test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false #useSSL安全加固 username: xxkfz password: xxkfz # MyBatis Plus配置 mybatis-plus: # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapper-locations: classpath*:mapper/**/*.xml #实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.xxkfz.simplememory.entity global-config: db-config: id-type: auto configuration: # 开启驼峰,开启后,只要数据库字段和对象属性名字母相同,无论中间加多少下划线都可以识别 map-underscore-to-camel-case: true # Seata 配置 seata: application-id: seata-server # 是否启用数据源bean的自动代理 enable-auto-data-source-proxy: false tx-service-group: dev_xxkfz_group # 必须和服务器配置一样 # 必须和服务器配置一样 registry: type: nacos nacos: # Nacos 服务地址 server-addr: http://localhost:8848 group: SEATA_GROUP namespace: 0fff12f9-61ef-4c65-b48a-70150001c547 application: seata-server # 必须和服务器配置一样 username: nacos password: nacos cluster: default config: type: nacos nacos: server-addr: ${spring.cloud.nacos.discovery.server-addr} group: SEATA_GROUP namespace: 0fff12f9-61ef-4c65-b48a-70150001c547 service: vgroup-mapping: tx-service-group: dev_xxkfz_group # 必须和服务器配置一样 disable-global-transaction: false client: rm: # 是否上报成功状态 report-success-enable: true # 重试次数 report-retry-count: 5
order-service ——> OrderController.java
@GetMapping("/order/create") public R<Order> createOrder(Order order) { orderService.create(order); return new R<>(200, "订单创建成功!", order); }
处理逻辑
order-service ——> OrderServiceImpl.java
public void createOrder(Order order) { // 创建订单 baseMapper.createOrder(order); // 库存扣减 storageService.decrStorage(order.getProductId(), order.getCount()); log.info("----->库存扣减Count完成"); // 账户扣减 accountService.decrAccount(order.getUserId(), order.getMoney()); }
声明storage、account的Feign接口。
@FeignClient(value = "storage-service") public interface StorageService { /** * 扣减库存 * * @param productId * @param count * @return */ @PostMapping(value = "/storage/decrease") R decrStorage(@RequestParam("productId") Long productId, @RequestParam("count") Integer count); }
@FeignClient(value = "account-service") public interface AccountService { /** * 账户扣减 * * @param userId * @param money * @return */ @PostMapping(value = "/account/decrease") R decrAccount(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }
当前用户userId = 1 的账户为1000,库存100,模拟用户购买商品1个,消费100,业务调用后,数据库数据状态应该如下:
测试正常下单过程:
下面使用Seata全部事务注解@GlobalTransactional模拟异常,进行回滚操作。
模拟操作:
代码修改1:在创建订单createOrder方法上添加注解@GlobalTransactional。
代码修改2:在账户服务中模拟异常。
由于Seata需要对数据源进行代理,以实现分布式事务的管理,我们需要对数据源进行配置。示例配置如下:
@Configuration public class DataSourceConfig { @Value("${mybatis-plus.mapper-locations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } @Bean @Primary public DataSourceProxy dataSourceProxy(DataSource dataSource) { return new DataSourceProxy(dataSource); } @Bean public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSourceProxy); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(mapperLocations)); sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory()); return sqlSessionFactoryBean.getObject(); } }
分别重启三个服务,同时把数据库表数据恢复为原来的数据。
访问接口:
发现表数据无变化,全局事务回滚成功。
seata 控制台全局事务和分支事务回滚日志
打断点调试查看seata 和 undo_log 数据的变化
断点继续往下走,触发异常,,已插入的数据被回滚,且undo_log被清空,分布式事务回滚正常。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有