• 一篇入门分布式事务框架Seata【AT模式】


    写在前面

    ​ 本地事务很好保证要么所有操作都成功要么都失败,但是随着业务越来越复杂,单机版已经满足不了我们的需求,就需要项目从单体应用演变成分布式应用,然而随之也带来了一个问题,那就是如何保证多个微服务对DB的操作要么一起成功要么一起失败的问题,也就是分布式事务的问题。

    ​ 网上有一大堆分布式事务的解决方案的理论,转化落地的有Seata这么的框架。

    官网首页对它的一句话描述是,Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

    ​ Seata提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。其中,AT 模式是一种无侵入的分布式事务解决方案,比较好上手,实现起来简单,应用的比较广泛,本文将介绍AT模式的实现。

    AT模式前提

    1. 基于支持本地 ACID 事务的关系型数据库。
    2. Java 应用,通过 JDBC 访问数据库。

    三大模块

    ​ Seata 中有三⼤模块,分别是 TM、RM 和 TC。其中 TM 和 RM 是作为 Seata 的客户端与业务系统集成在⼀起,TC 作为 Seata 的服务端独⽴部署。

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

    ​ 在 Seata 中,分布式事务的执⾏流程:

    • TM 开启分布式事务,TM会 向 TC 注册全局事务记录;
    • 操作具体业务模块的数据库操作之前,RM 会向 TC 注册分⽀事务;
    • 当业务操作完事后,TM会通知 TC 提交/回滚分布式事务;
    • TC 汇总事务信息,决定分布式事务是提交还是回滚;
    • TC 通知所有 RM 提交/回滚 资源,事务⼆阶段结束。

    整体机制

    ​ 两阶段提交协议的演变:

    • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
    • 二阶段:
      • 提交异步化,非常快速地完成。
      • 回滚通过一阶段的回滚日志进行反向补偿。

    Seata Server(TC)安装

    ​ 本文使用的是v1.4.0

    1. 配置registry.conf

      Seata Server要向注册中⼼进⾏注册,这样,其他服务就可以通过注册中⼼去发现 Seata Server,与Seata Server进⾏通信。Seata ⽀持多款注册中⼼服务:nacos 、eureka、redis、zk、consul、etcd3、sofa。我们项⽬中要使⽤ nacos注册中⼼,nacos服务的连接地址、注册的服务名,这需要在D:seataconf egistry.conf⽂件中进⾏配置:

      registry {
        # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
        type = "nacos"
        loadBalance = "RandomLoadBalance"
        loadBalanceVirtualNodes = 10
      
        nacos {
          application = "seata-server"
          serverAddr = "127.0.0.1:8848"
          group = "SEATA_GROUP"
          namespace = "b8107264-b340-4939-8d60-df77d18057dc"
          cluster = "default"
          username = "nacos"
          password = "nacos"
        }
      }
      
      config {
        # file、nacos 、apollo、zk、consul、etcd3
        type = "nacos"
      
        nacos {
          serverAddr = "127.0.0.1:8848"
          namespace = "b8107264-b340-4939-8d60-df77d18057dc"
          group = "SEATA_GROUP"
          username = "nacos"
          password = "nacos"
        }
      }
      
    2. 向nacos中添加配置信息

      • 下载配置config.txt

        Seata参数配置中针对每一个配置项有介绍。

      • 将config.txt⽂件放⼊seata⽬录下⾯

      • 修改config.txt信息

        Server端存储的模式(store.mode)现有file,db,redis三种。主要存储全局事务会话信息,分⽀事务信息, 锁记录表信息,Seata server默认是file模式。file只能⽀持单机模式, 如果想要⾼可⽤模式的话可以切换db或者redis,本文采⽤db数据库模式。

        注意这里我改了事务分组,默认是my_test_tx_group,我改成了my_tx_group。

        service.vgroupMapping.my_tx_group=default
        
        store.mode=db
        store.db.datasource=druid
        store.db.dbType=mysql
        store.db.driverClassName=com.mysql.jdbc.Driver
        store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
        store.db.user=root
        store.db.password=123456
        store.db.minConn=5
        store.db.maxConn=30
        store.db.globalTable=global_table
        store.db.branchTable=branch_table
        store.db.queryLimit=100
        store.db.lockTable=lock_table
        store.db.maxWait=5000
        
      • 新建seata数据库,并创建global_table,branch_table,lock_table三张表,点击这里下载。

        -- -------------------------------- The script used when storeMode is 'db' --------------------------------
        -- the table to store GlobalSession data
        CREATE TABLE IF NOT EXISTS `global_table`
        (
            `xid`                       VARCHAR(128) NOT NULL,
            `transaction_id`            BIGINT,
            `status`                    TINYINT      NOT NULL,
            `application_id`            VARCHAR(32),
            `transaction_service_group` VARCHAR(32),
            `transaction_name`          VARCHAR(128),
            `timeout`                   INT,
            `begin_time`                BIGINT,
            `application_data`          VARCHAR(2000),
            `gmt_create`                DATETIME,
            `gmt_modified`              DATETIME,
            PRIMARY KEY (`xid`),
            KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
            KEY `idx_transaction_id` (`transaction_id`)
        ) ENGINE = InnoDB
          DEFAULT CHARSET = utf8;
        
        -- the table to store BranchSession data
        CREATE TABLE IF NOT EXISTS `branch_table`
        (
            `branch_id`         BIGINT       NOT NULL,
            `xid`               VARCHAR(128) NOT NULL,
            `transaction_id`    BIGINT,
            `resource_group_id` VARCHAR(32),
            `resource_id`       VARCHAR(256),
            `branch_type`       VARCHAR(8),
            `status`            TINYINT,
            `client_id`         VARCHAR(64),
            `application_data`  VARCHAR(2000),
            `gmt_create`        DATETIME(6),
            `gmt_modified`      DATETIME(6),
            PRIMARY KEY (`branch_id`),
            KEY `idx_xid` (`xid`)
        ) ENGINE = InnoDB
          DEFAULT CHARSET = utf8;
        
        -- the table to store lock data
        CREATE TABLE IF NOT EXISTS `lock_table`
        (
            `row_key`        VARCHAR(128) NOT NULL,
            `xid`            VARCHAR(96),
            `transaction_id` BIGINT,
            `branch_id`      BIGINT       NOT NULL,
            `resource_id`    VARCHAR(256),
            `table_name`     VARCHAR(32),
            `pk`             VARCHAR(36),
            `gmt_create`     DATETIME,
            `gmt_modified`   DATETIME,
            PRIMARY KEY (`row_key`),
            KEY `idx_branch_id` (`branch_id`)
        ) ENGINE = InnoDB
          DEFAULT CHARSET = utf8;
        
      • 使⽤nacos-config.sh ⽤于向 Nacos 中添加配置

        #!/usr/bin/env bash
        # Copyright 1999-2019 Seata.io Group.
        #
        # Licensed under the Apache License, Version 2.0 (the "License");
        # you may not use this file except in compliance with the License.
        # You may obtain a copy of the License at、
        #
        #      http://www.apache.org/licenses/LICENSE-2.0
        #
        # Unless required by applicable law or agreed to in writing, software
        # distributed under the License is distributed on an "AS IS" BASIS,
        # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        # See the License for the specific language governing permissions and
        # limitations under the License.
        
        while getopts ":h:p:g:t:u:w:" opt
        do
          case $opt in
          h)
            host=$OPTARG
            ;;
          p)
            port=$OPTARG
            ;;
          g)
            group=$OPTARG
            ;;
          t)
            tenant=$OPTARG
            ;;
          u)
            username=$OPTARG
            ;;
          w)
            password=$OPTARG
            ;;
          ?)
            echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
            exit 1
            ;;
          esac
        done
        
        if [[ -z ${host} ]]; then
            host=localhost
        fi
        if [[ -z ${port} ]]; then
            port=8848
        fi
        if [[ -z ${group} ]]; then
            group="SEATA_GROUP"
        fi
        if [[ -z ${tenant} ]]; then
            tenant=""
        fi
        if [[ -z ${username} ]]; then
            username=""
        fi
        if [[ -z ${password} ]]; then
            password=""
        fi
        
        nacosAddr=$host:$port
        contentType="content-type:application/json;charset=UTF-8"
        
        echo "set nacosAddr=$nacosAddr"
        echo "set group=$group"
        
        failCount=0
        tempLog=$(mktemp -u)
        function addConfig() {
          curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$1&group=$group&content=$2&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
          if [[ -z $(cat "${tempLog}") ]]; then
            echo " Please check the cluster status. "
            exit 1
          fi
          if [[ $(cat "${tempLog}") =~ "true" ]]; then
            echo "Set $1=$2 successfully "
          else
            echo "Set $1=$2 failure "
            (( failCount++ ))
          fi
        }
        
        count=0
        for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
          (( count++ ))
        	key=${line%%=*}
            value=${line#*=}
        	addConfig "${key}" "${value}"
        done
        
        echo "========================================================================="
        echo " Complete initialization parameters,  total-count:$count ,  failure-count:$failCount "
        echo "========================================================================="
        
        if [[ ${failCount} -eq 0 ]]; then
        	echo " Init nacos config finished, please start seata-server. "
        else
        	echo " init nacos config fail. "
        fi
        
      • 将nacos-config.sh放在D:seataconf⽂件夹中

      • 打开git bash here 执⾏nacos-config.sh,需要提前将nacos启动

        sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t b8107264-b340-4939-8d60-df77d18057dc
        

        看下nacos中配置列表

    3. 启动Seata Server

      查看控制台中打印信息,启动成功!

      观察nacos服务列表

    TM/RM整合Seata

    1. 数据库环境准备

      ​ 新建工程study-seata,分为业务聚合(TM)study-seata-business,订单服务(RM)study-seata-order,积分服务(RM)study-seata-point,库存服务(RM)study-seata-storage。

      ​ 新建seata_order数据库,新建表t_order:

      CREATE TABLE `t_order`  (
        `id` bigint(20) NOT NULL COMMENT '订单id',
        `goods_Id` int(11) NULL DEFAULT NULL COMMENT '商品ID',
        `num` int(11) NULL DEFAULT NULL COMMENT '商品数量',
        `money` decimal(10, 0) NULL DEFAULT NULL COMMENT '商品总金额',
        `create_time` datetime(0) NULL DEFAULT NULL COMMENT '订单创建时间',
        `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '用户名称',
        PRIMARY KEY (`id`) USING BTREE
      ) ENGINE = InnoDB CHARACTER SET = utf8;
      

      ​ 新建seata_point数据库,新建表t_point:

      CREATE TABLE `t_point`  (
        `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '积分ID',
        `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '用户名',
        `point` int(11) NULL DEFAULT NULL COMMENT '用户积分',
        PRIMARY KEY (`id`) USING BTREE
      ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
      

      ​ 新建seata_storage数据库,新建表t_storage:

      CREATE TABLE `t_storage`  (
        `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '库存ID',
        `goods_id` int(11) NULL DEFAULT NULL COMMENT '商品ID',
        `storage` int(11) NULL DEFAULT NULL COMMENT '库存量',
        PRIMARY KEY (`id`) USING BTREE
      ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
      
      INSERT INTO `t_storage` VALUES (1, 1, 100);
      

      ​ AT 模式在RM端需要 UNDO_LOG 表,来记录每个RM的事务信息,主要包含数据修改前后的相关信息,⽤于回滚处理,所以在所有数据库中分别执⾏:

      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,
        PRIMARY KEY (`id`),
        UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
      ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
      

      ​ 最终数据库环境搭建好了,如下:

    2. 项目依赖配置

      版本选择:

      工具/服务组件 版本
      JDK 1.8
      SpringBoot 2.3.2.RELEASE
      SpringCloud Hoxton.SR8
      Spring-Cloud-alibaba 2.2.5.RELEASE
      Seata 1.4.0
      Mysql 5.7.28

      父pom.xml中添加Seata依赖管理,⽤于Seata的版本锁定。

      <dependencyManagement>
          <dependencies>
              <!--Spring Cloud-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-dependencies</artifactId>
                  <version>Hoxton.SR8</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
              <!--Spring Cloud Alibaba-->
              <dependency>
                  <groupId>com.alibaba.cloud</groupId>
                  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                  <version>2.2.5.RELEASE</version>
                  <type>pom</type>
                  <scope>import</scope>
              </dependency>
              <!--mysql -->
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <!-- seata-spring-boot-starter -->
              <dependency>
                  <groupId>io.seata</groupId>
                  <artifactId>seata-spring-boot-starter</artifactId>
                  <version>1.4.0</version>
              </dependency>
          </dependencies>
      </dependencyManagement>
      

      在TM/RM端pom.xml中引入Seata依赖:

      <!--seata依赖-->
      <dependency>
          <groupId>com.alibaba.cloud</groupId>
          <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
          <!--排除低版本seata依赖-->
          <exclusions>
              <exclusion>
                  <groupId>io.seata</groupId>
                  <artifactId>seata-spring-boot-starter</artifactId>
              </exclusion>
          </exclusions>
      </dependency>
      <!--添加⾼版本seata依赖-->
      <dependency>
          <groupId>io.seata</groupId>
          <artifactId>seata-spring-boot-starter</artifactId>
          <version>1.4.0</version>
      </dependency>
      

      关于依赖引入请参考版本说明

    3. 微服务配置

      study-seata-business的application.yml配置如下:

      server:
        port: 8080
      spring:
        application:
          name: study-seata-business
        # nacos配置
        cloud:
          nacos:
            # 服务发现
            discovery:
              server-addr: 127.0.0.1:8848
              namespace: b8107264-b340-4939-8d60-df77d18057dc
              username: nacos
              password: nacos
      # seata配置
      seata:
        tx-service-group: my_tx_group
        registry:
          type: Nacos
          nacos:
            application: seata-server
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
        config:
          type: Nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
      # ribbon配置
      ribbon:
        ReadTimeout: 500000
        ConnectTimeout: 50000
      

      study-seata-service-order的application.yml配置如下:

      server:
        port: 8081
      spring:
        application:
          name: study-seata-service-order
        # 数据源配置
        datasource:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_order?useSSL=false
          username: root
          password: 123456
        # nacos配置
        cloud:
          nacos:
            # 服务发现
            discovery:
              server-addr: 127.0.0.1:8848
              namespace: b8107264-b340-4939-8d60-df77d18057dc
              username: nacos
              password: nacos
      # seata配置
      seata:
        tx-service-group: my_tx_group
        registry:
          type: Nacos
          nacos:
            application: seata-server
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
        config:
          type: Nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
      

      study-seata-service-point的application.yml配置如下:

      server:
        port: 8082
      spring:
        application:
          name: study-seata-service-point
        # 数据源配置
        datasource:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_point?useSSL=false
          username: root
          password: 123456
        # nacos配置
        cloud:
          nacos:
            # 服务发现
            discovery:
              server-addr: 127.0.0.1:8848
              namespace: b8107264-b340-4939-8d60-df77d18057dc
              username: nacos
              password: nacos
      # seata配置
      seata:
        tx-service-group: my_tx_group
        registry:
          type: Nacos
          nacos:
            application: seata-server
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
        config:
          type: Nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
      

      study-seata-service-storage的application.yml配置如下:

      server:
        port: 8083
      spring:
        application:
          name: study-seata-service-storage
        # 数据源配置
        datasource:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/seata_storage?useSSL=false
          username: root
          password: 123456
        # nacos配置
        cloud:
          nacos:
            # 服务发现
            discovery:
              server-addr: 127.0.0.1:8848
              namespace: b8107264-b340-4939-8d60-df77d18057dc
              username: nacos
              password: nacos
      # seata配置
      seata:
        tx-service-group: my_tx_group
        registry:
          type: Nacos
          nacos:
            application: seata-server
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
        config:
          type: Nacos
          nacos:
            server-addr: 127.0.0.1:8848
            group: SEATA_GROUP
            namespace: b8107264-b340-4939-8d60-df77d18057dc
            username: nacos
            password: nacos
      
    4. 添加注解@GlobalTransactional

      AT模式实现分布式事务十分简单,在发起全局事务的TM端方法上增加@GlobalTransactional就可以了。

      先看看不加此注解时各服务的操作DB的情况:

      此时,并未加注解@GlobalTransactional,在碰到异常时,订单表插入了一行,积分增加了,但是库存并没有减少:

      现在,加上注解@GlobalTransactional,再次观察一下这三个表中的变化,最好打一下断点,你会发现,程序在执行完异常代码处,会回滚掉插入的订单和被修改的积分值,说明保证了分布式事务中各分支事务要么都成功提交要么发生异常都回滚。

    总结

    ​ AT 模式是⼀种⽆侵⼊的分布式事务解决⽅案。在 AT 模式下,⽤户只需关注⾃⼰的“业务 SQL”,⽤户的 “业务 SQL” 作为⼀阶段,Seata 框架会⾃动⽣成事务的⼆阶段提交和回滚操作。AT模式基于数据源代理实现,对ORM框架十分友好,集成难度低,学习成本低,十分推荐使用。

    ​ 最后,附上源码

  • 相关阅读:
    PHP数组简介
    如何在不使用系统函数的情况下实现PHP中数组系统函数的功能
    弹性盒布局display:flex详解
    关于JS面向对象中原型和原型链以及他们之间的关系及this的详解
    如何使用AngularJS对表单提交内容进行验证
    如何用canvas画布画旋转的五角星
    MYSQL常用函数以及如何操作数据
    数据库基础以及表的创建
    PHP中的OOP
    PHP中数组的遍历
  • 原文地址:https://www.cnblogs.com/mgyboom/p/14826927.html
Copyright © 2020-2023  润新知