生命周期
TC(Transaction Coordinator):事务协调者
维护全局和分支事务状态,驱动全局事务提交或回滚
TM(Transaction Manager): 事务管理器
定义全局事务的范围,开始全局事务、提交或回滚全局事务
RM(Resource Manager):资源管理器
管理分支事务处理的资源,与TC交谈注册分支事务和报告分支事务状态,并驱动分支事务提交或回滚
其中,TC为单独部署的Server,TM和RM为嵌入到应用中的Client
1.TM请求TC开启一个全局事务。TC会生成一个XID作为该全局事务的编号。XID会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起,
2.RM请求TC将本地事务注册为全局事务的分支事务,通过全局事务的 XID进行关联。
3.TM请求 TC告诉 XID 对应的全局事务是进行提交还是回滚
4.TC动RM们将 XID对应的自己的本地事务进行提交还是回滚
第一阶段
第二阶段成功
第二阶段回滚
分布式事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新SQL井执行,以完成分支的回滚
Seata使用
Seata Server搭建(docker)
有三种持久化模式:
- file:(默认)单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高
- db(5.7+,包含):高可用模式,全局事务会话信息通过db共享,相应性能差些
- redisT Seata-Server 1.3及以上版本支持,性能较高,存在事务信息委失风险,请提前配置适合当前场景的redis持久化配置
我要搭建一个db模式并且集成Nacos的注册和配置功能,这样在集群中可以进行负载和统一配置,Nacos搭建略…… - 先启动一个容器拷贝点文件
docker run --name seata-server -p 8091:8091 -d seataio/seata-server:1.3.0
docker cp seata-server:/seata-server E:/docker/volumes
拷贝完就可以把这个容器删了,目录结构如下
- 修改E:\docker\volumes\seata-server\resources下的file.conf
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## 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"
url = "jdbc:mysql://192.168.2.115:3306/seata"
user = "root"
password = "1"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
mode 修改为 db,下面的配置修改为自己的数据库,注意我的mysql装在宿主机上所以写的是内网IP,因为要让容器访问到。
这里是将Seata的数据持久化到数据库用于多个Seata节点共享数据,多余的配置我都删除了
3. 创建数据库seata并初始化表结构,给出一份sql脚本:
-- 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`)
);
- 修改E:\docker\volumes\seata\resources下的registry.conf
registry {
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "192.168.2.115:8848"
group = "SEATA_GROUP"
namespace = "seata-server"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.2.115:8848"
namespace = "seata-server"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
type改成nacos,下面的配置改对,这里注意我的nacos也是在本地所以也写的宿主机的IP,namespace写Nacos的namespace,我是自己创建了个,不写的话默认是public
现在seata就能注册进Nacos并且能使用配置中心了
- 将本地配置推送到Nacos
这步比较容易出错,需要将本地的一些配置推送到Nacos中
在registry.conf同级目录下新建:config.txt,这个文件可以在seata源码中找到,我是删除了一些不相关的配置
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.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=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.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=root
store.db.password=1
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
需要修改
store.mode
store.db.url
store.db.user
store.db.password
注意:service.vgroupMapping这个配置,.后面的my_test_tx_group是可以自定义的,这个配置意思是作为事务分组来标明机房组,比如实际生产中会做异地多活,这个值作为不同区域机房组。可以将同一个机房的多个实例配置成一个组。
service.vgroupMapping.my_test_tx_group的值必须与registry.conf文件中的cluster值一样
目录下新建一个sh文件夹并且新建一个文件:nacos-config.sh,需要执行这个脚本将Seata初始化配置推送到Nacos,给出脚本内容,别改动:
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
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=$1&group=$group&content=$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
在windows下怎么执行sh脚本呢?在脚本同级目录下右键 - Git Bash Here (需要安装Git,相信大家都有),输入命令
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t seata-server -u nacos -w nacos
就能推送上去
=========================================================================
Complete initialization parameters, total-count:80 , failure-count:0
=========================================================================
Init nacos config finished, please start seata-server.
这里有个问题,如果下载的官方的config.txt没有进行改动时,官方配置有一些key值是空,这些配置会导入失败,目测应该不影响功能
启动Seate
docker run -d --name seata-server -p 8091:8091 -v E:/docker/volumes/seata:/seata-server seataio/seata-server:1.3.0
查看容器日志:
2023-02-17 23:49:35 2023-02-17 15:49:35.289 INFO --- [ main] io.seata.server.Server : The server is running in container.
2023-02-17 23:49:35 2023-02-17 15:49:35.564 INFO --- [ main] io.seata.config.FileConfiguration : The configuration file used is registry.conf
2023-02-17 23:49:39 2023-02-17 15:49:39.343 INFO --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2023-02-17 23:49:40 2023-02-17 15:49:40.436 INFO --- [ main] i.s.core.rpc.netty.NettyServerBootstrap : Server started, listen port: 8091
服务已经注册进来了