Seata入门

1.背景资料

1.1 SEATA 是什么?

Seata: Simple Extensible Autonomous Transaction Architecture Seata
是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA
事务模式,为用户打造一站式的分布式解决方案。 —— 引用自 SEATA 官方文档

开源时间:2019年1月

1.2 什么是AT模式

Seata AT模式是基于二阶段提交的事务模式,优点是对原有业务无入侵性,入门简单。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

2.Seata Server端

2.1 下载Seata服务端

下载seata-server-1.1.0服务端 :
下载地址:https://github.com/seata/seata/releases/download/v1.1.0/seata-server-1.1.0.zip
下载seata-server-0.0.9服务端 :(1.1.0缺少一些配置需要从0.9版本里找)
下载地址:https://github.com/seata/seata/releases/download/v0.9.0/seata-server-0.9.0.zip

2.2 服务端配置

seata-server中,/conf目录下,有两个配置文件,需要结合自己的情况来修改。
本次demo中使用了Nacos作为注册、配置中心,Seata全局事务选择数据库存储方式。配置如下

2.2.1 registry.conf 配置

registry{}中是注册中心相关配置,config{}中是配置中心相关配置。seata中,注册中心和配置中心是分开实现的。

 #注册中心配置选项
registry {    
	# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
    type = "nacos"               #使用nacos
    nacos {
      serverAddr = "localhost"   #设置配置中心地址
      namespace = ""             #namespace的命名空间为空的话默认是public
      cluster = "default"
  	}
    ...
}

#配置中心配置选项
config {
  # file、nacos 、apollo、zk、consul、etcd3
  type = "nacos"                 #使用nacos
  nacos {
    serverAddr = "localhost"     #同上
    namespace = ""
  }
  ...
}

2.2.2 file.conf配置

里面有事务组配置,锁配置,事务日志存储等相关配置信息。

## transaction log store, only used in seata-server
store {
  ## store mode: file、db
  mode = "db"

  ## 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) etc.
    datasource = "druid"
    ## mysql/oracle/h2/oceanbase etc.
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:3306/seata"
    user = "root"
    password = "root"
    minConn = 1
    maxConn = 10
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
  }
}

2.2.3 nacos-config.txt配置

Seata-server v1.x release版缺少初始化配置相关文件,因此需要从v0.9版本导入。
从Seata-server v0.9版本复制 nacos.config.*(3个文件)到Seata server v1.1.0。

需要修改的配置如下:

#存储模式设置为数据库存储。
store.mode=db
#设置存储数据库相关信息。
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://172.16.103.27:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.branch.table=branch_table
store.db.datasource=dbcp
store.db.dbType=mysql
store.db.global.table=global_table
store.db.lockTable=lock_table
.....
#设置业务系统相关配置(vrgroupMapping)。
#---事务组(驼峰命名为v1.0以上版本格式。)
service.vgroupMapping.tx_tison_group=default

#添加业务系统中子事务:(3个微服务)
#---事务组中子事务storage-service
service.vgroupMapping.storage-service-fescar-service-group=default
#---事务组中子事务order-service
service.vgroupMapping.order-service-fescar-service-group=default
#---事务组中子事务account-service
service.vgroupMapping.account-service-fescar-service-group=default
......

特别注意事项:
关于nacos-config 的坑,官方更新1.0之后对配置的key做了一些升级。统一格式命名为驼峰命名,否则在启动seata服务的时候会报错,报错也会打印找不到指定的key。
例如:store.db.driver-class-name 改成 store.db.driverClassName=com.mysql.jdbc.Driver

2.2.4 数据库配置

当Server端存储模式为db时,需要创建global_table、branch_table、lock_table。

2.3 Seata将配置导入nacos

2.3.1 启动Nacos Server

见官方文档Nacos Queck Start

2.3.2 初始化Seata配置到Nacos

当Seata设置Nacos为注册中心时,需要把Seata的配置初始化到Nacos配置中心。
方式1:脚本导入
在seata1.0的包里已经没有提供 nacos-config.txt 的配置文件及nacos-config.sh。只能用seata0.9版本,在seata\conf\nacos-config.txt、nacos-config.sh。该文件可通过nacos-config.sh脚本导入。
执行脚本的命令:

sh nacos-config.sh -h localhost -p 8848

方式2:http注册 (window系统不支持sh命令时)
可以自己写个post请求脚本或使用postMan工具,将nacos-config.txt里的所有参数注册到nacos。例如下面
请求方式:POST 请求地址:http://192.168.1.151:8848/nacos/v1/cs/configs? 封装的参数格式:

