6 Star 72 Fork 28

JustryDeng / notebook

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
[01]seata实现分布式事务.md 37.19 KB
一键复制 编辑 原始数据 按行查看 历史
JustryDeng 提交于 2023-09-16 22:53 . seata实现分布式事务

seata实现分布式事务

简述

Seata 是一款阿里开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案

相关概念

TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚

TM (Transaction Manager) - 事务管理器

定义全局事务的范围:开始全局事务、提交或回滚全局事务

RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚

AT模式 - Auto Transaction

AT模式基于支持本地 ACID事务的关系型数据库,是Seata默认的工作模式,AT模式是最终一致的分阶段事务模式,无业务侵入

AT模式是seata的默认模式,也是seata主推的分布式事务解决方案

使用前提:需要支持本地 ACID 事务的关系型数据库,应用为 Java应用,且使用JDBC访问数据库

更多详见here

这是一种无侵入式分布式事务解决方案,该模式下,用户只需要关注自己的""业务SQL"(这是第一个阶段),seata框架会自动生成分布式事务的二阶段提交或回滚

  • 一阶段

    在该阶段,seata会拦截业务SQL,首先解析SQL语义,找到对应要更新的业务数据,在业务数据更新之前,将其保存成"before image"(未更新前的原始数据--回滚数据),然后执行业务SQL,更新业务数据。在更新之后,再将其保存为"after image"(更新之后的数据--提交数据),最后生成行锁,以上操作全部在一个数据库事务内完成,这样保证了一阶段事务的原子性

    image-20230615095208980

  • 二阶段提交

    二阶段如果是提交的话,因为业务SQL在一阶段已经提交至数据库,所以seata框架只需要将一阶段保存的快照数据和行锁删掉即可

    image-20230615094616206

  • 二阶段回滚

    seata需要回滚一阶段已经执行的业务SQL,使用一阶段存储的"before image"还原业务数据;但在还原前,要首先校验脏写,对比数据库当前业务数据和"after image",如果两份数据完全一样,说明没有脏写,可以还原业务数据,如果不一致,说明出现脏写,需要人工干预

    image-20230615094630941

TCC模式 - Try、Confirm、Cancel模式

最终一致的分阶段事务模式,有业务侵入,更多详见here

该模式需要用户根据自己的业务场景(因此侵入性太强,但是整个过程不涉及锁,性能较高),实现Try、Confirm和Cancel三个操作,事务发起方在一阶段执行Try方法,二阶段执行Confirm或Cancel方法

image-20230615095240266

SAGA模式

长事务模式,有业务侵入,更多详见here

Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现

image-20230615095030308

适用场景

  • 业务流程长、业务流程多
  • 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口

优势

  • 一阶段提交本地事务,无锁,高性能
  • 事件驱动架构,参与者可异步执行,高吞吐
  • 补偿服务易于实现

XA模式

强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入

使用前提:需要分支数据库支持XA 事务,应用为 Java应用,且使用JDBC访问数据库

更多详见here

XA与AT不同的是,AT是分支事务执行完了就会提交,后面有不对再回滚,而XA是,分支事务执行完了不会提交,而是等待所有的分支事务都执行完了再一起提交(或一起回滚)

image-20230615100126784

RM一阶段的工作

  • 注册分支事务到TC
  • 执行分支业务sql但不提交
  • 报告执行状态到TC

TC二阶段的工作

  • TC检测各分支事务的执行状态。如果都成功,通知所有RM提交事务;如果有失败,通知所有RM回滚事务

Seata的四种事务模式总结

Seata的四种模式的思想其实都是二阶段处理(第一阶段是执行,第二阶段是确认结果或者回滚结果)

AT TCC XA SAGA
一阶段 分支事务执行并提交

架构统筹者:TM、TC、RM
分支事务执行并提交

架构统筹者:TM、TC、RM
分支事务执行但不提交

架构统筹者:TM、TC、RM
分支事务执行并提交

架构统筹者:状态机
二阶段确认 所有分支事务都执行成功了,则处理掉预留的用于反向补偿的image数据及锁


