一.Seata介绍
1. seata是什么
是一款开源的分布式事务解决方案,供了 AT、TCC、SAGA 和 XA 事务模式。
2.分布式事务中的概念
2.1. 二阶段提交
二阶段提交的含义就是将事务的提交分成两个步骤,分别为:
准备阶段:事务协调者询问所有参与者是否准备好提交或者回滚事务,并等待它们的响应。(注意,等待响应一般会设置时间,利用超时进行事务回退是常见的一种方式)
提交阶段:如果所有参与者都准备好提交,事务协调者通知它们提交事务;如果有任何一个参与者未能准备好或者出现错误,事务协调者通知它们回滚事务.
二阶段提交中,从第一阶段的准备阶段,业务所涉及的数据就被锁定,并且锁定跨越整个提交流程。在高并发和涉及业务模块较多的情况下 对数据库的性能影响较大,但是它提供了解决分布式系统下数据一致性问题的思路
2.2.三阶段提交
三阶段提交是对两阶段提交的改进,通过在第一阶段引入额外的准备阶段来解决某些情况下的阻塞问题。在三阶段提交中,除了准备和提交阶段外,还有一个称为"预提交"或"预准备"阶段,用于减少参与者在第二阶段的等待时间
2.3.TC,TM,RM
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。对于我们使用seata来说,TC就是我们部署的seata服务端、
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。seata的AT,XA模式中,TM可以片面的理解成加了@GlobalTransactional的方法的代理类(这个例子为了方便大家理解,并不完全准确)。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。seata的AT,XA模式中,RM可以理解成datasource(这个例子为了方便大家理解,并不完全准确)。
3.XA,AT,TCC,SAGA概念
3.1. XA
Seata XA 模式是利用事务资源(数据库、消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种事务模式,简单来说,就是seata作为一个协调者,借助数据库本身的能力进行分布式事务管理。
具体工作机制参见官网:
3.2. AT
AT 模式是 Seata 的一种非侵入式的分布式事务解决方案,代码层面来讲,只需要一个注解就可以实现分布式事务管理,使用AT模式时候,我们操作数据库实际上用的是 Seata 自带的数据源代理 DataSourceProxy,Seata 在这层代理中加入了很多逻辑,比如插入回滚 undo_log 日志,检查全局锁等。简单来讲,AT将事务分成两阶段:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源
二阶段:无异常,提交异步化,非常快速地完成。有异常,滚通过一阶段的回滚日志进行补偿。
具体的读写隔离的原理参考官网:
https://seata.apache.org/zh-cn/docs/dev/mode/at-mode
3.3. TCC
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,其分布式事务模型直接作用于服务层,不依赖底层数据库。
从代码角度讲就是,TCC需要我们不仅要自己写业务代码,还要写失败的补偿代码,成功的提交代码。这样带来的好处就是,不仅能支持关系型数据库,还能支持redis,三方调用等任何方式的数据层修改。
具体执行模型见官网:
3.4.SAGA
SAGA官方说法是支持长事务的一种模式,其实简单来理解,它是流程+TCC的结合。在使用上,我们需要先定义状态机,然后对每个步骤进行编码。
其原理详见官网:
二.Seata-Server 搭建
2.1. Seata-Server下载
官方下载地址:https://github.com/seata/seata/releases
下载完成之后需要解压
2.2. Seata-Server配置
2.2.1. 需要打开conf目录
2.2.2. 配置registry.conf文件
这个文件用来管理seata服务的注册,我们修改Seata的注册中心和配置中心为Nacos
** 配置文件里一堆东西都不用管,type指定了nacos,只修改nacos的就行 **
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
# type 指定了nacos,这里我们只需要管nacos的配置就行 后面的config也是
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = ""
password = ""
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = 0
password = ""
cluster = "default"
timeout = 0
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
# type 指定了nacos,这里我们只需要管nacos的配置就行
# 注意,这里指定了一个配置文件,叫做 seataServer.properties
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = ""
password = ""
dataId = "seataServer.properties"
}
consul {
serverAddr = "127.0.0.1:8500"
aclToken = ""
}
apollo {
appId = "seata-server"
## apolloConfigService will cover apolloMeta
apolloMeta = "http://192.168.1.204:8801"
apolloConfigService = "http://192.168.1.204:8080"
namespace = "application"
apolloAccesskeySecret = ""
cluster = "seata"
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
nodePath = "/seata/seata.properties"
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
2.2.3. 修改file.conf
这个文件管理Seata的存储模式。
Server端存储模式(store.mode)支持三种:
-
file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高(默认)
-
DB:高可用模式,全局事务会话信息通过DB共享,相对性能差一些
-
redis:Seata-Server1.3及以上版本支持,性能较高,存在事务信息丢失风险,需要配合实际场景使用。
我们把Seata的默认存储模式修改为数据库"DB",同时需要配置JDBC 如下:
## transaction log store, only used in seata-server
store {
## store mode: file、db、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.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://localhost:3306/seata?useUnicode=true&characterEncoding=UTF-8"
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 {
## redis mode: single、sentinel
mode = "single"
## single mode property
single {
host = "127.0.0.1"
port = "6379"
}
## sentinel mode property
sentinel {
masterName = ""
## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381"
sentinelHosts = ""
}
password = ""
database = "0"
minConn = 1
maxConn = 10
maxTotal = 100
queryLimit = 100
}
}
数据库中,我们需要建几个seata的表
建表语句地址:https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
-
global_table : 全局事务会话表
-
branch_table: 分支事务会话表
-
lock_table: 锁数据表
也可以从我这里粘:
-- 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_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(6),
`gmt_modified` DATETIME(6),
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(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`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
2.2.4.nacos配置中心替换file.conf
本地启动可以不做这一步,seata集群,或者企业项目,一般会用nacos的配置中心而非本地file.conf文件
在1.2.2中,我们进行了nacos的配置,其中有一个config{ dataId: "xxxx" }
我们可以在配置中心的配置管理里,加一个对应的配置,来使用配置中心而非本地文件配置
内容如下:
#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 routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#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 storage configuration, only for the server. The file, DB, and redis configuration values are optional.
store.mode=db
store.lock.mode=file
store.session.mode=file
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
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
#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
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
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.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#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
每个属性的含义可以在seata官网查到,太多了这里就不一个个介绍了
2.3. 启动
启动步骤为,先启动nacos然后在启动Seata-Server
启动Seata-Server的方式非常简单,直接双击此文件即可:seata-server-1.4.2\bin\seata-server.bat
启动完成效果
然后在nacos控制台上就可以看到Seata-Server
三.微服务项目集成
3.1. 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
3.2. 配置TC(Seata-server)地址
seata:
registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
# 参考tc服务自己的registry.conf中的配置
type: nacos
nacos: # 用于在nacos中寻找tc
server-addr: 127.0.0.1:8848
namespace: ""
group: SEATA_GROUP # 分组名要入registry.conf中的配置一致
application: seata-tc-server # tc服务在nacos中的服务名称
cluster: SH # 对应TC的配置
tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
service:
vgroup-mapping: # 事务组与TC服务cluster的映射关系
seata-demo: SH
3.3. 模式配置
3.3.1. 使用XA模式
XA模式是通过数据库的支持达到事务的。使用xa模式,需要两个条件
1,数据库本身支持XA事务
2,java程序通过jdbc访问数据库
需要进行的配置如下:
step1: yml配置文件中增加配置:
seata:
data-source-proxy-mode: XA
step2:要使用事务的方法上加 @GlobalTransactional
@Service
public class BusinessService {
@GlobalTransactional
public void doBusiness() {
// 执行本地事务
// ...
// 调用远程服务
// ...
}
}
3.3.2. 使用AT模式
AT模式的本质是Seata 的 JDBC 数据源代理通过对业务 SQL 的解析,把业务数据在更新前的数据镜像组织成回滚日志,利用 本地事务的 ACID 特性,将业务数据的更新和回滚日志的写入在同一个本地事务中提交。
具体操作步骤如下:
step1: yml配置文件中增加配置:
seata:
data-source-poxy-mode: AT
step2:每个服务的数据库增加一个表:
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`)
)
step3:Service层增加注解
@Service
public class BusinessService {
@GlobalTransactional
public void doBusiness() {
// 执行本地事务
// ...
// 调用远程服务
// ...
}
}
3.3.2. 使用TCC模式
TCC是比较复杂的一个事务模式,它不依赖于底层数据资源的事务支持,且Try-Confirm-Cancel三者完全由开发者自行开发定义:
- 一阶段 prepare 行为:调用 自定义 的 Try逻辑。
- 二阶段 commit 行为:调用 自定义的 Confirm 逻辑。
- 二阶段 rollback 行为:调用 自定义的 Cancel 逻辑
当服务中涉及如下情况,则可考虑使用TCC模式:
- 不支持事务的数据库与中间件(如redis)等的操作
- AT模式暂未支持的数据库(目前AT支持Mysql、Oracle与PostgreSQL)
- 跨公司服务(第三方服务)的调用(无法共享Seata Server)
- 跨语言的应用调用
- 有手动控制整个二阶段提交过程的需求
**我实际做过的项目里,没有使用TCC的,所以这东西,了解一下就好**
具体操作步骤如下:
step1:由于TCC模式不赖数据库事务能力,所以不再需要进行data-source-poxy-mode的配置,yml文件中不需要改东西
step2:编写两个服务的service,每个service都要手动编写commit,rollback,接口声明如下
服务A的service
public interface serviceOne {
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
服务B的service
public interface serviceTwo {
@TwoPhaseBusinessAction(name = "prepare", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") String a);
public boolean commit(BusinessActionContext actionContext);
public boolean rollback(BusinessActionContext actionContext);
}
step3:编写业务主方法
@GlobalTransactional
public String doTransactionCommit(){
//服务A事务参与者
serviceOne.prepare(null,"one");
//服务B事务参与者
serviceTwo.prepare(null,"two");
}
3.3.2. 使用SAGA模式
官方介绍:Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
个人理解:saga其实类似TCC,开发者自己来维护事务的提交和失败补偿,但是saga引入了类似流程引擎的东西,通过定义流程来确定成功,失败该使用对应的什么方式来处理,sage可以视作一种TCC的增强(TCC+引擎模板)。
这个模式我没有在实际开发中用过,建议大家看这位大佬的:
springcloud+eureka整合阿里seata-saga模式_51CTO博客_spring cloud整合seata
稍微总结一下,就是
step1:定义状态机(就是一个json文件)
step2:增加三张库表,用于维护状态机状态
step3:类似于TCC模式,自己变成写事务的提交,回滚方法