{
	dataId: store.db.driverClassName
	namespace:public
	group:SEATA_CONFIG_GROUP
	content:com.mysql.jdbc.Driver
}

2.4启动seata服务

启动路径在 seata\bin\seata-server.bat。对应的nacos注册中心的服务列表对应的命名空间里就会有该服务。

3. 项目中集成Seata(客户端)

编写简单的项目模拟seata分布式事务 分别为:订单服务、库存服务、用户服务,核心模块。 springcloud seata+nacos+feign+mybatis-plus

3.1 Demo项目结构

core:通用依赖及Seata初始化配置工具
order:订单服务
storage:库存服务
user:用户服务
其中order-service通过FeignClient调用user-service进行扣款处理,调用storage-service进行库存减少处理。

3.2 配置说明

3.2.1 事务组配置

事务组概念请见官网:事务分组
配置Seata管理分布式事务时,需要保证Server端(TC)的事务分组和子事务(RM)的事务分组保持一致。

3.2.2 注册子事务

每个微服务,都需要注册RM(资源管理器)到Seata。

#resource/registry.config
registry {
  type = "nacos"

  nacos {
    serverAddr = "172.16.103.27:8848"
    namespace = ""
    cluster = "default"
  }
}

config {
  type = "nacos"
   nacos {                                 #需要与Seata初始化配置保持一致
      serverAddr = "172.16.103.27:8848"
      namespace = ""
      group = "SEATA_CONFIG_GROUP"            
      cluster = "default"
   }
}

注:v1.0版本之后,Seata支持yml/properties配置,本次demo中并未使用。

3.3 创建undo_log表

在事务链涉及的服务的数据库中新建 undo_log 表用来存储 UndoLog 信息,用于二阶段回滚操作,表中包含xid、branchId、rollback_info 等关键字段信息。

3.4 pom导入

1.8 2.2.0.RELEASE 1.1.0 2.0.0.RELEASE … ​ … com.alibaba.cloud spring-cloud-alibaba-seata ${spring-cloud-alibaba-seata.version} io.seata seata-all io.seata seata-all ${seata.version} … …
注意事项
seata-all 0.x版本和seata-all 1.x版本存在不兼容问题,spring-cloud-alibaba-seata中又包含seata-all低版本,注意排除。
seata-spring-boot-starter
1.0.0可用于替换seata-all,GlobalTransactionScanner自动初始化(依赖SpringUtils) 若其他途径实现GlobalTransactionScanner初始化,请保证io.seata.spring.boot.autoconfigure.util.SpringUtils先初始化; starter使用file配置中心时默认开启数据源自动代理
spring-cloud-alibaba-seata
2.1.0内嵌seata-all 0.7.1,2.1.1内嵌seata-all 0.9.0,2.2.0内嵌seata-spring-boot-starter 1.0.0
2.1.0和2.1.1兼容starter解决方案:
@SpringBootApplication注解内exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration
3.5 添加全局事务

 /**
   * GlobalTransactional 分布式事务注解
   */
  @Override
  @GlobalTransactional
  public void generateOrder(String userId, String commodityCode, Integer count){
    //假设商品默认价格为:100元
    int unitPrice = 100;
    int money = count * 100;
    //扣除用户的余额
    DefaultResult defaultResult = userServiceClient.deduction(userId,money);
    //如果状态不为200直接返回
    if (defaultResult.getStatus() != 200) {
      //回滚事务
      throw new RuntimeException(defaultResult.getMsg());
    }
    //如果用户扣除成功,生成订单
    OrderInfo orderInfo = new OrderInfo();
    orderInfo.setCommodityCode(commodityCode);
    orderInfo.setMoney(money);
    orderInfo.setCount(count);
    orderInfo.setUserId(userId);
    save(orderInfo);
    String xid = RootContext.unbind();
    //扣除库存
    defaultResult = storageServiceClient.deduction(commodityCode,count);
    if (defaultResult.getStatus() != 200) {
      //抛出异常,回滚事务
      throw new RuntimeException(defaultResult.getMsg());
    }
  }

全局事务发生异常时,不回滚部分事务

String xid = RootContext.unbind();    //解绑Xid
...                                    //不需要回顾的处理。
RootContext.bind(xid);                //重新绑定xid

4. AT模式限制

4.1 数据库

仅支持ACIM模式的关系型数据库事务,自动回滚(基于SQL自动补偿)
需要数据库带有InnoDB引擎
官方网站支持数据库列表:MySQL、Oracle、PostgreSQL

4.2 业务场景

不支持文件相关操作的回滚。
不支持非关系型数据库的回滚。

4.3 开发环境

