云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

从分布式事务解决到Seata使用,一梭子给你整明白了

jxf315 2025-05-03 14:49:57 教程文章 2 ℃
  1. 事务,TC 就会创建一个全局事务并返回一个唯一的 XID
  2. 服务A中的 RMTC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中
  3. 服务A开始执行分支事务
  4. 服务A开始远程调用B服务,此时 XID 会根据调用链传播
  5. 服务B中的 RM 也向 TC 注册分支事务,然后将这个分支事务纳入 XID 对应的全局事务管辖中
  6. 服务B开始执行分支事务
  7. 全局事务调用处理结束后,TM 会根据有误异常情况,向 TC 发起全局事务的提交或回滚
  8. TC 协调其管辖之下的所有分支事务,决定是提交还是回滚

Seata使用

我们从上面了解到了 Seata 的组成和执行流程,我们接下来就来实际的使用下 Seata

示例演示

我们简单创建了一个微服务项目,其中有订单服务和库存服务。

我们这里采用了 nacos 作为注册中心,分别启动两个服务,我们在nacos控制台可以看到两个已经注册的服务:

号外:如果对nacos还不熟悉的小伙伴可以跳转查看 nacos讲解:微服务新秀之Nacos

我们接着创建了一个数据库,其中有两张表:c_order 和 c_product,其中商品表中有一条数据,而订单表中还未有数据,接下来我们将要对其进行操作!

我们现在模拟一个下单的过程:

  1. 请求进来,通过商品 pid 往数据库中查商品的信息
  2. 创建一条该商品的订单
  3. 对应扣减该商品的库存量
  4. 流程结束

我们接下来就进入代码演示一下:

注意:这里 ProductService 并非是库存服务里面的类,而是利用 Feign 远程调用库存服务的接口

代码三步走,正常请况下肯定是没有问题的:

订单生成,库存也对应减少,感觉自己代码可以上线进入正轨的时候,我们来模拟一下库存中的异常,库存商品数量归为 100,订单表清空:

我们继续发送下单请求,可以看到库存服务已经抛出了异常

正常来说这个时候,库存表数量不应该减少,订单表不应该插入订单数据,但是事实真的是这样的吗?我们看数据:

库存数量没减,但是订单却增加了。好了,到这里,你就已经见识到了分布式事务的灾难性危害。接下来主角登场!

Seata 安装

我们首先需要点击下载地址进行下载 Seata

由于我们是使用 nacos 作为服务中心和配置中心,因此我们下载解压后需要做一些修改操作

  • 进入 conf 目录编辑 registry.conf 和 file.conf 两个文件,编辑后内容如下:
  • 由于新版 Seata 中没有 nacos-conf.sh 和 config.txt 两个文件,因此我们需要独立下载:

nacos-config.sh 下载地址

config.txt 下载地址

我们需要将 config.txt 文件放到 seata 目录下,而非 conf 目录下,并且需要修改 config.txt 内容

config.txt就是seata各种详细的配置,执行 nacos-config.sh 即可将这些配置导入到nacos,这样就不需要将file.conf和registry.conf放到我们的项目中了,需要什么配置就直接从nacos中读取。

  • 执行导入

conf 目录下打开 git bash 窗口,执行以下命令:

sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t namespace-id(需要替换) -u nacos -w nacos
复制代码

操作结束后,我们便可以在 nacos 控制台中看到配置列表,日后配置有需要修改便可以直接从这边修改,而不用修改目录文件:

  • 数据库配置

1.4.1 最新版中依然没有 sql 文件,所以我们还是需要另外下载:sql 下载地址

seata 数据中执行这个文件,生成三张表:

在我们的业务数据库中执行 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;
复制代码
  • 添加 log 文件

如果我们没有log输出文件,启动 seata 可能会报错,因此我们需要在 seata 目录下创建 logs 文件夹,在 logs 文件下创建 seata_gc.log 文件

  • 启动

做好了以上准备,我们便可以启动 seata 了,直接在 bin 目录下 cmd 执行 bat 脚本即可,启动结束便可在 nacos 中看到 seata 服务:

Seata 集成

在 Seata 安装的步骤中我们便完成了 Seata 服务端 的启动安装,接下来就是在项目中集成 Seata 客户端

  • 第一步:我们需要在 pom.xml 文件中添加两个依赖:seata 依赖 和 nacos 配置依赖
<!--nacos-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--seata-->
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <exclusions>
                <!-- 排除依赖 指定版本和服务器端一致 -->
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-all</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.4.1</version>
        </dependency>
复制代码

注意: 这里需要排除
spring-cloud-starter-alibaba-seata 自带的
seata 依赖,然后引入我们自己需要的 seata 版本,如果版本不一致启动时可能会造成 no available server to connect 错误!

  • 第二步:我们需要把 restry.conf 文件复制到项目的 resource 目录下
  • 第三步:需要自己配置seata代理数据源
@Configuration
public class DataSourceProxyConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Primary
    @Bean
    public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }
}
复制代码

配置完数据源我们得在启动类的 SpringBootApplication 上排除Druid数据源依赖,否则可能会出现循环依赖的错误:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
复制代码
  • 第四步:在 nacos 的配置文件控制台中加入我们服务的事务组项:
service.vgroupMapping + 服务名称 = default
group为: SEATA_GROUP
复制代码
  • 第五步:项目中配置修改
  • 第六步:开启全局事务

这步就是最终一步了,在我们需要开启事务的方法上添加 @GlobalTransactional 注解,类似于我们单体事务添加的@Transactional

Seata 测试

我们现在回到项目中,在上面的示例演示中,我们已经知道了如果库存服务发生异常,会出现的情况是,库存没有减少,而订单依然会生成。那我们如果增加了 Seata 来管理全局事务,情况是否会有所改变?我们测试如下:

库存服务已经了异常:

看下数据库数据:

看样子我们全局事务已经生效了,事务也已经完美的控制住了!

而我们创建的 undo_log 这张表在管理事务中也启动了重要的作用:

看完了以上操作,我们趁热打铁来梳理一下其中的执行流程,让你印象更加深刻些~

相信看完这张图,你对 Seata 执行事务的流程也更加熟悉了吧!

这还没结束,我们接着来看看其中的一些要点:

  1. 每个 RM 都需要使用 DataSourceProxy 连接数据库,这样是为了使用 ConnectionProxy,使用数据源和数据连接代理的目的就是在第一阶段将 undo_log 和业务数据放在一个本地事务提交,这样就保存了只要有业务操作就一定有 undo_log 产生!
  2. 在第一阶段的 undo_log 中存放了数据修改前和修改后的值,为事务回滚做好准备,所以第一阶段就已经将分支事务提交,也就释放了锁资源!
  3. TM 开启全局事务后,便会将 XID 放入全局事务的上下文中,我们通过 feign 调用也会将 XID 传入下游服务中,每个分支事务都会将自己的 Branch IDXID 相关联!
  4. 第二阶段如果全局事务是正常提交,那么TC 会通知各分支参与者提交分支事务,各参与者只需要删除对应的 undo_log 即可,并且可以异步执行!
  5. 第二阶段如果全局事务需要回滚,那么 TC 会通知各分支事务参与者回滚分支事务,通过 XIDBranch ID 找到相应的 undo_log 日志,通过回滚日志生成反向 SQL 并执行,完成事务提交之前的状态,如果回滚失败便会重试回滚操作!
最近发表
标签列表