架构统筹者:TM、TC、RM
所有分支事务都try执行成功了,则Confirm


架构统筹者:TM、TC、RM
所有分支事务都成功了,则都提交

架构统筹者:TM、TC、RM
所有分支事务都成功了,则状态机总为状态成功

架构统筹者:状态机
二阶段回滚 存在分支事务执行失败,
则根据预留的用于反向补偿的image数据,实现数据反向补偿

架构统筹者:TM、TC、RM
存在分支事务执行失败,
则所有分支事务都Cancel


架构统筹者:TM、TC、RM
存在分支事务执行失败,
则都回滚

架构统筹者:TM、TC、RM
存在分支事务执行失败,
则进行补偿


架构统筹者:状态机

这是一张来自Seata分布式事务详解(原理流程及4种模式)的对比分析:

image-20230615103118861

部署seata服务端

确认版本

组件版本关系确认要下载的Seata版本

如本人目前(2023-06-15)用的这个系列

image-20230615113551215

安装seata

直接安装

第一步:下载安装包并解压

这里下载即可,不论是windows还是linux,zip或tar.gz都可以

image-20230615113932277

image-20230615114637072

第二步:配置注册中心、配置中心

配置文件位置:config/application.yml(,可参考配置样例config/application.example.yml完成配置)

数据库相关的,这里先进行配置,然后接着再初始化相关库表就行

server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  # 其余不存在,注释掉即可
  #extend:
  #  logstash-appender:
  #    destination: 127.0.0.1:4560
  #  kafka-appender:
  #    bootstrap-servers: 127.0.0.1:9092
  #    topic: logback_to_logstash

# 设置seata控制台账密
console:
  user:
    username: seata
    password: seata
    
seata:
  # ##################################### 配置中心配置 #####################################
  config:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      # 不填则默认为public
      namespace:
      group: SEATA_GROUP
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      data-id: seataServer.properties
  # ##################################### 注册中心配置 #####################################
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    # 多网卡情况下,seata往注册中心注册的ip地址不对时,可使用此参数指定前缀
    #preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      # 不填则默认为public
      namespace:
      cluster: default
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      
  # ##################################### 数据存储配置(建议配置在配置中心里) #####################################
  #store:
  #  # support: file 、 db 、 redis
  #  mode: db # 这里指定采用db进行数据存储
  #  # 进行database相关配置(指定连接的库、用户名、密码、、相关表名)
  #  db:
  #    datasource: druid
  #    db-type: mysql
  #    driver-class-name: com.mysql.cj.jdbc.Driver
  #    # 如果你重命名了库名,需要在这里指定
  #    url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
  #    user: root
  #    password: dengshuai
  #    min-conn: 10
  #    max-conn: 100
  #    global-table: global_table # 如果你重命名了表名,需要在这里指定
  #    branch-table: branch_table # 如果你重命名了表名,需要在这里指定
  #    lock-table: lock_table # 如果你重命名了表名,需要在这里指定
  #    distributed-lock-table: distributed_lock # 如果你重命名了表名,需要在这里指定
  #    query-limit: 1000
  #    max-wait: 5000
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
第三步:配置seata的存储方式及其它可选参数

seata配置大全见官网说明

在配置中心nacos中,seata所在的命名空间下,创建在上一步(通过seata.config.nacos.data-id=seataServer.properties)指定的配置文件,并完成配置

image-20230627124506495

################################## seata的存储配置 ##################################
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
# 默认为file,这里改为db
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption

#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
# 设置mysql的连接地址及账密
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=dengshuai
store.db.minConn=5
store.db.maxConn=30
# 指定相关表的表名名
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.lockTable=lock_table
store.db.queryLimit=100
store.db.maxWait=5000