只能在JDK 8以上环境中使用(Seata限制)
附录
1.官方链接
官方:https://seata.io/zh-cn/docs/overview/terminology.html
Git:https://github.com/seata
资源目录:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
2.常见问题
1.registry.conf修改问题
registry.conf修改为application.yml后异常:
Issue: 将配置参数改为application.yml后的问题
I have searched the issues of this repository and believe that this is not a duplicate.
Ⅰ. Issue Description
尝试将registry.conf和file.conf配置到application.yml中,目前发现一下两个问题,
undo相关配置不生效(估计seata.client.rm.lock相关属性也有类似问题)
service.disable-global-transaction不生效
2.注册类型为null异常
经排查,必须在@SpringApplication注解内手动exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration自动装配类,使用seata-spring-boot-starter内的io.seata.spring.boot.autoconfigure.SeataAutoConfiguration对GlobalTransactionScanner进行装配。否则项目运行时会优先使用GlobalTransactionAutoConfiguration进行装配,导致在启动加载阶段报错(”io.seata.common.exception.NotSupportYetException: not support register type: null“),报错位置为io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#get(String dataId),该方法在使用seata的spring工具类SpringUtils.getBean(propertyClass)从ApplicationContext中获取ConfigProperties.class的Bean时,因无法从容器中到符合要求的Bean而抛出NullPoinException异常.
Issue: 同时引入spring-cloud-alibaba-seata和seata-spring-boot-starter依赖,出现启动阶段无法读取ConfigProperties.class(SpringUtils.getBean抛出空指针异常)
Ⅰ. Issue Description
经排查,必须在@SpringApplication注解内手动exclude掉spring-cloud-alibaba-seata内的com.alibaba.cloud.seata.GlobalTransactionAutoConfiguration自动装配类,使用seata-spring-boot-starter内的io.seata.spring.boot.autoconfigure.SeataAutoConfiguration对GlobalTransactionScanner进行装配。否则项目运行时会优先使用GlobalTransactionAutoConfiguration进行装配,导致在启动加载阶段报错(”io.seata.common.exception.NotSupportYetException: not support register type: null“),报错位置为io.seata.spring.boot.autoconfigure.provider.SpringBootConfigurationProvider#get(String dataId),该方法在使用seata的spring工具类SpringUtils.getBean(propertyClass)从ApplicationContext中获取ConfigProperties.class的Bean时,因无法从容器中到符合要求的Bean而抛出NullPoinException异常.
依赖列表:

<dependency>
 <groupId>com.alibaba.cloudgroupId>
 <artifactId>spring-cloud-alibaba-seataartifactId>
 <version>2.1.1.RELEASEversion>
 dependency>
<dependency>
 <groupId>io.seatagroupId>
 <artifactId>seata-allartifactId>
 <version>1.0.0version>
 dependency>
 <dependency>
 <groupId>io.seatagroupId>
 <artifactId>seata-spring-boot-starterartifactId>
...

3.无法找到服务
no available service ‘null’ found, please make sure registry config correct
Seata 初始化配置到Nacos的配置文件有问题。
查看事务组名称是否相同。
Seata服务端中配置了子事务(客户端)vgroupMapping,参考见附录-参考配置-Seata配置导入Nacos。
查看Nacos保存的配置中,命名空间和组(Group)是否一致。
DEMO源码
demo参考:https://github.com/a970066364/spring-cloud-alibaba-seata
官方Demo: https://github.com/seata/seata-samples
参考配置
Seata配置导入Nacos

store.mode=db                                #存储模式设置为数据库存储。
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://172.16.103.27:3306/seata?useUnicode=true
store.db.user=root
store.db.password=root
store.db.branch.table=branch_table
store.db.datasource=dbcp
store.db.dbType=mysql
store.db.global.table=global_table
store.db.lockTable=lock_table
store.db.maxConn=3
store.db.minConn=1
store.db.queryLimit=100
store.file.dir=file_store/data
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
client.async.commit.buffer.limit=10000
client.lock.retry.internal=10
client.lock.retry.times=30
client.report.retry.count=5
client.support.spring.datasource.autoproxy=true
metrics.enabled=false
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
metrics.registryType=compact
recovery.asynCommittingRetryPeriod=1000
recovery.committingRetryPeriod=1000
recovery.rollbackingRetryPeriod=1000
recovery.timeoutRetryPeriod=1000
service.disable=false
service.disableGlobalTransaction=false
service.enableDegrade=false
service.max.commit.retry.timeout=-1
service.max.rollback.retry.timeout=-1
#---事务组(驼峰命名为v1.0以上版本格式,下划线命名为v0.9以下版本格式,根据服务器版本选择。)
service.vgroupMapping.tx_tison_group=default
service.vgroup_mapping.tx_tison_group=default
#---事务组中子事务storage-service
service.vgroup_mapping.storage-service-fescar-service-group=default
service.vgroupMapping.storage-service-fescar-service-group=default
#---事务组中子事务order-service
service.vgroup_mapping.order-service-fescar-service-group=default
service.vgroupMapping.order-service-fescar-service-group=default
#---事务组中子事务account-service
service.vgroup_mapping.account-service-fescar-service-group=default
service.vgroupMapping.account-service-fescar-service-group=default
store.file.session.reload.read_size=100
transaction.undo.data.validation=true
transaction.undo.log.delete.period=86400000
transaction.undo.log.save.days=7
transaction.undo.log.serialization=jackson
transaction.undo.log.table=undo_log
transport.compressor=none
transport.heartbeat=true
transport.serialization=seata
transport.server=NIO
transport.shutdown.wait=3
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.bossThreadSize=1
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.workerThreadSize=8
transport.type=TCP

