轻识Logo
目录

    SpringCloud下基于Seata AT的分布式事务实践

    Seata是Spring Cloud Alibaba中一款开源的分布式事务解决方案,本文具体就Seata的AT模式进行介绍、实践

    6c1896dd7eb0eaa959c2a6f3c21baeae.webp

    abstract.png基本原理

    在Seata的设计架构中有三个角色,具体如下

    • TC(Transaction Coordinator): 事务协调者。维护全局和分支事务的状态,驱动全局事务提交或回滚
    • TM(Transaction Manager): 事务管理器。定义全局事务的范围,用于开始、提交、回滚全局事务
    • RM(Resource Manager): 资源管理器。管理分支事务处理的资源,与TC通讯以注册分支事务和报告分支事务的状态,并驱动分支事务进行提交或回滚

    TC是Seata的服务端需独立部署,而TM、RM则是作为Seata的客户端与各微服务进行集成。三者之间的流程关系如下图所示。具体地,Seata的分布式事务模型是基于 2PC(两阶段提交,Tow-Phase Commit) 协议,基本执行流程如下

    1. TM向TC申请开启一个分布式事务,事务创建成功后会生成一个全局唯一的事务ID,即所谓的XID
    2. RM向TC注册分支事务,汇报资源准备状态
    3. TM通知TC 提交/回滚 分布式事务,事务一阶段结束
    4. TC汇总各分支事务信息,决定分布式事务是提交还是回滚
    5. TC通知所有RM 提交/回滚 资源,分布式事务的二阶段结束

    933900918e8a5f52b4eee5886ef591ad.webp

    figure 1.png

    具体地,Seata支持AT、TCC、Saga、XA四种模式。这里就AT模式进行展开说明,其是一种无侵入的分布式事务解决方案,使得开发者只需关注自己的业务SQL即可。Seata会自动进行二阶段的提交/回滚。流程如下

    • 一阶段: Seata对业务SQL进行拦截、语义解析,进而确定业务SQL需要操作的相关业务数据记录。然后在执行业务SQL前,将相关业务数据记录保存为Before Image。在执行业务SQL后,再将其保存成After Image。并最终生成行锁。上述操作会在一个数据库的本地事务内完成,以保证一阶段操作的原子性

    ac1b5037a31728b0ab62671623fd5d17.webp

    figure 2.png
    • 二阶段提交: 二阶段提交时,因为业务SQL在一阶段已经提交至各数据库。故Seata只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可

    0c8b221365d68f33363691b6e0f724ff.webp

    figure 3.png
    • 二阶段回滚: 二阶段回滚时,首先需要对数据库当前相关的数据与After Image进行比对,如果完全一致,这说明未发生脏写。即没有被除当前全局事务之外的其他操作修改过,可以放心进行回滚。而具体回滚则是通过Before Image生成逆向SQL来进行反向补偿,并最终删除相应快照数据和行锁

    5a4e4c65bedc5ab60d3694ea6abdd694.webp

    figure 4.png基于Seata AT模式的实践

    搭建Seata Server环境

    基于Docker Compose的服务部署

    Seata Server事实上就是上文提到的事务协调者TC。这里通过Docker Compose来进行部署,如下所示。可以看到我们不仅创建了Seata Server服务,还创建了MySQL、Nacos服务。后面会一一进行解释

    # Compose 版本
    version: '3.8'

    # 定义Docker服务
    services:

      # Seata 服务
      Seata-Service-1:
        image: seataio/seata-server:1.3.0
        container_name: Seata-Service-1
        ports:
          - "9091:8091"
        networks:
          seata_service_net:
            ipv4_address: 120.120.120.21
        depends_on:
          - MySQL-Service-1
          - Nacos-Service-1

      # MySQL 服务
      MySQL-Service-1:
        image: mysql:5.7
        container_name: MySQL-Service-1
        ports:
          - "9306:3306"
        environment:
          MYSQL_ROOT_PASSWORD: 12345
        networks:
          seata_service_net:
            ipv4_address: 120.120.120.22

      # Nacos 服务
      Nacos-Service-1:
        image: nacos/nacos-server:1.4.2
        container_name: Nacos-Service-1
        ports:
          - "9848:8848"
        environment:
          MODE: standalone
        networks:
          seata_service_net:
            ipv4_address: 120.120.120.23

    # 定义网络
    networks:
      seata_service_net:
        ipam:
          config:
            - subnet: 120.120.120.0/24

    配置Seata Server的持久化

    Seata-Server支持多种持久化方式包括文件、DB、Redis等,默认为文件File。这里我们使用刚刚部署MySQL-Service-1服务进行持久化。进入Seata-Service-1容器,修改/seata-server/resources下的file.conf文件,将存储模式修改为db,同时修改相应的数据库连接信息。如下所示,可以看到这里datasource我们选择了druid

    086683f2c1f22011b2f0eb4a4d078f47.webp

    figure 5.jpeg

    然后,通过数据库客户端连接MySQL-Service-1实例。首先创建file.conf文件中所连接的数据库seataServer,然后在该数据库中执行建表语句。其中SQL脚本可通过Github进行获取,地址如下所示

    # 下载地址: Seata Server使用DB进行持久化的SQL初始化脚本
    https://github.com/seata/seata/blob/1.3.0/script/server/db/mysql.sql

    效果如下所示

    d4b9e1156542f56d89c81c47bfd327b9.webp

    figure 6.jpeg

    配置Seata Server的注册中心、配置中心

    前面提到,我们还创建了一个Nacos容器,即Nacos-Service-1实例。其是用于作为整个分布式环境的配置中心、注册中心。同样进入Seata-Service-1容器,修改/seata-server/resources下的registry.conf文件。将注册中心、配置中心均设置Nacos。详细配置如下所示

    43c2fd84d6e624fa23a333d6db728141.webp

    figure 7.jpeg

    导入配置信息至Nacos

    事实上对于Seata而言,其配置信息支持两种形式:本地文件、配置中心。对于后者而言,我们需要将Seata的相关配置项导入到配置中心。同样,我们需要通过Github来下载配置文件config.txt及相应的导入脚本nacos-config.sh

    # 下载地址: 配置中心的配置项
    https://github.com/seata/seata/blob/1.3.0/script/config-center/config.txt

    # 下载地址: 用于将配置项导入至Nacos的脚本
    https://github.com/seata/seata/blob/1.3.0/script/config-center/nacos/nacos-config.sh

    对于配置文件config.txt而言,有以下两点需要注意

    1. 将配置项store.mode存储模式修改为db,同时修改以store.db为前缀的相关配置项,保证其与file.conf文件中相关数据库的配置一致
    2. 配置项service.vgroupMapping.my_test_tx_group=default的含义是,事务分组my_test_tx_group使用名为default的Seata Server集群。换言之,my_test_tx_group即为事务分组的名称,支持自定义。这里我们直接使用默认的事务分组名。而Seata Server集群名default实际上就是来自registry.conf文件的cluster配置项

    4a7131c121877f1a3163630778cf129c.webp

    figure 8.jpeg

    在完成配置文件config.txt的修改后,即可利用Shell脚本导入至Nacos中。值得一提的是,配置文件config.txt应与Shell脚本的上一级目录保持平行。然后在Shell脚本所在目录中执行如下命令即可

    # 执行Shell脚本
    sh nacos-config.sh -h localhost -p 9848

    该Shell脚本支持的选项如下所示

    • -h: Nacos服务的IP地址,默认为localhost
    • -p: Nacos服务的Port端口,默认为8848
    • -g: Nacos分组名,默认为SEATA_GROUP
    • -t: Nacos命名空间ID。默认为“”,即使用public命名空间
    • -u: Nacos服务的用户名
    • -w: Nacos服务的密码

    效果如下所示

    bf60078564cdcba52a85b9906138264e.webp

    figure 9.jpeg

    至此,Seata server相关环境及配置就完成了。最后,重启Seata-Service-1容器以让修改生效即可。通过Nacos的Web管理页面可以看到,Seata服务已经注册到Nacos

    098cd765ea660654330e55d27ce6d218.webp

    figure 10.jpeg

    搭建order服务

    POM依赖

    这里通过SpringBoot搭建一个微服务——order服务。这里给出关键性的依赖及版本,如下所示

    <dependencyManagement>
      <dependencies>
      
        
        <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-dependenciesartifactId>
          <version>2.3.2.RELEASEversion>
          <type>pomtype>
          <scope>importscope>
        dependency>
      
        
        <dependency>
          <groupId>org.springframework.cloudgroupId>
          <artifactId>spring-cloud-dependenciesartifactId>
          <version>Hoxton.SR8version>
          <type>pomtype>
          <scope>importscope>
        dependency>
      
        
        <dependency>
          <groupId>com.alibaba.cloudgroupId>
          <artifactId>spring-cloud-alibaba-dependenciesartifactId>
          <version>2.2.3.RELEASEversion>
          <type>pomtype>
          <scope>importscope>
        dependency>

      dependencies>
    dependencyManagement>

    <dependencies>

      
      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-seataartifactId>
        <exclusions>
          <exclusion>
            <groupId>io.seatagroupId>
            <artifactId>seata-spring-boot-starterartifactId>
          exclusion>
        exclusions>
      dependency>
      
      <dependency>
        <groupId>io.seatagroupId>
        <artifactId>seata-spring-boot-starterartifactId>
        <version>1.3.0version>
      dependency>

      
      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
      dependency>

      
      <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
      dependency>

      
      <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.76version>
      dependency>

      
      <dependency>
        <groupId>com.baomidougroupId>
        <artifactId>mybatis-plus-boot-starterartifactId>
        <version>3.4.1version>
      dependency>
      
    dependencies>

    服务配置

    order服务的配置文件application.yml,如下所示。这里关于Seata数据源的代理,我们选择自动代理的方式。此外配置文件中的相关IP、端口信息均为容器内部的IP、Port。因为对于SpringBoot服务我们也会通过Docker的方式进行构建、打包及部署

    server:
      port: 89

    spring:
      application:
        name: order
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://120.120.120.42:3306/order?allowPublicKeyRetrieval=true&useSSL=false
        username: root
        password: 12345
      cloud:
        nacos:
          discovery:
            # 注册中心 Nacos 地址信息
            server-addr: 120.120.120.23:8848
        alibaba:
          seata:
            # 配置所使用的事务分组名称
            tx-service-group: my_test_tx_group

    # Mybatis-Plus 配置
    mybatis-plus:
      mapper-locations: classpath:mapper/*.xml

    # Seata Server配置
    seata:
      # Seata服务端所在注册中心的配置信息
      registry:
        # 注册中心类型
        type: nacos
        nacos:
          # Seata服务端的服务名
          application: seata-server
          # Seata服务端所在的注册中心信息
          server-addr: 120.120.120.23:8848
          username: nacos
          password: nacos
          group: SEATA_GROUP
      # Seata服务端所在配置中心的配置信息
      config:
        type: nacos
        nacos:
          server-addr: 120.120.120.23:8848
          username: nacos
          password: nacos
          group: SEATA_GROUP
      # 使能Seata自动代理数据源
      enable-auto-data-source-proxy: true

    # Actuator配置: 开启所有端点
    management:
      endpoints:
        web:
          exposure:
            include: "*"
          base-path: /actuator

    Controller层

    在order服务中通过添加一个Controller类用于进行测试,核心代码实现如下。addRecord方法逻辑很简单。首先向自己的数据库插入一条记录,然后再调用另外一个服务pyament的接口。由于该方法是作为分布式事务的发起者,故需要在方法上添加 @GlobalTransactional 注解,以开启一个分布式事务

    @RestController
    @RequestMapping("order2")
    public class OrderController2 {

        // 使用 注册中心的服务名
        public static final String PAYMENT_URL = "http://payment";

        @Autowired
        private RestTemplate restTemplate;

        @Autowired
        private OrderRecordMapper orderRecordMapper;

        @GlobalTransactional
        @GetMapping("/addRecord")
        public String addRecord(@RequestParam String name, @RequestParam Integer total) {
            OrderRecord orderRecord = OrderRecord.builder()
                .name(name)
                .total(total)
                .build();

            // save方法通过MybatisPlus中的自定义SQL实现
            orderRecordMapper.save(orderRecord);
            String msg = restTemplate.getForObject(PAYMENT_URL +"/pay3/test1?name={1}", String.classname);
            return "OK";
        }
    }

    ...

    @Configuration
    public class RestTemplateConfig {
        @Bean
        @LoadBalanced
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }
    ...

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName("orderRecord")     // 指定数据库的表名
    public class OrderRecord {
        @TableId
        private int id;
        private String name;
        private int total;
    }

    服务部署

    首先将SpringBoot服务打包为Docker镜像,然后通过Docker Compose进行服务部署。为保证各服务、容器间的网络互通互联,这里order服务的容器同样需要使用Seata Server所在的名为seata_service_net的自定义网络。由于docker-compose.yml中自定义网络在创建后,其最终的网络名称是包含项目名的。故首先用docker network ls查看该网络的全名。如下所示,即该网络全名为seata-service_seata_service_net

    0e70c6157e8ce7650b89450aa2e8e2f7.webp

    figure 11.jpeg

    在分布式环境下,每个微服务都是使用自己的数据库。这一点在order服务的application.yml配置文件中也可以看到。故在docker-compose.yml中我们同样需要为order服务创建一个MySQL实例。如下所示

    # Compose 版本
    version: '3.8'

    # 定义Docker服务
    services:

      # Web服务
      Order-Service:
        image: aaron1995/spring_boot_order:1.0
        container_name: Order-Service
        ports:
          - "8089:89"
        networks:
          seata-service_seata_service_net:
            ipv4_address: 120.120.120.41
        depends_on:
          - Order-MySQL

      # MySQL 服务
      Order-MySQL:
        image: mysql:5.7
        container_name: Order-MySQL
        ports:
          - "9307:3306"
        environment:
          MYSQL_ROOT_PASSWORD: 12345
        networks:
          seata-service_seata_service_net:
            ipv4_address: 120.120.120.42

    # 定义网络
    networks:
      # 声明名为seata-service_seata_service_net的网络是一个已存在的网络
      seata-service_seata_service_net:
        external: true

    数据库初始化

    通过数据库客户端连接Order服务的数据库,即Order-MySQL容器。首先order服务所连接的数据库order,然后在该数据库中执行相关业务的建表语句

    # 建库建表
    create database `order`;
    use `order`;
    create table orderRecord (
        id int not null auto_increment,
        name varchar(255null,
        total int null,
        primary key (id)
    );

    当然上述这些并无什么特别,只是业务方面需要。而为了保证Seata在事务出现异常时可以实现对业务数据进行回滚,我们还需要在业务的数据库中建立undo_log表。类似地,该SQL脚本也可通过Github进行获取,下载地址如下所示

    # 下载地址: 业务数据库中undo_log表的建表SQL脚本
    https://github.com/seata/seata/blob/1.3.0/script/client/at/db/mysql.sql

    效果如下所示

    1448fc8cd1f5cbcce1e1dd397e92df89.webp

    figure 12.jpeg

    搭建payment服务

    为了验证分布式事务,自然不能只有一个微服务。故这里类似地我们再搭建一个payment服务。当然基本搭建过程与order服务并无明显差异。首先在POM依赖方面,payment服务的POM依赖与order服务一致,同样也需要引入Seata、Nacos等相关依赖。其次在服务配置方面,payment服务的application.yml配置文件中关于Seata、Nacos相关的配置自然与order服务并无二致。但需调整修改其所连接的数据库信息,如下所示。即使用自身的数据库

    server:
      port: 8011

    spring:
      application:
        name: payment
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://120.120.120.52:3306/payment?allowPublicKeyRetrieval=true&useSSL=false
        username: root
        password: 12345

    在payment服务中添加相应的Controller方法

    @RestController
    @RequestMapping("pay3")
    public class PaymentController3 {

        @Autowired
        private PayRecordMapper payRecordMapper;

        @GetMapping("/test1")
        public String test1(@RequestParam String name) {
            // 更新自身数据库中id为1的记录
            PayRecord payRecord = PayRecord.builder()
                .id(1)
                .serial( name +", "+ UUID.randomUUID().toString() )
                .build();
            payRecordMapper.updateById(payRecord);
            if(name.equals("Tony")) {
                throw new RuntimeException("发生业务异常");
            }
            return "OK";
        }
    }

    ...

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName("payRecord")     // 指定数据库的表名
    public class PayRecord {
        private int id;
        private String serial;
    }

    类似地,将payment打包为Docker镜像后,通过docker compose进行部署,如下所示

    # Compose 版本
    version: '3.8'

    # 定义Docker服务
    services:

      # Web服务
      Payment-Service:
        image: aaron1995/spring_boot_payment:1.0
        container_name: Payment-Service
        ports:
          - "8015:8011"
        networks:
          seata-service_seata_service_net:
            ipv4_address: 120.120.120.51
        depends_on:
          - Payment-MySQL

      # MySQL 服务
      Payment-MySQL:
        image: mysql:5.7
        container_name: Payment-MySQL
        ports:
          - "9308:3306"
        environment:
          MYSQL_ROOT_PASSWORD: 12345
        networks:
          seata-service_seata_service_net:
            ipv4_address: 120.120.120.52

    # 定义网络
    networks:
      # 声明名为seata-service_seata_service_net的网络是一个已存在的网络
      seata-service_seata_service_net:
        external: true

    最后,在payment服务所使用的数据库Payment-MySQL容器上完成建库建表操作。不仅包含业务表,也包含上文提到的undo_log表。如下所示,由于PaymentController3的test1方法的业务逻辑是更新id为1记录,故这里也提前插入便于后续演示

    9885a93dbec9d42ebe66fe1ab64bc9b8.webp

    figure 13.jpeg

    测试验证

    现在各服务部署完成后,从Nacos页面可以看到Seata Server、order、payment服务均已注册上线

    25208bce34ba9fa6ab8467fab076d39f.webp

    figure 14.jpeg

    当向order服务的接口发送HTTP请求时,由于name不为Tony未抛出异常。order的表中新增了一条记录。而payment表id为1的数据也被正确地更新了

    48edcfb38d99963076091ea4134928c7.webp

    figure 15.jpeg

    而当HTTP请求的name参数为Tony时,payment服务发生异常。不仅payment表未发生更新,而且order的表中也没有新增数据。即被正常回滚

    f58e92c3d3994181e5104b3759cec4d8.webp

    figure 16.jpegNote
    • 在本次实践过程中,发现通过Mybatis Plus Mapper内置的insert方法进行插入的数据在发生异常时无法进行回滚,故在order服务中添加记录是通过在相应的xml文件自定义SQL实现的。后者在发生异常时,可以对插入的数据进行回滚
    浏览 17
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    举报
    SpringCloud Alibaba之Seata分布式事务
    java1234
    0
    Springcloud 分布式事务解决方案 集成Naco Seata
    java1234
    0
    SpringCloud分布式事务
    java1234
    0
    Seata分布式事务中间件
    2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy
    Seata分布式事务中间件
    0
    微服务 SpringCloud Alibaba Seata处理分布式事务
    java1234
    0
    Seata分布式事务中间件
    2019年1月,阿里巴巴中间件团队发起了开源项目Fescar(Fast&EaSyCommitAndRollback),和社区一起共建开源分布式事务解决方案。Fescar的愿景是让分布式事务的使
    Seata分布式事务中间件
    0
    开源的分布式事务解决方案-Seata
    软件老王
    0
    SpringBoot分布式事务整合Seata
    java1234
    0
    一篇搞定分布式事务Seata
    java1234
    0
    点赞
    评论
    收藏
    分享

    手机扫一扫分享

    举报

    聚圣源守株待兔的故事火线保镖国语恋爱时代给姓庄的男宝宝起名葵司作品网上起名好不吗凌氏女孩子起名任氏宝宝起名大全贾姓男孩如何起名蛛丝马迹电影我们一起学猫叫歌名叫什么58度c奶茶加盟媛媛英文名怎么起茂字辈起名女孩拉丝工艺给小女孩起个名字好听中石化邮箱姓孔的女孩起名字悲惨世界读后感虎年春节祝福语招摇电视剧免费观看全集完整版体育行业公司怎么起名路桥养护公司起名给龙凤胎宝宝起姓名商贸公司起名大全服装长脸适合烫什么发型睿字起名大全侠隐阁攻略阮起名男本地连接不见了淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻让美丽中国“从细节出发”清明节放假3天调休1天男孩疑遭霸凌 家长讨说法被踢出群国产伟哥去年销售近13亿网友建议重庆地铁不准乘客携带菜筐雅江山火三名扑火人员牺牲系谣言代拍被何赛飞拿着魔杖追着打月嫂回应掌掴婴儿是在赶虫子山西高速一大巴发生事故 已致13死高中生被打伤下体休学 邯郸通报李梦为奥运任务婉拒WNBA邀请19岁小伙救下5人后溺亡 多方发声王树国3次鞠躬告别西交大师生单亲妈妈陷入热恋 14岁儿子报警315晚会后胖东来又人满为患了倪萍分享减重40斤方法王楚钦登顶三项第一今日春分两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?周杰伦一审败诉网易房客欠租失踪 房东直发愁男子持台球杆殴打2名女店员被抓男子被猫抓伤后确诊“猫抓病”“重生之我在北大当嫡校长”槽头肉企业被曝光前生意红火男孩8年未见母亲被告知被遗忘恒大被罚41.75亿到底怎么缴网友洛杉矶偶遇贾玲杨倩无缘巴黎奥运张立群任西安交通大学校长黑马情侣提车了西双版纳热带植物园回应蜉蝣大爆发妈妈回应孩子在校撞护栏坠楼考生莫言也上北大硕士复试名单了韩国首次吊销离岗医生执照奥巴马现身唐宁街 黑色着装引猜测沈阳一轿车冲入人行道致3死2伤阿根廷将发行1万与2万面值的纸币外国人感慨凌晨的中国很安全男子被流浪猫绊倒 投喂者赔24万手机成瘾是影响睡眠质量重要因素春分“立蛋”成功率更高?胖东来员工每周单休无小长假“开封王婆”爆火:促成四五十对专家建议不必谈骨泥色变浙江一高校内汽车冲撞行人 多人受伤许家印被限制高消费

    聚圣源 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化