SA实战 ·《SpringCloud Alibaba实战》第25章-分布式事务:项目整合Seata实现分布式事务

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 大家好,我是冰河~~❝一不小心《SpringCloud Alibaba实战》专栏都更新到第25章了,再不上车就跟不上了,小伙伴们快跟上啊!注意:本项目完整源码加入 「冰河技术」 知识星球即可获取,文末有入场方式。

前文回顾

在《SpringCloud Alibaba实战》专栏前面的文章中,我们实现了用户微服务、商品微服务和订单微服务之间的远程调用,并且实现了服务调用的负载均衡。

冰河技术

分享各种编程语言、开发技术、分布式与微服务架构、分布式数据库、分布式事务、云原生、大数据与云计算技术和渗透技术。另外,还会分享各种面试题和面试技巧。

524篇原创内容

公众号

基于阿里开源的Sentinel实现了服务的限流与容错,并详细介绍了Sentinel的核心技术与配置规则。简单介绍了服务网关,并对SpringCloud Gateway的核心架构进行了简要说明,也在项目中整合了SpringCloud Gateway网关实现了通过网关访问后端微服务.

同时,也基于SpringCloud Gateway整合Sentinel实现了网关的限流功能,详细介绍了SpringCloud Gateway网关的核心技术。在链路追踪章节,我们开始简单介绍了分布式链路追踪技术与解决方案,随后在项目中整合Sleuth实现了链路追踪,并使用Sleuth整合ZipKin实现了分布式链路追踪的可视化 。

在消息服务章节,我们介绍了MQ的使用场景,引入MQ后的注意事项以及MQ的选型对比,在项目中整合了RocketMQ,并给大家介绍了RocketMQ的核心技术。

在服务配置章节,我们首先介绍了服务配置与Nacos作为配置中心的相关概念,并在项目中整合了Nacos配置中心。接下来,又基于Nacos实现了动态刷新与配置共享。

在分布式事务篇章,我们简单介绍了分布式事务的核心原理与SpringCloud Alibaba技术栈中的Seata框架。接下来,我们就在项目中整合Seata框架实现分布式事务。

image.png


分布式事务问题

细心的小伙伴会发现,目前,我们的项目中是不支持分布式事务的。也就是说,如果我们调用订单微服务的下单接口提交订单,如果扣减库存失败了,订单依然会写入订单数据表,这是一种典型的分布式事务问题。

image.png

(2)查看商品数据表,如下所示。

image.png

这里,我们以id为1001的商品为例,此时发现商品的库存为100。

(3)查询订单数据表,如下所示。

image.png

可以发现订单数据表为空。

(4)查询订单条目数据表,如下所示。


image.png可以看到,订单条目数据表为空。

重现分布式事务问题

(1)复制商品微服务的io.binghe.shop.order.service.impl.OrderServiceV7Impl类为io.binghe.shop.order.service.impl.OrderServiceV8Impl类,后续的操作在io.binghe.shop.order.service.impl.OrderServiceV8Impl类中进行,修改OrderServiceV8Impl类中上的@Service注解中的bean名称为orderServiceV8,并修改saveOrder()方法的代码。这里,只列出有改动的部分代码。

在saveOrder()方法中的扣减商品库存和发送RocketMQ消息之间加入一行代码int i = 1 / 0;,使其扣减库存成功后抛出异常。

修改前的部分代码如下所示。



image.png

「注意:这里只给出了修改的部分代码,修改后的完整代码,加入【冰河技术】知识星球获取。」

(2)修改订单微服务的io.binghe.shop.order.controller.OrderController类中注入的OrderService的bean名称,将其修改为orderServiceV8,如下所示。

image.png

(3)分别启动Nacos、Sentinel、ZinKin、RocketMQ,并启动用户微服务,商品微服务,订单微服务和服务网关。打开浏览器访问http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。


image.png

可以看到,下单减库存时,系统已经抛出了异常。

(4)查看各个微服务和网关输出的日志信息,分别如下所示。

  • 用户微服务输出的日志如下所示。

image.png

可以看到,用户微服务无异常信息。

  • 商品微服务输出的日志如下所示。



image.png

可以看到,订单微服务抛出了ArithmeticException异常。

  • 网关服务输出的日志如下所示。

image.png

可以看到,网关服务无异常信息。

查询数据表数据

(1)打开cmd终端,进入MySQL命令行,并进入shop商城数据库,如下所示。


image.png

