SpringCloud Alibaba-Seata分布式事务

文章目录

  • 前言
  • 一、简述
    • 1、分布式事务过程
    • 2、处理过程
  • 二、安装
    • 1、file.conf
    • 2、数据库
    • 3、registry.conf
  • 三、使用
    • 1、POM
    • 2、yml
    • 3、客户端配置信息
    • 4、具体效果
    • 5、使用
    • 6、回滚成功


前言

今天大致学完了Seata,版本对应还是挺重要的
版本对应关系

  • SpringBoot—2.3.12.RELEASE
  • SpringCloud—Hoxton.SR12
  • SpringCloud Alibaba—2.2.7.RELEASE
  • Nacos—2.0.4
  • Seata—1.4.2

一、简述

官网

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

1、分布式事务过程

分布式事务处理过程的一ID+三组件模型:

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

2、处理过程

  1. TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  2. XID在微服务调用链路的上下文中传播
  3. RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
  4. TM向TC发起针对XID的全局提交或回滚决议
  5. TC调度XID下管辖的全部分支事务完成提交或回滚请求
    SpringCloud Alibaba-Seata分布式事务_第1张图片

二、安装

官方下载Github
将下载好的压缩包解压到指定目录,可以先将配置文件file.conf—registry.conf进行备份
官方服务端SQL文件
官方回滚SQL文件
官方客户端file.conf®istry.conf
官方客户端配置信息config.txt

  • 服务端(即seata文件)需要修改conf目录下的file.conf以及registry.conf
    • file.conf主要修改的是数据存储,如果是使用数据库进行存储,需要独立的seata数据库,可以使用官方服务端SQL文件进行初始化
    • registry.conf主要修改的是seata的注册地点以及配置信息的位置
  • 客户端(即需要使用seata的服务模块)需要指定seata的注册地点以及配置信息的位置
    • 如果使用的file方式,需要将file.conf和registry.conf放入resources目录下,文件内容可以参考官方客户端file.conf®istry.conf
    • 如果使用的不是file方式(Nacos…),只需要指定地点即可
  • 配置中心(eg:Nacos)需要有配置信息,具体可以参考官方教程
    • 高版本(v1.4.2)只需要一个配置文件,配置文件的内容可以参考官方客户端配置信息config.txt
    • 低版本的每一个配置规则独立成为一个配置文件,可以使用脚本上传配置到Nacos
  • 一切以官方文档为主

1、file.conf

主要修改seata信息的存储方式,以及存储地点的一些设置

## transaction log store, only used in seata-server
store {
  ## store mode: file、db、redis
  ##信息储存模式:file--文件,db--数据库,redis--redis
  mode = "db"
  ## rsa decryption public key
  publicKey = ""
  ## file store property
  file {
    ## store location dir
    dir = "sessionStore"
    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    maxBranchSessionSize = 16384
    # globe session size , if exceeded throws exceptions
    maxGlobalSessionSize = 512
    # file buffer size , if exceeded allocate new buffer
    fileWriteBufferCacheSize = 16384
    # when recover batch read size
    sessionReloadReadSize = 100
    # async, sync
    flushDiskMode = async
  }

  ## database store property
  ##要储存的数据库信息
  db {
    ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
    ##连接池
    datasource = "druid"
    ## mysql/oracle/postgresql/h2/oceanbase etc.
    ##数据库类型
    dbType = "mysql"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
    ##数据库名为seata,需要自己创建,并在创建表global_table--branch_table--lock_table,可以使用官方提供的SQL文件
    url = "jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&serverTimezone=UTC"
    user = "root"
    password = "123456"
    minConn = 5
    maxConn = 100
    ##具体的数据库表:global--branch--lock
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }

2、数据库

  • 如果在file.conf中指定了使用数据库存储信息,这需要对数据库进行初始化
  • seata服务端需要的数据库名为seata,需要三个表:global_table–branch_table–lock_table
  • 每一个微服务需要调用的数据库都要有一个回滚表(同一个数据库的多个表可以共用一个回滚表):undo_log

服务端表的SQL文件(数据库seata)
官方SQL

-- -------------------------------- 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_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- 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 = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

回滚表(每一个微服务需要调用的数据库)
官方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';

3、registry.conf

# seata注册方式
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"

  nacos {
  	#服务端注册进nacos的服务名
    application = "seata-server"
    #nacos地址
    serverAddr = "localhost:8848"
    #服务端注册进nacos的group
    group = "SEATA_GROUP"
    #服务端注册进nacos的namespace,默认值为public
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
  ······
}
#配置信息获取方式
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"

  nacos {
  	#nacos地址
    serverAddr = "localhost:8848"
    #配置信息所在的namesapce,默认为public
    namespace = ""
    #配置信息所在的group
    group = "SEATA_GROUP"
    #配置信息的dataId
    dataId = "seataServer.properties"
    username = "nacos"
    password = "nacos"
  }
  ······
}

三、使用

