Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的一款分布式事务解决方案,旨在帮助开发者解决微服务架构下的分布式事务问题。它提供了高效且易于使用的分布式事务管理能力,支持多种事务模式,确保数据的一致性和完整性。
以下是 Seata 的一些关键特性和功能:
-
全局事务管理:Seata 提供了一个全局事务协调器(Transaction Coordinator, TC),负责管理全局事务的生命周期,包括开始、提交、回滚等操作。
-
AT 模式(Automatic Transaction):这是 Seata 最常用的事务模式,通过代理数据库操作,实现自动的分支事务管理。AT 模式下,Seata 会在业务操作前后自动生成快照,并在事务提交或回滚时进行相应的处理。
-
TCC 模式(Try-Confirm-Cancel):这种模式需要开发者手动实现 Try、Confirm 和 Cancel 三个阶段的逻辑。Try 阶段预留资源,Confirm 阶段确认操作,Cancel 阶段则回滚操作。这种模式适用于需要精细控制事务逻辑的场景。
-
SAGA 模式:基于补偿机制的长事务解决方案,适用于跨多个服务的复杂业务流程。每个步骤都有对应的补偿操作,当某一步失败时,可以通过执行补偿操作来回滚整个事务。
-
XA 模式:基于两阶段提交协议的分布式事务解决方案,适用于支持 XA 协议的数据库。第一阶段准备事务,第二阶段提交或回滚事务。
-
高性能:Seata 通过优化网络通信和存储机制,提供了高性能的事务处理能力,能够满足大规模分布式系统的需求。
-
易于集成:Seata 支持与 Spring Cloud、Dubbo、gRPC 等主流微服务框架的无缝集成,开发者只需进行简单配置即可使用分布式事务功能。
-
可扩展性:Seata 提供了丰富的 SPI 接口,允许开发者根据具体需求进行扩展和定制,例如自定义事务日志存储、事务协调策略等。
-
社区活跃:作为一个开源项目,Seata 拥有活跃的社区和丰富的文档资源,开发者可以方便地获取支持和帮助。
通过 Seata,开发者可以轻松地在分布式系统中实现一致性事务管理,确保数据的一致性和完整性,从而提高系统的可靠性和稳定性。
参考 官方文档
下载 Seata
官方下载地址
我使用的是 1.4.1 版本:
进入conf目录,调整下面的配置文件
registry.conf
设置使用 Nacos 注册中心:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# type = "file"
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "public"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
# type = "file"
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "public"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
file.conf
使用 MySQL 8.X JDBC 驱动、数据库账号、密码:
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
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)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true"
user = "root"
password = "root"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
## redis store property
redis {
host = "127.0.0.1"
port = "6379"
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
seata-1.4.1 配置
克隆 1.4.1 分支
git clone -b 1.4.1 https://gitee.com/ma-jiansheng/seata
微服务 yml 配置
seata:
enabled: true
application-id: admin-server
tx-service-group: admin-server-group
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace: public
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: public
userName: "nacos"
password: "nacos"
修改 script/config-center 下 config.txt 文件
修改 service.vgroupMapping 和数据库地址:
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
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
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
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=false
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
service.vgroupMapping.admin-server-group=default
service.vgroupMapping.discussion-server-group=default
service.vgroupMapping.enterprise-server-group=default
service.vgroupMapping.job-server-group=default
service.vgroupMapping.student-server-group=default
store.mode=db
store.file.dir=file_store/data
store.file.maxBranchSessionSize=16384
store.file.maxGlobalSessionSize=512
store.file.fileWriteBufferCacheSize=16384
store.file.flushDiskMode=async
store.file.sessionReloadReadSize=100
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql:///dgut?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=true
store.db.user=root
store.db.password=root
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
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.database=0
store.redis.password=null
store.redis.queryLimit=100
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
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
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
注意:
service.vgroupMapping.admin-server-group=default
service.vgroupMapping.discussion-server-group=default
service.vgroupMapping.enterprise-server-group=default
service.vgroupMapping.job-server-group=default
service.vgroupMapping.student-server-group=default
ABC-server-group 与项目模块中 yml 配置的 seata.tx-service-group 内容相同。
启动 nacos-config.sh ,将配置注入到 nacos 中
sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos
在 nocos 中心可以看到新增的配置:
启动 Seata 服务
seata-server.bat -p 9000 -m file
-p 指定启动seata server的端口号。
-h 指定seata server所绑定的主机。
-m 事务日志、事务执行信息存储的方式,目前支持file(文件方式)、db(数据库方式,建表语句请查看config/db_store.sql、config/db_undo_log.sql)
报错:
原因:路径问题,需手动指定 logs 路径
修改启动脚本:
@REM ----------------------------------------------------------------------------
@REM Copyright 2001-2006 The Apache Software Foundation.
@REM
@REM Licensed under the Apache License, Version 2.0 (the "License");
@REM you may not use this file except in compliance with the License.
@REM You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing, software
@REM distributed under the License is distributed on an "AS IS" BASIS,
@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@REM See the License for the specific language governing permissions and
@REM limitations under the License.
@REM ----------------------------------------------------------------------------
@REM
@REM Copyright (c) 2001-2006 The Apache Software Foundation. All rights
@REM reserved.
@echo off
set ERROR_CODE=0
:init
@REM Decide how to startup depending on the version of windows
@REM -- Win98ME
if NOT "%OS%"=="Windows_NT" goto Win9xArg
@REM set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" @setlocal
@REM -- 4NT shell
if "%eval[2+2]" == "4" goto 4NTArgs
@REM -- Regular WinNT shell
set CMD_LINE_ARGS=%*
goto WinNTGetScriptDir
@REM The 4NT Shell from jp software
:4NTArgs
set CMD_LINE_ARGS=%$
goto WinNTGetScriptDir
:Win9xArg
@REM Slurp the command line arguments. This loop allows for an unlimited number
@REM of arguments (up to the command line limit, anyway).
set CMD_LINE_ARGS=
:Win9xApp
if %1a==a goto Win9xGetScriptDir
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto Win9xApp
:Win9xGetScriptDir
set SAVEDIR=%CD%
%0\
cd %0\..\..
set BASEDIR=%CD%
cd %SAVEDIR%
set SAVE_DIR=
goto repoSetup
:WinNTGetScriptDir
set BASEDIR=%~dp0\..
:repoSetup
set REPO=
if "%JAVACMD%"=="" set JAVACMD=java
if "%REPO%"=="" set REPO=%BASEDIR%\lib
set CLASSPATH="%BASEDIR%"\conf;"%REPO%"\*
set ENDORSED_DIR=
if NOT "%ENDORSED_DIR%" == "" set CLASSPATH="%BASEDIR%"\%ENDORSED_DIR%\*;%CLASSPATH%
if NOT "%CLASSPATH_PREFIX%" == "" set CLASSPATH=%CLASSPATH_PREFIX%;%CLASSPATH%
@REM Reaching here means variables are defined and arguments have been captured
:endInit
%JAVACMD% %JAVA_OPTS% -server -Xmx512m -Xms512m -Xmn512m -Xss512k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\ideaFiles\seata-server-1.4.1\seata\bin\logs\java_heapdump.hprof -XX:+DisableExplicitGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 -Xloggc:D:\ideaFiles\seata-server-1.4.1\seata\bin\logs\seata_gc.log -verbose:gc -Dio.netty.leakDetectionLevel=advanced -Dlogback.color.disable-for-bat=true -classpath %CLASSPATH% -Dapp.name="seata-server" -Dapp.repo="%REPO%" -Dapp.home="%BASEDIR%" -Dbasedir="%BASEDIR%" io.seata.server.Server %CMD_LINE_ARGS%
if %ERRORLEVEL% NEQ 0 goto error
goto end
:error
if "%OS%"=="Windows_NT" @endlocal
set ERROR_CODE=%ERRORLEVEL%
:end
@REM set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" goto endNT
@REM For old DOS remove the set variables from ENV - we assume they were not set
@REM before we started - at least we don't leave any baggage around
set CMD_LINE_ARGS=
goto postExec
:endNT
@REM If error code is set to 1 then the endlocal was done already in :error.
if %ERROR_CODE% EQU 0 @endlocal
:postExec
if "%FORCE_EXIT_ON_ERROR%" == "on" (
if %ERROR_CODE% NEQ 0 exit %ERROR_CODE%
)
exit /B %ERROR_CODE%
再次启动:成功
nacos 注册中心可以查看到 Seata 服务已启动:
使用 Seata 实现事务控制
创建数据库seata
建立下面三张表(branch_table, global_table, lock_table):
-- the table to store GlobalSession data
drop table if exists `global_table`;
create table `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`)
);
-- the table to store BranchSession data
drop table if exists `branch_table`;
create table `branch_table` (
`branch_id` bigint not null,
`xid` varchar(128) not null,
`transaction_id` bigint ,
`resource_group_id` varchar(32),
`resource_id` varchar(256) ,
`lock_key` varchar(128) ,
`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`)
);
-- the table to store lock data
drop table if exists `lock_table`;
create table `lock_table` (
`row_key` varchar(128) not null,
`xid` varchar(96),
`transaction_id` long ,
`branch_id` long,
`resource_id` varchar(256) ,
`table_name` varchar(32) ,
`pk` varchar(36) ,
`gmt_create` datetime ,
`gmt_modified` datetime,
primary key(`row_key`)
);
在 dgut 数据库中加入一张 undo_log 表
这是 Seata 记录事务日志要用到的表:
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;
添加配置
在需要进行分布式控制的微服务中进行下面几项配置:
pom依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<!-- 里面已经集成服务间调用X-id的传递,包括FeignClient的重写,如果在之前自定义封装过Feign,注意启动冲突-->
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--去除低版本-->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加 seata starter ,与服务端保持一致-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
添加配置类 DataSourceProxyConfig
package com.yushanma.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
yml 配置加入 Seata
seata:
enabled: true
application-id: order-server
tx-service-group: order-server-group
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace: public
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace: public
userName: "nacos"
password: "nacos"
微服务开启全局事务 @GlobalTransactional
测试
在插入语句和异常语句设置断点:
此时,未执行插入语句,undo_log 为空:
断点往下走,此时数据库执行插入语句,但未抛出异常,undo_log 有记录:
断点往下走,0 为分母抛出异常,控制台报错:
可以看到 Seata 控制台也有相关信息:
此时执行了事务回滚,undo_log 为空:
数据插入失败,则 dgut_school_admin 表无新增记录:
可见,Seata 事务有效。