可以看到,此时商品数据表中,id为1001的商品库存数量由100变成了99,减少了1个库存数量。

(3)查看订单数据表,如下所示。

image.png

可以看到,订单条目数据表为空。

综上,在下单扣减库存的业务逻辑中,在订单微服务中扣减完商品库存后,抛出了异常,导致商品库存被扣减了。但是,订单数据却没有写入到数据库中,出现了分布式事务问题。接下来,我们就在项目中整合Seata来解决分布式事务问题。

搭建并整合Seata

接下来,我们就正式在项目中整合Seata来实现分布式事务。这里,我们主要整合Seata的AT模式。

搭建Seata基础环境

(1)到https://github.com/seata/seata/releases/tag/v1.4.2链接下载Seata的安装包和源码,这里,下载的是1.4.2版本,如下所示。

image.png

点击新建命名空间,并填写Seata相关的信息,如下所示。

image.png

可以看到,这里我填写的信息如下所示。

  • 命名空间ID:seata_namespace_001,如果不填的话Nacos会自动生成命名空间的ID。
  • 命名空间名:seata。
  • 描述:seata的命名空间。

「这里,需要记录下命名空间的ID:seata_namespace_001,在后面的配置中会使用到。」

点击确定后如下所示。

image.png

可以看到,这里为Seata在Nacos中创建了命名空间。

(3)解压Seata安装文件,进入解压后的seata/seata-server-1.4.2/conf目录,修改registry.conf注册文件,修改后的部分文件内容如下所示。

image.png其中,namespace的值就是在Nacos中配置的Seata的命名空间ID:seata_namespace_001。

「注意:这里只列出了修改的部分内容,完整的registry.conf文件可以到项目的doc/nacos/config/chapter25目录下获取。」

(4)修改Seata安装文件的seata/seata-server-1.4.2/conf目录下的file.conf文件,修改后的部分配置如下所示。

image.png

「注意:这里只列出了修改的部分内容,完整的file.conf文件可以到项目的doc/nacos/config/chapter25目录下获取。」

(5)在下载的Seata源码的seata-1.4.2/script/config-center目录下找到config.txt文件,如下所示。


image.png

接下来,修改Seata安装包解压的根目录下的config.txt文件,这里还是只列出修改的部分,如下所示。

image.png

注意:在config.txt中,部分配置的等号“=”后面为空,需要在等号“=“后面添加空字符串""。同样的,小伙伴们可以到项目的doc/nacos/config/chapter25目录下获取完整的config.txt文件。」

(6)在下载的Seata源码的seata-1.4.2/script/config-center/nacos目录下找到nacos-config.sh文件,如下所示。

image.png

(7).sh文件是Linux操作系统上的脚本文件,如果想在Windows操作系统上运行.sh文件,可以在Windows操作系统上安装Git后在运行.sh文件。

接下来,在Git的Bash命令行进入Seata安装文件中nacos-config.sh文件所在的目录,执行如下命令。

image.png

其中,命令中的每个参数含义如下所示。

  • -h:Nacos所在的IP地址。
  • -p:Nacos的端口号。
  • -g:分组。
  • -t:命名空间的ID,这里我们填写在Nacos中创建的命名空间的ID:seata_namespace_001。如果不填,默认是public命名空间。
  • -u:Nacos的用户名。
  • -w:Nacos的密码。

执行命令后的结果信息如下所示。

image.png

可以看到,整个配置执行成功。

(8)打开Nacos的配置管理-配置列表界面,切换到seata命名空间,可以看到有关Seata的配置都注册到Nacos中了,如下所示。

image.png

(9)在MySQL数据库中创建seata数据库,如下所示。

image.png

接下来,在seata数据库中执行Seata源码包seata-1.4.2/script/server/db目录下的mysql.sql脚本文件,mysql.sql脚本的内容如下所示。

image.png

image.png

这里,也将mysql.sql文件放在了项目的doc/nacos/config/chapter25目录下。

(10)启动Seata服务,进入在命令行进入Seata安装文件的seata/seata-server-1.4.2/bin目录,执行如下命令。

image.png

说明Seata已经启动成功。

至此,Seata的基础环境搭建完毕。

项目整合Seata

在我们开发的微服务程序中,订单微服务下单成功后会调用库存微服务扣减商品的库存信息,而用户微服务只提供了查询用户信息的接口。这里,我们在商品微服务和订单微服务中整合Seata。

导入unlog表