官方手册
本地@Transactional
全局@GlobalTransactional
SpringCloud Alibaba-Seata分布式事务_第2张图片
使用的时候是需要使用一个@GlobalTransactional注解,使用在事务最开始的业务方法上即可

1、POM

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

2、yml

seata的配置信息和注册方式为nacos

seata:
  #该值要和配置文件中的service.vgroupMapping.XXX=default相匹配
  tx-service-group: my-test-tx-group  #config.txt  里面的 group
  service:
    vgroup-mapping:
      my-test-tx-group: default
  enabled: true
  #设为true就不用再手动配置数据源代理
  #如果为false则需要手动配置数据代理,数据源代理应为seata下的类:io.seata.rm.datasource.DataSourceProxy
  #seata对数据源代理才能完成事务控制
  enable-auto-data-source-proxy: true 
  #seata客户端获取配置信息
  #高版本使用一个默认配置信息:dataId:seataServer.properties
  #低版本使用多个配置信息,一条配置为一个配置信息,dataId为配置名
  config:
  	#获取配置信息的方式
  	#如果为file方式,则要在resource目录下创建文件file.conf,该文件下写入seata客户端的配置信息
  	#如果为nacos方式,则要在naocs中创建配置信息
    type: nacos
    nacos:
      server-addr: localhost:8848
      group: SEATA_GROUP
      #namespace默认为public
      #使用public为命名空间不能写成-->namespace: public/"public",这样会获取不到配置信息
      namespace: ""
	  #nacos中的配置信息的dataId      
      data-id: seataServer.properties
      username: nacos
      password: nacos
  #seata服务端注册的方式
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: localhost:8848
      group: SEATA_GROUP
      namespace: ""
      username: nacos
      password: nacos

3、客户端配置信息

  • 配置信息或注册方式为file
    • 官方file.conf
    • 官方registry.conf
    • 修改file.conf和registry.conf里的部分配置信息,并放入resources目录下
  • 配置信息为nacos
    • 在nacos中添加配置信息
    • dataId为seataServer.properties(v1.4.2)
    • group的值随意,但是需要客户端可以找到该配置
    • 具体配置内容可以参考官方config.txt

file.conf或seataServer.properties的一个重要配置

  • service.vgroupMapping.default_tx_group=default

default_tx_group可以随意指定,但是该值必须要和客户端的yml里的值一致,相同才能表明是同一个事务组

  • file.conf/seataServer.properties
    • service.vgroupMapping.XXX=default
  • application.yml
    • seata.tx-service-group: XXX

4、具体效果

  • 启动Nacos

    • 运行bin/startup.cmd
  • 启动Seata

    • 运行bin/seata-server.bat
      SpringCloud Alibaba-Seata分布式事务_第3张图片
  • seata数据库

    • 需要有指定结构的四个表:branch_table—distributed_lock—global_table—lock_table
      SpringCloud Alibaba-Seata分布式事务_第4张图片
  • 服务操作的数据库

    • 每一个数据库都需要有指定结构的undo_log表
      在这里插入图片描述SpringCloud Alibaba-Seata分布式事务_第5张图片
  • Nacos中有seata客户端需要的配置
    SpringCloud Alibaba-Seata分布式事务_第6张图片

  • 服务的application.yml指定的seata的一些配置要求
    SpringCloud Alibaba-Seata分布式事务_第7张图片

  • 启动服务
    SpringCloud Alibaba-Seata分布式事务_第8张图片

5、使用

在一个事务组中,只需要使用@GlobalTransactional注解在事务最开始的方法上即可
如果调用其他服务出错时,会将该事务所以涉及的数据进行回滚
@GlobalTransactional的name属性设置一个独特的名字即可
@GlobalTransactional的rollbackFor属性为回滚条件

    @Override
    //Exception.class表明出现了任意异常都会对数据进行回滚
    @GlobalTransactional(name = "test-create-order",rollbackFor = Exception.class)
    public void create(Order order)
    {
        log.info("----->开始新建订单");
        //1 新建订单
        orderMapper.create(order);

        //2 扣减库存
        log.info("----->订单微服务开始调用库存,做扣减Count");
        storageService.decrease(order.getProductId(),order.getCount());
        log.info("----->订单微服务开始调用库存,做扣减end");

        //3 扣减账户
        log.info("----->订单微服务开始调用账户,做扣减Money");
        accountService.decrease(order.getUserId(),order.getMoney());
        log.info("----->订单微服务开始调用账户,做扣减end");

        //4 修改订单状态,从零到1,1代表已经完成
        log.info("----->修改订单状态开始");
        orderMapper.update(order.getUserId(),0);
        log.info("----->修改订单状态结束");

        log.info("----->下订单结束了,O(∩_∩)O哈哈~");

    }

6、回滚成功

SpringCloud Alibaba-Seata分布式事务_第9张图片
如果对服务进行debug,可以在保存数据的地址(eg:数据库…)看到每一个相关数据库的undo_log存在数据,seata数据库的表也存在数据
如果服务回滚完成,会将这些数据删除
官方说明

你可能感兴趣的:(spring,cloud)