#Transaction routing rules configuration, only for the client
# 格式为: service.vgroupMapping.{事务组}={TC集群名},指明{事务组}是被哪个{TC集群}管理的
#         此参数主要用于当存在多个TC集群时,指定事务组从属TC集群关系的
#         如果不是(多)集群部署的seate-server的话,这里我们只需要指定{事务组}名称即可,TC集群名写default即可
#         使用场景案例见:https://seata.io/zh-cn/docs/user/txgroup/transaction-group-and-ha.html
service.vgroupMapping.demo-group=default
#If you use a registry, you can ignore it
# 这里走注册中心的方式,不需要指定seata的地址
#service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false


################################## 其它配置 ##################################
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none

#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h

#Log rule configuration, for client and server
log.exceptionRate=100

#Transaction rule configuration, only for the server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false

#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
第四步:初始化相关库表

数据库脚本位置(以mysql为例):script/server/db/mysql.sql

先保证数据库存在

create database seata;
use seata;

再执行script/server/db/mysql.sql完成数据库的初始化

image-20230615115952020

这里也直接贴一下script/server/db/mysql.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` (`xid`)
) 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);
第五步:启动seata

提示:因为这里本人依赖了数据库和nacos,所以启动seata钱需保证这两者可用

直接运行启动脚本即可

# windows双击运行
bin/seata-server.bat
# linux. 注:此脚本中即为后台启动
# sh ./bin/seata-server.sh
第六步:验证一下

启动seata后,访问一下seata的console页面查看一下

访问:{ip}:{端口}进行访问,如:localhost:7091,账密为前面application.yml中设置的,本人前面设置的账密是seata/seata

image-20230615121922367

image-20230615121928521

同时nacos中,seata-server也注册进去了

image-20230615122022730

docker安装

参考官网教学

第一步:确保安装有docker

安装教程见[CentOS 7安装docker、docker-compose](./[04]CentOS 7安装docker、docker-compose.md)

查看docker版本

docker -v

image-20221203082303705

第二步:拉取镜像

镜像有哪些版本,可以通过查看一个docker镜像有哪些版本查看

docker pull seataio/seata-server:1.6.1
第三步:准备相关挂载目录及文件
# 先临时启动一个seata-tmp容器
docker run -d --name seata-tmp seataio/seata-server:1.6.1

# 将容器中/seata-server/resources目录,拷贝到宿主机/opt/seata
mkdir -p /opt/seata
docker cp seata-tmp:/seata-server/resources /opt/seata
# 移除临时容器
docker stop seata-tmp
docker rm seata-tmp
第四步:注册中心、配置中心

编辑上一步拷贝出来的application.yml文件

[root@localhost resources]# pwd
/opt/seata/resources
[root@localhost resources]#
[root@localhost resources]# vim /opt/seata/resources/application.yml
server:
  port: 7091

spring:
  application:
    name: seata-server

logging:
  config: classpath:logback-spring.xml
  file:
    path: ${user.home}/logs/seata
  # 其余不存在,注释掉即可
  #extend:
  #  logstash-appender:
  #    destination: 127.0.0.1:4560
  #  kafka-appender:
  #    bootstrap-servers: 127.0.0.1:9092
  #    topic: logback_to_logstash

# 设置seata控制台账密
console:
  user:
    username: seata
    password: seata
    
seata:
  # ##################################### 配置中心配置 #####################################
  config:
    # support: nacos 、 consul 、 apollo 、 zk  、 etcd3
    type: nacos
    nacos:
      server-addr: 192.168.44.3:8848
      # 不填则默认为public
      namespace:
      group: SEATA_GROUP
      # seata连接nacos时,会对特殊字符转义,所以nacos的姓名密码最好不要有特殊字符(如:!@之类的),否则可能导致nacos认证失败连接不上
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      data-id: seataServer.properties
  # ##################################### 注册中心配置 #####################################
  registry:
    # support: nacos 、 eureka 、 redis 、 zk  、 consul 、 etcd3 、 sofa
    type: nacos
    # 多网卡情况下,seata往注册中心注册的ip地址不对时,可使用此参数指定前缀
    #preferred-networks: 30.240.*
    nacos:
      application: seata-server
      server-addr: 192.168.44.3:8848
      group: SEATA_GROUP
      # 不填则默认为public
      namespace:
      cluster: default
      # seata连接nacos时,会对特殊字符转义,所以nacos的姓名密码最好不要有特殊字符(如:!@之类的),否则可能导致nacos认证失败连接不上
      username: nacos
      password: nacos
      context-path:
      ##if use MSE Nacos with auth, mutex with username/password attribute
      #access-key:
      #secret-key:
      
  # ##################################### 数据存储配置(建议配置在配置中心中) #####################################
  #store:
  #  # support: file 、 db 、 redis
  #  mode: db # 这里指定采用db进行数据存储
  #  # 进行database相关配置(指定连接的库、用户名、密码、、相关表名)
  #  db:
  #    datasource: druid
  #    db-type: mysql
  #    driver-class-name: com.mysql.cj.jdbc.Driver
  #    # 如果你重命名了库名,需要在这里指定
  #    url: jdbc:mysql://192.168.44.3:3306/seata?rewriteBatchedStatements=true&useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
  #    user: root
  #    password: dengshuai
  #    min-conn: 10
  #    max-conn: 100
  #    global-table: global_table # 如果你重命名了表名,需要在这里指定
  #    branch-table: branch_table # 如果你重命名了表名,需要在这里指定
  #    lock-table: lock_table # 如果你重命名了表名,需要在这里指定
  #    distributed-lock-table: distributed_lock # 如果你重命名了表名,需要在这里指定
  #    query-limit: 1000
  #    max-wait: 5000
#  server:
#    service-port: 8091 #If not configured, the default is '${server.port} + 1000'
  security:
    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
    tokenValidityInMilliseconds: 1800000
    ignore:
      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
第五步:配置seata的存储方式及其它可选参数

与直接安装的一样,见[配置seata的存储方式及其它可选参数](# 第三步:配置seata的存储方式及其它可选参数)

第六步:初始化相关库表

docker镜像中没有数据库初始化脚本文件,需要和直接安装一样,去这里下载安装包,然后在压缩包里script/server/db/目录下,有各个数据库的初始化脚本文件。如,mysql的脚本文件在压缩包的script/server/db/mysql.sql

script/server/db/mysql.sql的内容,上文直接安装步骤中有贴,见[直接安装步骤中贴出来的数据库脚本](# 第四步:初始化相关库表)

第七步:启动seata
# 正式启动seata
docker run -d --name seata-server \
 -p 7091:7091 \
 -p 8091:8091 \
 # 设置seata的通信ip
 -e SEATA_IP=192.168.44.139
 # 设置seata的通信端口(注:这里指定的端口,指的是容器内seata-server服务的端口,而不是宿主机的端口)
 # 简单的,即:第二个-p指的是宿主机端口是多少,那么对应映射的容器里面的端口就应该是多少,这里就指定多少,都是同一个值。如这里都是 8091一样
 -e SEATA_PORT=8091
 -v /opt/seata/resources:/seata-server/resources \
 seataio/seata-server:1.6.1
 
 
# 查看一下日志
docker logs -f seata-server
第八步:验证一下

验证方式与直接安装的验证方式一样,见[直接安装的验证方式](# 第六步:验证一下)

使用示例

AT模式(示例)

第一步:引入依赖

相关微服务都需要引入seata;需要引入与spring-cloud-alibaba对应的版本,可以去组件版本关系确认Seata版本

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <!-- 排除掉springcloud默认的seata版本,以免版本不一致出现问题-->
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-all</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.6.1</version>
</dependency>

第二步:进行配置

在相关微服务中进行seata配置

# 根据seata-server的 service.vgroupMapping.{事务组名}={TC集群名} 配置,对应填写这里的值
seata:
  enabled: true
  # 当前客户端的id
  application-id: ${spring.application.name:} 
  # 事务模式设置为AT模式(默认即为AT模式)
  data-source-proxy-mode: AT
  # 事务所属分组
  tx-service-group: demo-group # 事务分组名称,要和服务端对应
  service:
    vgroup-mapping:
      # 事务分组名 及 该事务分组所属的 TC集群名
      demo-group: default
  # 指定seata相关配置中心注册中心
  config:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.config.server-addr}
      namespace: ${spring.cloud.nacos.config.namespace:}
      username: nacos
      password: ${spring.cloud.nacos.config.password:}
      # 指定seata的配置文件(注:因为将一部分server、client共用的配置写在了server端的配置文件seataServer.properties里。所以虽然当前是client,也可以直接指定server端的配置文件seataServer.properties)
      data-id: seataServer.properties
  registry:
    type: nacos
    nacos:
      server-addr: ${spring.cloud.nacos.discovery.server-addr}
      namespace: ${spring.cloud.nacos.discovery.namespace:}
      username: nacos
      password: ${spring.cloud.nacos.discovery.password:}

第三步:在每个业务数据库中,创建undo_log表

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

第四步:使用测试

完整demo项目见文末链接

使用以下代码即可验证分布式事务生效与否

@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserPO> implements SysUserService {
    
    @Resource
    private BetaFeignClient betaFeignClient;
    
    /**
     * 未开启分布式事务前:调用此方法后, sys_user不会新增任何数据;而sys_role将成功新增一条数据
     * 开启分布式事务后:调用此方法后, sys_user和sys_role都不会新增仍和数据
     * <p>
     * 注:与本地事务@Transactional的识别机制一样,只有当@GlobalTransactional感知到异常时,才会自动全局回滚。
     *    所以,如果失败了,需要抛出异常,而不要try catch掉异常
     * 注:一般的,使用了@GlobalTransactional后就不需要使用@Transactional了。但是当一个分支事务(本地事务)中存在多个dml时,
     *     建议使用@GlobalTransactional的同时,也使用@Transactional
     * 注:与本地事务@Transactional类似,@GlobalTransactional也可以嵌套@GlobalTransactional进行使用
     * 注:当与本地事务结合@Transactional和@GlobalTransactional连用时,@Transactional
     *     只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional
     *     标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class) // 添加此注解,则表示当前操作开启了分布式事务
    public SysUserPO test() {
        // 1. 本地添加用户A成功
        SysUserPO sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户A");
        save(sysUser);
        
        // 2. feign调用beta微服务添加角色成功
        betaFeignClient.randomAddRole();
        
        // 3. 本地添加随机用户B失败
        sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户B");
        save(sysUser);
        System.err.println(1 / 0);
        return null;
    }
}

注意事项

注意事项(更多详见Seata FAQ):

  1. 业务表中必须包含单列主键,若存在复合主键,请参考问题 13 。

  2. 每个业务库中必须包含 undo_log 表,若与分库分表组件联用,分库不分表。

  3. 跨微服务链路的事务需要对相应 RPC 框架支持,目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。

  4. 目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL和 TiDB。

  5. 使用注解开启分布式事务时,若默认服务 provider 端加入 consumer 端的事务,provider 可不标注注解。但是,provider 同样需要相应的依赖和配置,仅可省略注解。

  6. 使用注解开启分布式事务时,若要求事务回滚,必须将异常抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer 判断再抛出异常。

  7. AT 模式和 Spring @Transactional 注解连用时需要注意什么 ?

    @Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务,大家常用的是与本地事务结合。当与本地事务结合时,@Transactional和@GlobalTransactional连用,@Transactional 只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。

  8. AT模式下,@GlobalTransactional所谓的"回滚"其实不是未提交回滚,而是已提交后的反向补偿;而进行反向补偿操作可能会需要一定时间(网络差、要反向补偿的数据多等因素都可能影响这个时间的长短),所以有时,我们方法明明已经报错了,但是你去数据库查看数据时发现了还没"回滚",这时就可能是seata的反向补偿逻辑还没进行完,可以等个一两秒后再刷新观察数据是否已"回滚"

相关资料

1
https://gitee.com/JustryDeng/notebook.git
git@gitee.com:JustryDeng/notebook.git
JustryDeng
notebook
notebook
master

搜索帮助