我们使用的是Seata的AT模式,需要我们在涉及到使用Seata解决分布式事务问题的每个业务库中创建一个Seata的undo_log数据表,Seata中本身提供了创建数据表的SQL文件,这些SQL文件位于Seata源码包下的seata-1.4.2/script/client/at/db目录中,如下所示。

image.png

注意,这里要在shop数据库中执行mysql.sql脚本,同样的,我会将这里的mysql.sql文件放到项目的doc/nacos/config/chapter25目录下,并重命名为mysql_client.sql。

image.png

image.png

image.png

其中,配置的Nacos的namespace与group与registry.conf文件中的一致。

订单微服务整合Seata

(1)在订单微服务shop-product的pom.xml文件中引入Seata依赖,如下所示

image.png

(2)修改订单微服务shop-order的bootstrap.yml,修改后的文件如下所示。

image.png

image.png

(3)修改订单微服务的io.binghe.shop.order.service.impl.OrderServiceV8Impl类的saveOrder()方法,在saveOrder()方法上添加Seata的@GlobalTransactional注解,如下所示。

image.png

至此,搭建并整合Seata完毕,就是这么简单。

验证Seata事务

重置数据库数据

这里,首先将商品数据表t_product中id为1001的数据的库存信息重置为100,如下所示。

update t_product set t_pro_stock = 100 where id = 1001;

查询数据表数据

(1)打开cmd终端,进入MySQL命令行,并进入shop商城数据库,如下所示。

C:\Users\binghe>mysql -uroot -p
Enter password: ****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.35 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use shop;
Database changed

(2)查看商品数据表,如下所示。

mysql> select * from t_product;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
| 1002 | 小米       |     1999.00 |         100 |
| 1003 | iphone     |     4999.00 |         100 |
+------+------------+-------------+-------------+
3 rows in set (0.00 sec)

这里,我们以id为1001的商品为例,此时发现商品的库存为100。

(3)查询订单数据表,如下所示。

mysql> select * from t_order;
Empty set (0.00 sec)

可以发现订单数据表为空。

(4)查询订单条目数据表,如下所示。

mysql> select * from t_order_item;
Empty set (0.00 sec)

可以看到,订单条目数据表为空。

验证Seata事务

(1)分别启动Nacos、Sentinel、ZinKin、RocketMQ,Seata,并启动用户微服务,商品微服务,订单微服务和服务网关。打开浏览器访问http://localhost:10001/server-order/order/submit_order?userId=1001&productId=1001&count=1,如下所示。

image.png说明用户微服务无异常信息。

  • 商品微服务输出的日志如下所示。
获取到的商品信息为:{"id":1001,"proName":"华为","proPrice":2399.00,"proStock":100}
更新商品库存传递的参数为: 商品id:1001, 购买数量:1

说明商品微服务无异常信息。

值得注意的是,整合Seata后,商品微服务同时输出了如下日志。

rm handle branch rollback process:xid=192.168.0.111:8091:6638572304823066625,branchId=6638572304823066634,branchType=AT,resourceId=jdbc:mysql://localhost:3306/shop,applicationData=null
Branch Rollbacking: 192.168.0.111:8091:6638572304823066625 6638572304823066634 jdbc:mysql://localhost:3306/shop
xid 192.168.0.111:8091:6638572304823066625 branch 6638572304823066634, undo_log deleted with GlobalFinished
Branch Rollbacked result: PhaseTwo_Rollbacked

看上去应该是有事务回滚了。

  • 订单微服务输出的日志如下所示。
提交订单时传递的参数:{"count":1,"empty":false,"productId":1001,"userId":1001}
库存扣减成功
服务器抛出了异常:{}
java.lang.ArithmeticException: / by zero

说明订单微服务抛出了ArithmeticException异常。

同时,商品微服务会输出如下日志。

Branch Rollbacked result: PhaseTwo_Rollbacked
[192.168.0.111:8091:6638572304823066625] rollback status: Rollbacked

看上去应该是有事务回滚了。

  • 网关服务输出的日志如下所示。
执行前置过滤器逻辑
执行后置过滤器逻辑
访问接口主机: localhost
访问接口端口: 10001
访问接口URL: /server-order/order/submit_order
访问接口URL参数: userId=1001&productId=1001&count=1
访问接口时长: 1632ms

可以看到,网关服务无异常信息。

通过微服务打印出的日志信息,可以看到,有事务回滚了。

查询数据表数据

(1)打开cmd终端,进入MySQL命令行,并进入shop商城数据库,如下所示。

