参考文章:

若依+分布式事务seata_若依 seata-CSDN博客

分布式事务 | RuoYi

spring-cloud-alibaba与Spring Cloud、spingboot、seata版本对照关系

seata官网

记一次springboot项目中多数据源使用以及因事务导致的异常问题处理


1 分布式事务概念

1.1 什么是分布式事务概念

指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。从本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

1.2 为什么要使用分布式事务

在微服务独立数据源的思想,每一个微服务都有一个或者多个数据源,虽然单机单库事务已经非常成熟,但是由于网路延迟和不可靠的客观因素,分布式事务到现在也还没有成熟的方案,对于中大型网站,特别是涉及到交易的网站,一旦将服务拆分微服务,分布式事务一定是绕不开的一个组件,通常解决分布式事务问题。

1.3 seata 分布式事务

Seata是阿里开源的一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

Seata目标打造一站式的分布事务的解决方案,最终会提供四种事务模式:

AT 模式:参见(《Seata AT 模式》 (opens new window))文档
TCC 模式:参见(《Seata TCC 模式》 (opens new window))文档
Saga 模式:参见(《SEATA Saga 模式》 (opens new window))文档
XA 模式:正在开发中... 目前使用的流行度情况是:AT > TCC > Saga。因此,我们在学习Seata的时候,可以花更多精力在AT模式上,最好搞懂背后的实现原理,毕竟分布式事务涉及到数据的正确性,出问题需要快速排查定位并解决。


2 部署seata服务端

2.1 下载seata服务端

可以从https://github.com/seata/seata/releases下载seata-server-$version.zip包。

我下载的是1.7版本, 目前已上传至我的百度网盘,方便下载

注:若依版本:3.6.3,nacos版本:2.3.1,seata版本:1.7.0,数据库:8.0

解压后如图

2.2 修改seata配置文件

需要注意的是,1.5.0之前的版本配置文件是有多个的,都位于conf文件夹下如file.conf,registry,conf等。在1.5.0版本之后都整合到一个配置文件里了,即application.yml。以下配置项请按照自己版本查找修改。以seata-1.7.0为例,打开conf/application.yml进行修改,重点修改nacos部分配置。

修改成功后,意味着seata将从nacos获取配置信息,同时注册自身服务到nacos中心。

2.3 导入sql文件

创建ry_seata数据库,然后导入seata服务端的sql脚本 

2.4 添加seata配置文件,启动seata服务端

注意几点:

1、nacos的注册地址和配置中的server-addr对应

2、nacos的group分组和配置中的group对应

3、nacos的seata配置文件名和配置名data-id对应

配置文件中添加需要分布式事务的服务

具体配置项是从seata-server-1.7.0/seata/script/config-center/config.txt粘贴修改而来,这里只使用对我们有用的配置,主要是数据库配置信息。

具体配置如下:

service:
  vgroupMapping:
    ruoyi-tx-group: default
    
store:
  mode: db
  lock:
    mode: db
  session:
    mode: db
  db:
    datasource: druid
    dbType: mysql
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ry_seata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    user: root
    password: root
    minConn: 5
    maxConn: 30
    queryLimit: 100
    maxWait: 5000
    globalTable: global_table
    branchTable: branch_table
    distributedLockTable: distributed_lock
    lockTable: lock_table

然后到seata的bin目录下启动seata服务就好了,seata默认注册到nacos中的端口是8091,启动后到nacos确认下(微服务注册到seata时需要指定seata的ip和端口)

windows环境,直接点击\seata\bin\seata-server.bat

linux环境,sh seata-server.sh -h 127.0.0.1 -p 8091

启动成功如下:

启动成功后,seata控制台可以访问

nacos可以看到注册的服务 

至此,seata服务端部署完毕


3 客户端服务引入seata

3.1服务添加依赖

在若依的seata服务添加seata依赖

<dependencies>
        <!-- SpringBoot 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>
            </exclusions>
        </dependency>

        <!-- 我服务端是1.7.0这里就引入1.7.0 -->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
            <version>1.7.0</version>
        </dependency>
    </dependencies>

然后在需要使用seata的服务下添加依赖即可 

3.2 修改各个需要分布式事务的服务配置文件

比如我这个服务用到了分布式事务

然后在配置文件中,开启seata代理

添加配置对应seata服务,修改虚拟组和分组的映射,修改seata服务映射 

3.3 为每个服务添加 undo_log 表

SEATA AT 模式需要 UNDO_LOG 表。你可以通过 github 获取到指定版本的undo log SQL 脚本.

我已整理至项目下

-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
    `branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
    `context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
    `rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
    `log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
    `log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);

 3.4 方法添加事务注解使用

服务调用方添加注解即可


4 测试流程如下

4.1 多数据源测试

用户当前余额50,商品单价10元,库存20个,购买6个,则需要60元,超过余额则回滚

下面的商品和用户分别是两个服务(使用多数据源),所以需要用到分布式事务回滚

RootContext.getXID()可以获取全局的一个XID。

经测试可获取xid,并且正常回滚

 我们在seata控制台也可以看到,刚才的回滚信息

使用多数据源注意的点:如a方法上有个事务,b方法有个事务,a调用b,此时的b方法默认执行a方法使用的数据源。

所以需要在b方法上修改事务的传播方式,使用@Transactional(propagation = Propagation.REQUIRES_NEW)

 4.2 远程调用测试

我们在ruoyi-file模块配置seata

在seata服务端,加入该模块

 然后在刚才下单接口中,加入远程调用

可以看到tx模块报错了,余额不足

在file模块,我们也能获取xid,并且正确回滚

seata控制台也能看到回滚信息

 A模块远程调用B模块,B模块事务只用加@Transactional,而不需要加propagation = Propagation.REQUIRES_NEW


5 常见异常

5.1 feign调用服务,被调用服务seata事务不开启或者xid为空

方案1 在fein远程调用的时候将当前的xid传入header

由于使用的是seata-spring-boot-starter配置,这里的seata自动配置不会对feign进行处理,所以fein调用不会传xid,导致全局事务未生效。

// 该接口需要添加到自己应用的启动config中
@Configuration
@Slf4j
public class FeignSeataInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        String xid = RootContext.getXID();
        if (StringUtil.isNotEmpty(xid)) {
            log.debug("全局事务xid:{}",xid);
            requestTemplate.header(RootContext.KEY_XID, xid);
        }
    }
}    

方案二 使用spring-cloud-starter-alibaba-seata自动配置(推荐)

我们使用的就是方案二,直接引入该依赖,这里会对fein进行seata相关的配置。

​

注意:

1,异常需要层层往上抛,如果你在子服务将异常处理的话(比如全局异常处理GlobalExceptionHandler),seata会认为你已经手动处理了异常。

2,出现事务失效的情况下,优先检查 RootContext.getXID() ,xid是否传递且一致。

3,主服务加上@GlobalTransactional注解即可,被调用服务不用加@GlobalTransactional

4,@GlobalTransactional(rollbackFor = Exception.class)最好加上rollbackFor = Exception.class,表示遇到Exception都回滚,不然遇到有些异常(如自定义异常)则不会回滚

    Logo

    北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

    更多推荐