一、SEATA是什么?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
在继续学习使用SEATA之前,对seata介绍中提到的分布式事务、AT、TCC、SAGA 和 XA 事务模式这些名词有必要介绍一下。
1.什么是分布式事务?
首先说下事务,事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。
事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
事务更多指的是单机版、单数据库的概念。分布式事务用于在分布式系统中保证不同节点之间的数据一致性。
2. XA规范
有了分布式事务的场景,就会有解决该问题的方式规范,XA规范就是解决分布式事务的规范。分布式事务的实现方式有很多种,最具有代表性的是由Oracle Tuxedo系统提出的 XA分布式事务协议。XA协议包括两阶段提交(2PC)和三阶段提交(3PC)两种实现。
两阶段提交(2PC)
两阶段提交又称2PC(two-phase commit protocol),2pc是一个非常经典的强一致、中心化的原子提交协议。这里所说的中心化是指协议中有两类节点:一个是中心化协调者节点(coordinator)和N个参与者节点(partcipant)。
准备阶段 事务协调者,向所有事务参与者发送事务内容,询问是否可以提交事务,并等待参与者回复。 事务参与者收到事务内容,开始执行事务操作,讲 undo 和 redo 信息记入事务日志中(但此时并不提交事务)。 如果参与者执行成功,给协调者回复yes,表示可以进行事务提交。如果执行失败,给协调者回复no,表示不可提交。
提交阶段 如果协调者收到了参与者的失败信息或超时信息,直接给所有参与者发送回滚(rollback)信息进行事务回滚,否则,发送提交(commit)信息。 参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源) 。
[图片上传失败...(image-a6b970-1618380177779)]
以下几点是XA-两阶段提交协议中会遇到的一些问题:
-
性能问题
从流程上我们可以看得出,其最大缺点就在于它的执行过程中间,节点都处于阻塞状态。各个操作数据库的节点此时都占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知进行全局提交,参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
-
协调者单点故障问题
事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,会导致参与者收不到提交或回滚的通知,从而导致参与者节点始终处于事务无法完成的中间状态。
-
丢失消息导致的数据不一致问题
在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。
2PC 方案实现起来简单,基于上面提到的两阶段提交协议中会遇到的问题,实际项目中使用的比较少。那么有没有其他的方案来解决呢?
三阶段提交(3PC)
三阶段提交是在二阶段提交上的改进版本,其在两阶段提交的基础上增加了 CanCommit阶段,并加入了超时机制。同时在协调者和参与者中都引入超时机制。三阶段将二阶段的准备阶段拆分为2个阶段,插入了一个preCommit阶段,以此来处理原先二阶段,参与者准备后,参与者发生崩溃或错误,导致参与者无法知晓是否提交或回滚的不确定状态所引起的延时问题。
阶段 1:canCommit
协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复。
参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
阶段 2:preCommit
阶段一中,如果所有的参与者都返回Yes的话,那么就会进入PreCommit阶段进行事务预提交。此时分布式事务协调者会向所有的参与者节点发送PreCommit请求,参与者收到后开始执行事务操作,并将Undo和Redo信息记录到事务日志中。参与者执行完事务操作后(此时属于未提交事务的状态),就会向协调者反馈“Ack”表示我已经准备好提交了,并等待协调者的下一步指令。如果阶段一中有任何一个参与者节点返回的结果是No响应,或者协调者在等待参与者节点反馈的过程中因挂掉而超时(2PC中只有协调者可以超时,参与者没有超时机制)。整个分布式事务就会中断,协调者就会向所有的参与者发送“abort”请求。
阶段 3:do Commit
该阶段进行真正的事务提交,在阶段二中如果所有的参与者节点都可以进行PreCommit提交,那么协调者就会从“预提交状态” 转变为 “提交状态”。然后向所有的参与者节点发送"doCommit"请求,参与者节点在收到提交请求后就会各自执行事务提交操作,并向协调者节点反馈“Ack”消息,协调者收到所有参与者的Ack消息后完成事务。
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。假如在 DoCommit 过程,参与者A无法接收协调者的通信,那么参与者A会自动提交,但是提交失败了,其他参与者成功了,此时数据就会不一致。
3. AT(Auto Transaction)模式
AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
AT 模式如何做到对业务的无侵入
一阶段
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
以update语句为例:
update user set name = 'name_1' where name = 'name_0'
首先 Seata 的 JDBC数据源代理通过对业务 SQL 解析,提取 SQL 的元数据,也就是得到 SQL 的类型(UPDATE),表(user),条件(where id= 1)等相关的信息。
提取表元数据:
select id,name from user where name = 'name_0'
将查询到的结果如上图所示保存为“before image”,执行“业务 SQL”更新业务数据SQL。根据前镜像数据主键查询出后镜像数据,查询结果为:
select id,name from user where id = 1
把业务数据在更新前后的数据镜像组织成回滚日志,将业务数据的更新和回滚日志在同一个本地事务中提交,分别插入到业务表和 UNDO_LOG
表中。
二阶段提交
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
总结
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。但AT模式存在的不足就是 当操作的数据 是共享型数据,会存在脏写的问题,所以如果是 用户独有数据可以使用AT模式。
4.TCC(Try、Confirm、Cancel)模式
TCC方案其实是两阶段提交的一种改进。分成了Try、Confirm、Cancel三个操作。事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。@TwoPhaseBusinessAction
是TCC服务参与者必须加的注解,指定服务名称,提交方法commitMethod
及回滚方法rollbackMethod
,SecondAction同理
Try部分完成业务的准备工作
confirm部分完成业务的提交
-
cancel部分完成事务的回滚
TCC 模式,不依赖于底层数据资源的事务支持:
一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。 所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。简单点概括,SEATA的TCC模式就是手工的AT模式,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log。
@LocalTCC 适用于SpringCloud+Feign模式下的TCC
@TwoPhaseBusinessAction 注解try方法,其中name为当前tcc方法的bean名称,写方法名便可(记得全局唯一),commitMethod指向提交方法,rollbackMethod指向事务回滚方法。指定好三个方法之后,seata会根据全局事务的成功或失败,去帮我们自动调用提交方法或者回滚方法。
@BusinessActionContextParameter 注解可以将参数传递到二阶段(commitMethod/rollbackMethod)的方法。
BusinessActionContext 便是指TCC事务上下文
TCC实践,总结以下注意事项:
➢ 业务模型分2阶段设计 ➢ 并发控制 ➢ 允许空回滚 ➢ 防悬挂控制 ➢ 幂等控制
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
5.SAGA模式
SAGA简介
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
如图:T1-T3都是正向的业务流程,都对应着一个冲正逆向操作C1-C3。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
Saga 模式使用场景
Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。
Saga模式的优势与缺点
优势
一阶段提交本地数据库事务,无锁,高性能;
参与者可以采用事务驱动异步执行,高吞吐
补偿服务即正向服务的“反向”,易于理解,易于实现;
缺点
Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。
注意
与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:
空补偿:逆向操作早于正向操作时;
防悬挂控制:空补偿后要拒绝正向操作
幂等
总结 AT、TCC、Saga、XA 模式分析
四种分布式事务模式,分别在不同的时间被提出,每种模式都有它的适用场景:
AT 模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。
TCC 模式是高性能分布式事务解决方案,适用于核心系统等对性能有很高要求的场景。
Saga 模式是长事务解决方案,适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁,长流程情况下可以保证性能,多用于渠道层、集成层业务系统。事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。
XA模式是分布式强一致性的解决方案,但性能低而使用较少。
二.SEATA术语
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
三.Seata整合Nacos
1.环境准备
seata软件下载地址 http://seata.io/zh-cn/blog/download.html
1.1 file.conf
本地windows下载zip解压后进入conf目录,修改file.conf:
1.2 register.conf
修改register.conf,这里是整合nacos,只保留了nacos的配置:
1.3 nacos-config.sh
接下来将配置导入到nacos中需要在conf文件夹内,新建一个nacos-config.sh文件,这个文件1.4.0版本是没有的。github下载地址:
nacos-config.sh
#!/usr/bin/env bash
# Copyright 1999-2019 Seata.io Group.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at、
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
while getopts ":h:p:g:t:u:w:" opt
do
case $opt in
h)
host=$OPTARG
;;
p)
port=$OPTARG
;;
g)
group=$OPTARG
;;
t)
tenant=$OPTARG
;;
u)
username=$OPTARG
;;
w)
password=$OPTARG
;;
?)
echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] "
exit 1
;;
esac
done
urlencode() {
for ((i=0; i < ${#1}; i++))
do
char="${1:$i:1}"
case $char in
[a-zA-Z0-9.~_-]) printf $char ;;
*) printf '%%%02X' "'$char" ;;
esac
done
}
if [[ -z ${host} ]]; then
host=localhost
fi
if [[ -z ${port} ]]; then
port=8848
fi
if [[ -z ${group} ]]; then
group="SEATA_GROUP"
fi
if [[ -z ${tenant} ]]; then
tenant=""
fi
if [[ -z ${username} ]]; then
username=""
fi
if [[ -z ${password} ]]; then
password=""
fi
nacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"
echo "set nacosAddr=$nacosAddr"
echo "set group=$group"
failCount=0
tempLog=$(mktemp -u)
function addConfig() {
curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$(urlencode $1)&group=$group&content=$(urlencode $2)&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null
if [[ -z $(cat "${tempLog}") ]]; then
echo " Please check the cluster status. "
exit 1
fi
if [[ $(cat "${tempLog}") =~ "true" ]]; then
echo "Set $1=$2 successfully "
else
echo "Set $1=$2 failure "
(( failCount++ ))
fi
}
count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do
(( count++ ))
key=${line%%=*}
value=${line#*=}
addConfig "${key}" "${value}"
done
echo "========================================================================="
echo " Complete initialization parameters, total-count:$count , failure-count:$failCount "
echo "========================================================================="
if [[ ${failCount} -eq 0 ]]; then
echo " Init nacos config finished, please start seata-server. "
else
echo " init nacos config fail. "
fi
1.4 config.txt
在conf同级目录下,需要新建config.txt文件,1.4.0版本也没有,下载地址为:
config.txt
修改config.txt数据库连接信息为自己的数据库。
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.tableMetaCheckerInterval=60000
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
store.mode=file
store.publicKey=
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.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=
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.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.maxConn=10
store.redis.minConn=1
store.redis.maxTotal=100
store.redis.database=0
store.redis.password=
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
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
上面我们下载的这两个文件,其中config.txt是seata各种详细的配置,执行 nacos-config.sh 即可将这些配置导入到nacos,这样就不需要将file.conf和registry.conf放到我们的项目中了,需要什么配置就直接从nacos中读取。
1.5 seata全属性
公共部分:
key | desc | remark |
---|---|---|
transport.serialization | client和server通信编解码方式 | seata(ByteBuf)、protobuf、kryo、hession、fst,默认seata |
transport.compressor | client和server通信数据压缩方式 | none、gzip,默认none |
transport.heartbeat | client和server通信心跳检测开关 | 默认true开启 |
registry.type | 注册中心类型 | 默认file,支持file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom |
config.type | 配置中心类型 | 默认file,支持file、nacos 、apollo、zk、consul、etcd3、custom |
server端
key | desc | remark | |
---|---|---|---|
server.undo.logSaveDays | undo保留天数 | 默认7天,log_status=1(附录3)和未正常清理的undo | |
server.undo.logDeletePeriod | undo清理线程间隔时间 | 默认86400000,单位毫秒 | |
server.maxCommitRetryTimeout | 二阶段提交重试超时时长 | 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试。公式: timeout>=now-globalTransactionBeginTime,true表示超时则不再重试 | |
server.maxRollbackRetryTimeout | 二阶段回滚重试超时时长 | 同commit | |
server.recovery.committingRetryPeriod | 二阶段提交未完成状态全局事务重试提交线程间隔时间 | 默认1000,单位毫秒 | |
server.recovery.asynCommittingRetryPeriod | 二阶段异步提交状态重试提交线程间隔时间 | 默认1000,单位毫秒 | |
server.recovery.rollbackingRetryPeriod | 二阶段回滚状态重试回滚线程间隔时间 | 默认1000,单位毫秒 | |
server.recovery.timeoutRetryPeriod | 超时状态检测重试线程间隔时间 | 默认1000,单位毫秒,检测出超时将全局事务置入回滚会话管理器 | |
store.mode | 事务会话信息存储方式 | file本地文件(不支持HA),db数据库 | redis(支持HA) |
store.file.dir | file模式文件存储文件夹名 | 默认sessionStore | |
store.db.datasource | db模式数据源类型 | dbcp、druid、hikari;无默认值,store.mode=db时必须指定。 | |
store.db.dbType | db模式数据库类型 | mysql、oracle、db2、sqlserver、sybaee、h2、sqlite、access、postgresql、oceanbase;无默认值,store.mode=db时必须指定。 | |
store.db.driverClassName | db模式数据库驱动 | store.mode=db时必须指定 | |
store.db.url | db模式数据库url | store.mode=db时必须指定,在使用mysql作为数据源时,建议在连接参数中加上rewriteBatchedStatements=true (详细原因请阅读附录7) |
|
store.db.user | db模式数据库账户 | store.mode=db时必须指定 | |
store.db.password | db模式数据库账户密码 | store.mode=db时必须指定 | |
store.db.minConn | db模式数据库初始连接数 | 默认1 | |
store.db.maxConn | db模式数据库最大连接数 | 默认20 | |
store.db.maxWait | db模式获取连接时最大等待时间 | 默认5000,单位毫秒 | |
store.db.globalTable | db模式全局事务表名 | 默认global_table | |
store.db.branchTable | db模式分支事务表名 | 默认branch_table | |
store.db.lockTable | db模式全局锁表名 | 默认lock_table | |
store.db.queryLimit | db模式查询全局事务一次的最大条数 | 默认100 | |
store.redis.host | redis模式ip | 默认127.0.0.1 | |
store.redis.port | redis模式端口 | 默认6379 | |
store.redis.maxConn | redis模式最大连接数 | 默认10 | |
store.redis.minConn | redis模式最小连接数 | 默认1 | |
store.redis.database | redis模式默认库 | 默认0 | |
store.redis.password | redis模式密码(无可不填) | 默认null | |
store.redis.queryLimit | redis模式一次查询最大条数 | 默认100 | |
metrics.enabled | 是否启用Metrics | 默认false关闭,在False状态下,所有与Metrics相关的组件将不会被初始化,使得性能损耗最低 | |
metrics.registryType | 指标注册器类型 | Metrics使用的指标注册器类型,默认为内置的compact(简易)实现,这个实现中的Meter仅使用有限内存计数,性能高足够满足大多数场景;目前只能设置一个指标注册器实现 | |
metrics.exporterList | 指标结果Measurement数据输出器列表 | 默认prometheus,多个输出器使用英文逗号分割,例如"prometheus,jmx",目前仅实现了对接prometheus的输出器 | |
metrics.exporterPrometheusPort | prometheus输出器Client端口号 | 默认9898 |
client端
key | desc | remark |
---|---|---|
seata.enabled | 是否开启spring-boot自动装配 | true、false,(SSBS)专有配置,默认true(附录4) |
seata.enableAutoDataSourceProxy=true | 是否开启数据源自动代理 | true、false,seata-spring-boot-starter(SSBS)专有配置,SSBS默认会开启数据源自动代理,可通过该配置项关闭. |
seata.useJdkProxy=false | 是否使用JDK代理作为数据源自动代理的实现方式 | true、false,(SSBS)专有配置,默认false,采用CGLIB作为数据源自动代理的实现方式 |
transport.enableClientBatchSendRequest | 客户端事务消息请求是否批量合并发送 | 默认true,false单条发送 |
client.log.exceptionRate | 日志异常输出概率 | 默认100,目前用于undo回滚失败时异常堆栈输出,百分之一的概率输出,回滚失败基本是脏数据,无需输出堆栈占用硬盘空间 |
service.vgroupMapping.my_test_tx_group | 事务群组(附录1) | my_test_tx_group为分组,配置项值为TC集群名 |
service.default.grouplist | TC服务列表(附录2) | 仅注册中心为file时使用 |
service.disableGlobalTransaction | 全局事务开关 | 默认false。false为开启,true为关闭 |
client.tm.degradeCheck | 降级开关 | 默认false。业务侧根据连续错误数自动降级不走seata事务(详细介绍请阅读附录6) |
client.tm.degradeCheckAllowTimes | 升降级达标阈值 | 默认10 |
client.tm.degradeCheckPeriod | 服务自检周期 | 默认2000,单位ms.每2秒进行一次服务自检,来决定 |
client.rm.reportSuccessEnable | 是否上报一阶段成功 | true、false,从1.1.0版本开始,默认false.true用于保持分支事务生命周期记录完整,false可提高不少性能 |
client.rm.asynCommitBufferLimit | 异步提交缓存队列长度 | 默认10000。 二阶段提交成功,RM异步清理undo队列 |
client.rm.lock.retryInterval | 校验或占用全局锁重试间隔 | 默认10,单位毫秒 |
client.rm.lock.retryTimes | 校验或占用全局锁重试次数 | 默认30 |
client.rm.lock.retryPolicyBranchRollbackOnConflict | 分支事务与其它全局回滚事务冲突时锁策略 | 默认true,优先释放本地锁让回滚成功 |
client.rm.reportRetryCount | 一阶段结果上报TC重试次数 | 默认5次 |
client.rm.tableMetaCheckEnable | 自动刷新缓存中的表结构 | 默认false |
client.tm.commitRetryCount | 一阶段全局提交结果上报TC重试次数 | 默认1次,建议大于1 |
client.tm.rollbackRetryCount | 一阶段全局回滚结果上报TC重试次数 | 默认1次,建议大于1 |
client.undo.dataValidation | 二阶段回滚镜像校验 | 默认true开启,false关闭 |
client.undo.logSerialization | undo序列化方式 | 默认jackson |
client.undo.logTable | 自定义undo表名 | 默认undo_log |
在seata的conf目录下,通过git-bash执行命令:
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata-namespace -u nacos -w nacos
-h host:nacos主机名
-p 端口号
-g group组名称,配置的分组
-t 命名空间,我这里添加的时候指定为seata-namespace,不设置默认nacos会生成uuid.
-u 用户名,需要nacos开启权限
-w 密码
执行完命令成功标志如下图所示:
在nacos页面可以看到seata的各种配置。
1.6 数据库配置
新建的数据库还有我们的业务的数据库,也需要进行相应的表的建立。这里需要两个sql文件,在1.4.0版本中也是没有的,可从0.9.0版本中获取。其中db_store.sql在seata数据库中执行,db_undo_log.sql在业务数据库(这里我新建一个test_seata库作为业务库来使用)中执行。
1.7 logs文件夹
这个时候直接启动会报没有log文件的异常,在conf同级目录下新建logs文件夹,在logs文件夹中,新建一个seat_gc.log文件。
1.8 启动seata
进入bin目录,直接双击seata-server.bat启动即可,启动成功后可在nacos服务列表查看到seata-server服务。
2.项目配置
版本信息:
springcloud alibaba version:2.1.3.RELEASE
springcloud version:Greenwich.SR2
springboot version:2.1.6.RELEASE
spring-cloud-starter-alibaba-seata version:2.1.3.RELEASE
启动nacos,seata服务。
2.1 子工程引入seata依赖
我们需要在项目中引入seata的jar包,有以下选择(任选其一):
依赖seata-all 手动配置较多
依赖seata-spring-boot-starter,支持yml配置
依赖spring-cloud-starter-alibaba-seata,内部集成了seata,并实现了xid传递
注意:client 版本与 server端版本一致
上面三种方式,需要做不同的事情,尤其xid的传递是比较麻烦的,还好spring-cloud-starter-alibaba-seata已经帮我们实现,具体看看下图:
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
io.seata
seata-spring-boot-starter
io.seata
seata-spring-boot-starter
1.4.0
2.2 数据库准备
2个微服务模拟分布式事务AT模式,提交和回滚。需要新建两个库。两个库都需要新建undo_log表.
(seata-storage)仓储服务:对给定的商品扣除仓储数量。
(seata-order)订单服务:根据采购需求创建订单。
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
(seata-order)订单表:
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
`money` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
(seata-storage)仓储表:
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of storage_tbl
-- ----------------------------
INSERT INTO `storage_tbl` VALUES (1, 'product-1', 998);
INSERT INTO `storage_tbl` VALUES (2, 'product-2', 0);
2.3 YML配置
两个服务都需要配置以下数据库链接,以及seata相关内容
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
master:
url: jdbc:mysql://127.0.01:3306/order_seata?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
username: root
password:
slave:
enabled: false
url:
username:
password:
initialSize: 5
minIdle: 10
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
allow:
url-pattern: /druid/*
login-username:
login-password:
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
redis:
host: 251.12.39.34
port: 6111
password:
timeout: 10s
seata:
enabled: true
application-id: seata-server
tx-service-group: my_test_tx_group # 事务群组(可以每个应用独立取名, 也可以使用相同的名字, 注意跟配置文件保持一致)
enable-auto-data-source-proxy: true #启动自动开启数据源代理
client:
rm:
report-retry-count: 5 # 一阶段结果上报TC充实次数(默认5)
async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000)
table-meta-check-enable: false # 自动刷新缓存中的表结构
report-success-enable: true
lock:
retry-interval: 10 # 校验或占用全局锁重试间隔(默认10ms)
retry-times: 30 # 校验或占用全局锁重试次数(默认30)
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 3 # 一阶段全局提交上报 TC 重试次数(默认 1 次, 建议大于 1)
rollback-retry-count: 3 # 一阶段全局回滚上报 TC 重试次数(默认 1 次, 建议大于 1)
undo:
data-validation: true # 二阶段回滚镜像校验(默认 true 开启)
log-serialization: jackson # undo 序列化方式(默认 jackson)
log-table: undo_log # 自定义undo表名(默认undo_log)
log:
exception-rate: 100 # 日志异常输出概率(默认 100)
support:
spring:
datasource-autoproxy: true
service:
enable-degrade: false # 降级开关
disable-global-transaction: false # 禁用全局事物(默认 false)
vgroup-mapping:
my_test_tx_group: default
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
# 这里的名字就是 registry.conf 中 nacos 的 group 名字
group: SEATA_GROUP
username: nacos
password: nacos
namespace: seata-namespace
feign:
client:
config:
default:
# 表示 feign 的请求处理超时时间
read-timeout: 10000
# 表示 feign 的连接建立超时时间
connect-timeout: 10000
只需要使用一个 @GlobalTransactional
注解在业务方法上即可实现分布式事务:
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void placeOrder(String userId, String commodityCode, Integer count) {
BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
Order order = new Order()
.setId(1)
.setUserId(userId)
.setCommodityCode(commodityCode)
.setCount(count)
.setMoney(orderMoney);
//下单:创建订单、减库存,涉及到两个服务
orderMapper.insertOrder(order);
storageFeignClient.deduct(commodityCode, count);
}
到这里简单的分布式事务demo即完成了。Seata如此之多的配置和让人摸不到头脑的专业术语到底是怎么使用的才是接下来需要学习的重点。