SpringCloud系列 - Seata 分布式事务(六)

目录

一、介绍

二、什么情况下要使用分布式事务?

[三、Seata 核心架构与组件](#三、Seata 核心架构与组件)

[2.1 TC](#2.1 TC)

[2.2 TM](#2.2 TM)

[2.3 RM](#2.3 RM)

三、案例引入

[3.1 案例说明](#3.1 案例说明)

[3.2 环境搭建](#3.2 环境搭建)

[3.3 测试接口](#3.3 测试接口)

四、整合Seata

[4.1 下载并启动Seata服务端](#4.1 下载并启动Seata服务端)

[4.2 各微服务引入seata客户端](#4.2 各微服务引入seata客户端)

(1)引入依赖

(2)配置文件

(3)添加@GlobalTransactional

[五、Seata 事务模式](#五、Seata 事务模式)

[5.1 AT 模式](#5.1 AT 模式)

[5.2 TCC 模式](#5.2 TCC 模式)

[5.3 XA 模式](#5.3 XA 模式)

[5.4 Saga 模式](#5.4 Saga 模式)


一、介绍

Seata 是阿里巴巴开源的分布式事务 解决方案,旨在为微服务架构提供高性能且简单易用的分布式事务服务。它通过全局事务协调机制,解决了微服务架构下跨服务、跨数据库的数据一致性问题

二、什么情况下要使用分布式事务?

java 复制代码
    public void createOrder(Long productId, int quantity) {
        // 1. 扣减库存
        stockRepository.deductStock(productId, quantity);

        // 2. 创建订单
        Order order = new Order(productId, quantity);
        orderRepository.save(order);

        // 3. 业务逻辑校验(如库存不足、订单校验等)
        if (someBusinessValidationFails()) {
            throw new RuntimeException("业务校验失败");
        }
    }

如上所示,某业务方法内需要扣减库存、然后创建订单,如果出现业务逻辑异常,如何保证这两个操作能同时进行事务回滚?

情况一:该方法内扣库存和创建订单都在同一个数据库下,且是单体应用单实例部署。

很简单。本地事务(@Transactional)​ ​ 就可以来保证 ​​扣减库存​ ​ 和 ​​创建订单​​ 两个操作的原子性,确保在业务逻辑异常时能同时回滚。

需要注意并发竞争问题。

举个例子:扣减库存的前提是有库存才能扣减,如果扣减库存的逻辑是先查询出结果判断剩余数量再写sql扣减库存,那么就要合理的加锁,防止并发操作导致库存查询异常。

情况二:该方法内扣库存和创建订单都在同一个数据库下,且是单体应用多实例部署。

同样的,还是可以通过本地事务(@Transactional)​ ​ 来保证 ​​扣减库存​ ​ 和 ​​创建订单​​ 两个操作的原子性,确保在业务逻辑异常时能同时回滚。

但是要注意的是并发竞争问题

多实例下并发竞争问题更明显,像前面单体单实例部署的解决方案用普通锁,在多实例下是行不通的。普通锁不能跨进程生效,因此还必须引入分布式锁解决并发竞争问题,比如Redis的红锁。

情况三:该方法内扣库存和创建订单在不同的数据库下,且是单体应用单实例部署。

核心挑战

  • 跨数据库事务隔离

不同数据库的事务彼此独立,无法通过本地事务( @Transactional)直接协调回滚。

  • ​动态数据源切换的局限性

​​​​​​​若使用动态数据源,事务开启后切换数据源无效,因事务管理器已绑定初始数据源的连接。

解决办法 :因此需要使用分布式事务来解决数据一致性问题,保证不同数据库同时回滚或提交。

情况四:该方法内扣库存和创建订单在不同的数据库下,且是单体应用多实例部署。

一样的,只要跨库了那么事务就是彼此独立的。本地事务肯定不能直接回滚。所以还是要用分布式事务。

关于并发竞争问题,这个无论是哪种情况下都要考虑。多实例更要考虑,必要时引入分布式锁。

情况五:该方法内扣库存和创建订单在同一个数据库下,但是扣减库存和创建订单来自不同的微服务,相当于该方法内远程调用

虽然共享同一数据库,但不同微服务的事务管理器独立,无法通过本地事务(@Transactional)直接协调回滚。

因此必须使用分布式事务。

同样的并发竞争问题依然必须考虑,使用分布式锁解决。

三、Seata 核心架构与组件

Seata 的架构设计围绕三个核心组件展开,这三个组件协同工作,共同完成分布式事务的管理:

2.1 TC

Transaction Coordinator - 事务协调器

​角色定位​​:Seata 服务端的核心组件,相当于分布式事务的"大脑"

​核心职责​​:

  • 维护全局事务和分支事务的状态
  • 协调并驱动全局事务的提交或回滚
  • 管理全局事务ID(XID)的生成和传播
  • 处理分支事务的注册和状态报告

​部署方式​ ​:通常独立部署为Seata Server,支持集群部署保证高可用

2.2 TM

Transaction Manager - 事务管理器

​角色定位​ ​:集成在业务应用中,是全局事务的发起者和终结者

​核心职责​​:

  • 定义全局事务的边界(通过@GlobalTransactional注解)
  • 发起全局事务的开始、提交或回滚请求
  • 与TC保持通信,报告全局事务的最终状态

​实现方式​​:通过Java注解与业务代码集成,对业务侵入性低

2.3 RM

Resource Manager - 资源管理器

​角色定位​ ​:集成在每个参与分布式事务的微服务中,管理本地资源

​核心职责​​:

  • 管理分支事务的执行和状态
  • 向TC注册分支事务并报告执行状态
  • 根据TC指令执行本地事务的提交或回滚
  • 在AT模式下负责生成和管理undo_log回滚日志

​实现机制​​:通过代理数据源的方式拦截SQL操作,实现自动补偿

​三组件协作比喻​​:将分布式事务比作糖葫芦串,TC是串糖葫芦的竹签,TM是第一个糖葫芦(触发分布式事务的服务),RM则是各个糖葫芦(参与事务的微服务)。

三、案例引入

3.1 案例说明

现在有一个用户下单的业务场景。

首先,通过采购服务的购买接口,远程调用库存服务扣减库存,然后远程调用订单服务创建订单。创建订单服务又需要远程调用账户服务扣减个人账户余额。

这几个服务和数据库都是独立的,一旦任意环节出了问题,肯定是需要回滚操作的。

示意图如下:

3.2 环境搭建

导入事先提供好的关于seata-demo文件夹中的几个模块,到我们的项目中。

可以看到服务进来了,但是idea没有识别,原因是我们还没有在父工程的pom文件定义这几个模块。不用担心,处理完成后,刷新maven,然后就能识别出来。

配置文件中我们可以看到,每个服务对应的数据库是不一样的,是满足案例要求的。

然后就创建数据库和数据表,直接复制这段生成即可。

sql 复制代码
CREATE DATABASE
IF
	NOT EXISTS `storage_db`;
USE `storage_db`;
DROP TABLE
IF
	EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`commodity_code` VARCHAR ( 255 ) DEFAULT NULL,
	`count` INT ( 11 ) DEFAULT 0,
	PRIMARY KEY ( `id` ),
	UNIQUE KEY ( `commodity_code` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;
INSERT INTO storage_tbl ( commodity_code, count )
VALUES
	( 'P0001', 100 );
INSERT INTO storage_tbl ( commodity_code, count )
VALUES
	( 'B1234', 10 );-- 注意此处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;
CREATE DATABASE
IF
	NOT EXISTS `order_db`;
USE `order_db`;
DROP TABLE
IF
	EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`user_id` VARCHAR ( 255 ) DEFAULT NULL,
	`commodity_code` VARCHAR ( 255 ) DEFAULT NULL,
	`count` INT ( 11 ) DEFAULT 0,
	`money` INT ( 11 ) DEFAULT 0,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;-- 注意此处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;
CREATE DATABASE
IF
	NOT EXISTS `account_db`;
USE `account_db`;
DROP TABLE
IF
	EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`user_id` VARCHAR ( 255 ) DEFAULT NULL,
	`money` INT ( 11 ) DEFAULT 0,
	PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;
INSERT INTO account_tbl ( user_id, money )
VALUES
	( '1', 10000 );-- 注意此处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;

然后在idea中连接上这几个数据库方便查看。

3.3 测试接口

测试 购买 接口,结果程序报错了,订单服务中create方法有异常。

我们观察数据库,发现余额和库存都扣了,但是订单没有创建。这就存在了严重的一致性问题。

如果有一个地方失败,应该是全部回滚的。可是余额和库存都扣减了。

所以接下来进入分布式事务seata来解决这个问题~

四、整合Seata

4.1 下载并启动Seata服务端

官网下载地址:Seata-Server版本历史 | Apache Seata

推荐版本:apache-seata-2.1.0-incubating-bin.tar.gz

解压后,进入bin目录,cmd 执行 seata-server.bat

可以看到默认的服务端是在8091端口。

还额外提供了一个客户端web界面,在7091端口。账号密码均为seata。

4.2 各微服务引入seata客户端

(1)引入依赖

在servie父工程的pom.xml中引入

XML 复制代码
        <!-- 分布式事务seata -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

(2)配置文件

file.conf

bash 复制代码
 service {
   #transaction service group mapping
   #事务分组,默认名称就叫default,不改可以不配
   vgroupMapping.default_tx_group = "default"
   #only support when registry.type=file, please don't set multiple addresses
   #default事务分组的服务器列表地址,注意不是7091,7091是客户端页面端口
   default.grouplist = "127.0.0.1:8091"
   #degrade, current not support
   enableDegrade = false
   #disable seata
   disableGlobalTransaction = false
 }

(3)添加@GlobalTransactional

只需要在全局事务入口 (购买业务上)标记 @GlobalTransactional注解,其他的都不用动。

复制代码

启动项目查看效果

发现请求报错了,但是提交都回滚了,说明分布式事务启动成功!

五、Seata 事务模式

5.1 AT 模式

AT 模式是 Seata 创新的一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。

5.2 TCC 模式

TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。

5.3 XA 模式

XA 模式是从 1.2 版本支持的事务模式。XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式。

5.4 Saga 模式

Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。

相关推荐
{⌐■_■}5 小时前
【Kafka】登录日志处理的三次阶梯式优化实践:从同步写入到Kafka多分区批处理
数据库·分布式·mysql·kafka·go
qq_529835355 小时前
RabbitMQ的消息可靠传输
分布式·rabbitmq
CodeWithMe6 小时前
【Note】《Kafka: The Definitive Guide》 第九章:Kafka 管理与运维实战
运维·分布式·kafka
sql2008help6 小时前
1-Kafka介绍及常见应用场景
分布式·kafka
别来无恙1498 小时前
整合Spring、Spring MVC与MyBatis:构建高效Java Web应用
java·spring·mvc
工藤学编程11 小时前
分库分表之实战-sharding-JDBC绑定表配置实战
数据库·分布式·后端·sql·mysql
写不出来就跑路11 小时前
SpringBoot静态资源与缓存配置全解析
java·开发语言·spring boot·spring·springboot
墨着染霜华11 小时前
Caffeine的tokenCache与Spring的CaffeineCacheManager缓存区别
java·spring·缓存
gtestcandle12 小时前
rabbitmq 的多用户、多vhost使用
分布式·rabbitmq