Seata 是一款阿里开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案
维护全局和分支事务的状态,驱动全局事务提交或回滚
定义全局事务的范围:开始全局事务、提交或回滚全局事务
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
AT模式基于支持本地 ACID事务的关系型数据库,是Seata默认的工作模式,AT模式是最终一致的分阶段事务模式,无业务侵入
AT模式是seata的默认模式,也是seata主推的分布式事务解决方案
使用前提:需要支持本地 ACID 事务的关系型数据库,应用为 Java应用,且使用JDBC访问数据库
更多详见here
这是一种无侵入式分布式事务解决方案,该模式下,用户只需要关注自己的""业务SQL"(这是第一个阶段),seata框架会自动生成分布式事务的二阶段提交或回滚
一阶段
在该阶段,seata会拦截业务SQL,首先解析SQL语义,找到对应要更新的业务数据,在业务数据更新之前,将其保存成"before image"(未更新前的原始数据--回滚数据),然后执行业务SQL,更新业务数据。在更新之后,再将其保存为"after image"(更新之后的数据--提交数据),最后生成行锁,以上操作全部在一个数据库事务内完成,这样保证了一阶段事务的原子性
二阶段提交
二阶段如果是提交的话,因为业务SQL在一阶段已经提交至数据库,所以seata框架只需要将一阶段保存的快照数据和行锁删掉即可
二阶段回滚
seata需要回滚一阶段已经执行的业务SQL,使用一阶段存储的"before image"还原业务数据;但在还原前,要首先校验脏写,对比数据库当前业务数据和"after image",如果两份数据完全一样,说明没有脏写,可以还原业务数据,如果不一致,说明出现脏写,需要人工干预
最终一致的分阶段事务模式,有业务侵入,更多详见here
该模式需要用户根据自己的业务场景(因此侵入性太强,但是整个过程不涉及锁,性能较高),实现Try、Confirm和Cancel三个操作,事务发起方在一阶段执行Try方法,二阶段执行Confirm或Cancel方法
长事务模式,有业务侵入,更多详见here
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现
适用场景
优势
强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
使用前提:需要分支数据库支持XA 事务,应用为 Java应用,且使用JDBC访问数据库
更多详见here
XA与AT不同的是,AT是分支事务执行完了就会提交,后面有不对再回滚,而XA是,分支事务执行完了不会提交,而是等待所有的分支事务都执行完了再一起提交(或一起回滚)
RM一阶段的工作
TC二阶段的工作
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种模式)的对比分析:
去组件版本关系确认要下载的Seata版本
如本人目前(2023-06-15)用的这个系列
去这里下载即可,不论是windows还是linux,zip或tar.gz都可以
配置文件位置: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配置大全见官网说明
在配置中心nacos中,seata所在的命名空间下,创建在上一步(通过seata.config.nacos.data-id=seataServer.properties)指定的配置文件,并完成配置
################################## 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
完成数据库的初始化
这里也直接贴一下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);
提示:因为这里本人依赖了数据库和nacos,所以启动seata钱需保证这两者可用
直接运行启动脚本即可
# windows双击运行
bin/seata-server.bat
# linux. 注:此脚本中即为后台启动
# sh ./bin/seata-server.sh
启动seata后,访问一下seata的console页面查看一下
访问:
{ip}:{端口}
进行访问,如:localhost:7091,账密为前面application.yml中设置的,本人前面设置的账密是seata/seata
同时nacos中,seata-server也注册进去了
参考官网教学
安装教程见[CentOS 7安装docker、docker-compose](./[04]CentOS 7安装docker、docker-compose.md)
查看docker版本
docker -v
镜像有哪些版本,可以通过查看一个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的存储方式及其它可选参数)
docker镜像中没有数据库初始化脚本文件,需要和直接安装一样,去这里下载安装包,然后在压缩包里
script/server/db/
目录下,有各个数据库的初始化脚本文件。如,mysql的脚本文件在压缩包的script/server/db/mysql.sql
script/server/db/mysql.sql
的内容,上文直接安装步骤中有贴,见[直接安装步骤中贴出来的数据库脚本](# 第四步:初始化相关库表)
# 正式启动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
验证方式与直接安装的验证方式一样,见[直接安装的验证方式](# 第六步:验证一下)
相关微服务都需要引入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:}
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):
业务表中必须包含单列主键,若存在复合主键,请参考问题 13 。
每个业务库中必须包含 undo_log 表,若与分库分表组件联用,分库不分表。
跨微服务链路的事务需要对相应 RPC 框架支持,目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。
目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL和 TiDB。
使用注解开启分布式事务时,若默认服务 provider 端加入 consumer 端的事务,provider 可不标注注解。但是,provider 同样需要相应的依赖和配置,仅可省略注解。
使用注解开启分布式事务时,若要求事务回滚,必须将异常抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer 判断再抛出异常。
AT 模式和 Spring @Transactional 注解连用时需要注意什么 ?
@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务,大家常用的是与本地事务结合。当与本地事务结合时,@Transactional和@GlobalTransactional连用,@Transactional 只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。
AT模式下,@GlobalTransactional所谓的"回滚"其实不是未提交回滚,而是已提交后的反向补偿;而进行反向补偿操作可能会需要一定时间(网络差、要反向补偿的数据多等因素都可能影响这个时间的长短),所以有时,我们方法明明已经报错了,但是你去数据库查看数据时发现了还没"回滚",这时就可能是seata的反向补偿逻辑还没进行完,可以等个一两秒后再刷新观察数据是否已"回滚"
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。