C:\Users\binghe>mysql -uroot -p
Enter password: ****
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.7.35 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> use shop;
Database changed

(2)查看商品数据表,如下所示。

mysql> select * from t_product;
+------+------------+-------------+-------------+
| id   | t_pro_name | t_pro_price | t_pro_stock |
+------+------------+-------------+-------------+
| 1001 | 华为       |     2399.00 |         100 |
| 1002 | 小米       |     1999.00 |         100 |
| 1003 | iphone     |     4999.00 |         100 |
+------+------------+-------------+-------------+
3 rows in set (0.00 sec)

可以看到,此时商品数据表中,id为1001的商品库存数量仍然为100。

(3)查看订单数据表,如下所示。

mysql> select * from t_order;
Empty set (0.00 sec)

可以看到,订单数据表为空。

(4)查看订单条目数据表,如下所示。

mysql> select * from t_order_item;
Empty set (0.00 sec)

可以看到,订单条目数据表为空。

至此,我们成功在项目中整合了Seata解决了分布式事务的问题。




相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
3月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
658 3
|
4月前
|
人工智能 Kubernetes 数据可视化
Kubernetes下的分布式采集系统设计与实战:趋势监测失效引发的架构进化
本文回顾了一次关键词监测任务在容器集群中失效的全过程,分析了中转IP复用、调度节奏和异常处理等隐性风险,并提出通过解耦架构、动态IP分发和行为模拟优化采集策略,最终实现稳定高效的数据抓取与分析。
Kubernetes下的分布式采集系统设计与实战:趋势监测失效引发的架构进化
|
1月前
|
负载均衡 Java API
《深入理解Spring》Spring Cloud 构建分布式系统的微服务全家桶
Spring Cloud为微服务架构提供一站式解决方案,涵盖服务注册、配置管理、负载均衡、熔断限流等核心功能,助力开发者构建高可用、易扩展的分布式系统,并持续向云原生演进。
|
6月前
|
消息中间件 运维 Kafka
直播预告|Kafka+Flink 双引擎实战:手把手带你搭建分布式实时分析平台!
直播预告|Kafka+Flink 双引擎实战:手把手带你搭建分布式实时分析平台!
222 12
|
4月前
|
数据采集 缓存 NoSQL
分布式新闻数据采集系统的同步效率优化实战
本文介绍了一个针对高频新闻站点的分布式爬虫系统优化方案。通过引入异步任务机制、本地缓存池、Redis pipeline 批量写入及身份池策略,系统采集效率提升近两倍,数据同步延迟显著降低,实现了分钟级热点追踪能力,为实时舆情监控与分析提供了高效、稳定的数据支持。
156 1
分布式新闻数据采集系统的同步效率优化实战
|
5月前
|
缓存 NoSQL 算法
高并发秒杀系统实战(Redis+Lua分布式锁防超卖与库存扣减优化)
秒杀系统面临瞬时高并发、资源竞争和数据一致性挑战。传统方案如数据库锁或应用层锁存在性能瓶颈或分布式问题,而基于Redis的分布式锁与Lua脚本原子操作成为高效解决方案。通过Redis的`SETNX`实现分布式锁,结合Lua脚本完成库存扣减,确保操作原子性并大幅提升性能(QPS从120提升至8,200)。此外,分段库存策略、多级限流及服务降级机制进一步优化系统稳定性。最佳实践包括分层防控、黄金扣减法则与容灾设计,强调根据业务特性灵活组合技术手段以应对高并发场景。
1524 7
|
6月前
|
监控 Java 调度
SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战
本文对比分析了SpringBoot中的`@Scheduled`与Quartz定时任务框架。`@Scheduled`轻量易用,适合单机简单场景,但存在多实例重复执行、无持久化等缺陷;Quartz功能强大,支持分布式调度、任务持久化、动态调整和失败重试,适用于复杂企业级需求。文章通过特性对比、代码示例及常见问题解答,帮助开发者理解两者差异,合理选择方案。记住口诀:单机简单用注解,多节点上Quartz;若是任务要可靠,持久化配置不能少。
641 4
|
3月前
|
存储 负载均衡 NoSQL
【赵渝强老师】Redis Cluster分布式集群
Redis Cluster是Redis的分布式存储解决方案,通过哈希槽(slot)实现数据分片,支持水平扩展,具备高可用性和负载均衡能力,适用于大规模数据场景。
315 2
|
3月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
249 6