官方资源链接:https://seata.io/zh-cn/docs/ops/deploy-guide-beginner.html
Seata相关yaml配置
以下配置为v1.0新增,用于替代项目中(client端)registry.conf和file.conf配置文件。
社区issue中有Bug记录——将配置参数改为application.yml后的问题,建议谨慎尝试。

seata:
  enabled: true
  application-id: applicationName
  tx-service-group: my_test_tx_group
  enable-auto-data-source-proxy: true
  use-jdk-proxy: false
  client:
    rm:
      async-commit-buffer-limit: 1000
      report-retry-count: 5
      table-meta-check-enable: false
      report-success-enable: false
      lock:
        retry-interval: 10
        retry-times: 30
        retry-policy-branch-rollback-on-conflict: true
    tm:
      commit-retry-count: 5
      rollback-retry-count: 5
    undo:
      data-validation: true
      log-serialization: jackson
      log-table: undo_log
    log:
      exceptionRate: 100
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091
    enable-degrade: false
    disable-global-transaction: false
  transport:
    shutdown:
      wait: 3
    thread-factory:
      boss-thread-prefix: NettyBoss
      worker-thread-prefix: NettyServerNIOWorker
      server-executor-thread-prefix: NettyServerBizHandler
      share-boss-worker: false
      client-selector-thread-prefix: NettyClientSelector
      client-selector-thread-size: 1
      client-worker-thread-prefix: NettyClientWorkerThread
      worker-thread-size: default
      boss-thread-size: 1
    type: TCP
    server: NIO
    heartbeat: true
    serialization: seata
    compressor: none
    enable-client-batch-send-request: true
  config:
    type: file
    consul:
      server-addr: 127.0.0.1:8500
    apollo:
      apollo-meta: http://192.168.1.204:8801
      app-id: seata-server
      namespace: application
    etcd3:
      server-addr: http://localhost:2379
    nacos:
      namespace:
      serverAddr: localhost
      group: SEATA_GROUP
    zk:
      server-addr: 127.0.0.1:2181
      session-timeout: 6000
      connect-timeout: 2000
      username: ""
      password: ""
  registry:
    type: file
    consul:
      cluster: default
      server-addr: 127.0.0.1:8500
    etcd3:
      cluster: default
      serverAddr: http://localhost:2379
    eureka:
      application: default
      weight: 1
      service-url: http://localhost:8761/eureka
    nacos:
      cluster: default
      server-addr: localhost
      namespace:
    redis:
      server-addr: localhost:6379
      db: 0
      password:
      cluster: default
      timeout: 0
    sofa:
      server-addr: 127.0.0.1:9603
      application: default
      region: DEFAULT_ZONE
      datacenter: DefaultDataCenter
      cluster: default
      group: SEATA_GROUP
      addressWaitTime: 3000
    zk:
      cluster: default
      server-addr: 127.0.0.1:2181
      session-timeout: 6000
      connect-timeout: 2000
      username: ""
      password: ""

SQL(MySQL)
服务端

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,
    `gmt_modified`      DATETIME,
    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;

客户端

-- 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`
(
    `id`            BIGINT(20)   NOT NULL AUTO_INCREMENT COMMENT 'increment id',
    `branch_id`     BIGINT(20)   NOT NULL COMMENT 'branch transaction id',
    `xid`           VARCHAR(100) 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     NOT NULL COMMENT 'create datetime',
    `log_modified`  DATETIME     NOT NULL COMMENT 'modify datetime',
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 1
  DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';

你可能感兴趣的:(#,SpringBoot,java)