一、概述
Seata(Simple Extensible Autonomous Transaction Architecture 简单的可扩展自治事务架构) 是 阿里巴巴开源的分布式事务中间件,致力于提供高性能,零入侵和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata 的设计思路是将一个分布式事务可以理解成一个全局事务,下面挂了若干个分支事务,而一个分支事务是一个满足 ACID 的本地事务,因此我们可以操作分布式事务像操作本地事务一样。
二、Seata相关术语
一.TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
二.TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
三.RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
四.Transcation ID(XID)
由事务协调者创建的全局唯一的事务ID
三、Seata 工作流程
一.AT 模式工作机制(默认模式,二阶段提交,DB中实现)
两阶段提交协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
1、一阶段(拦截SQL,解析数据,保存原数据快照与更新后快照)
拦截业务SQL,根据业务SQL解析出需要修改或者提交的表的原数据,统一放入before image的快照当中。然后更新业务数据,将更新后的数据放入after image当中。此事会进行行锁,避免其他数据进行脏读。
2、二阶段
1.事务成功
因为业务SQL已经更新过了,只需要将第一阶段保存的快照数据删掉,解锁行锁即可。
2.事务回滚
事务回滚操作就比较复杂了,分为三步。
(1).校验脏读
比较after image中的数据和数据库数据,如果两份数据完全一致,则说明没有进行脏写。
(2).还原数据
将before image 中的数据你想SQL进行数据还原
(3).删除中间数据
删除before image和after image中的数据,并且解锁。
二.TCC 模式(二阶段提交,需要用户自己去实现)
TCC模式需要用户根据自己的业务实现Try,Confirm,Cancel三个操作。TCC模式侵入式比较强,AT模式无侵入,但是AT模式会有行锁,效率较差。
TCC 模型的隔离性思想就是通过业务的改造,在第一阶段结束之后,从底层数据库资源层面的加锁过渡为上层业务层面的加锁,从而释放底层数据库锁资源,放宽分布式事务锁协议,将锁的粒度降到最低,以最大限度提高业务并发性能。
1、try
预留资源
2、confirm
完成业务
3、concel
发生回滚,释放Try阶段预留的资源
三.Saga 模式
之后再补充
四、Seata配置文件
一.file.conf(持久化配置文件,db)
其他的我就删掉了
如果配置方式选择的是nacos,那么这个文件可以选择不修改。
## transaction log store, only used in seata-server 事务日志存储,仅用于 seata-server
store {
## store mode: file、db、redismode = "db" #事务日志存储模式文件、数据库、redis## rsa decryption public keypublicKey = ""## database store propertydb {
## 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" #5.0版本SQL驱动driverClassName = "com.mysql.cj.jdbc.Driver" ##8.0版本SQL驱动## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param## url = "jdbc:mysql://139.155.49.250:3306/seata?rewriteBatchedStatements=true" ##5.0版本urlurl = "jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&&serverTimezone=GMT%2B8&useInformationSchema=true" ##8.0版本urluser = "root"password = "xxxxxxx"minConn = 5maxConn = 100globalTable = "global_table" ##三张表名branchTable = "branch_table"lockTable = "lock_table"queryLimit = 100maxWait = 5000}
}
二.registry.conf(注册中心配置文件,nacos)
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype = "nacos" ##注册中心支持的模式nacos {
application = "seata-server" ##服务名称serverAddr = "119.91.99.85:8849" ##注册中心IPgroup = "SEATA_GROUP" ##分组namespace = "seata" ## 命名空间cluster = "default" ##是否集群username = "nacos" ##nacos账务密码password = "nacos"}
}
## 配置中心
config {
# file、nacos 、apollo、zk、consul、etcd3type = "nacos"#默认是去读取file.conf中的信息nacos {
serverAddr = "119.91.99.85:8849"namespace = "seata"group = "SEATA_GROUP"username = "nacos"password = "nacos"dataId = "seataServer.properties"}# file {
# name = "file.conf"# }
}
三.config.txt
注册中心的配置文件,需要用脚本把这些配置文件全部上传上去。
1、下载脚本
地址
https://github.com/seata/seata/blob/develop/script/config-center/nacos/nacos-config.sh
脚本
#!/bin/sh
# 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
docase $opt inh)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
doneif [ -z ${
host} ]; thenhost=localhost
fi
if [ -z ${
port} ]; thenport=8848
fi
if [ -z ${
group} ]; thengroup="SEATA_GROUP"
fi
if [ -z ${
tenant} ]; thentenant=""
fi
if [ -z ${
username} ]; thenusername=""
fi
if [ -z ${
password} ]; thenpassword=""
finacosAddr=$host:$port
contentType="content-type:application/json;charset=UTF-8"echo "set nacosAddr=$nacosAddr"
echo "set group=$group"urlencode() {
length="${#1}"i=0while [ $length -gt $i ]; dochar="${1:$i:1}"case $char in[a-zA-Z0-9.~_-]) printf $char ;;*) printf '%%%02X' "'$char" ;;esaci=`expr $i + 1`done
}failCount=0
tempLog=$(mktemp -u)
function addConfig() {
dataId=`urlencode $1`content=`urlencode $2`curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$dataId&group=$group&content=$content&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/nullif [ -z $(cat "${tempLog}") ]; thenecho " Please check the cluster status. "exit 1fiif [ "$(cat "${
tempLog}")" == "true" ]; thenecho "Set $1=$2 successfully "elseecho "Set $1=$2 failure "failCount=`expr $failCount + 1`fi
}count=0
for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); docount=`expr $count + 1`key=${
line%%=*}value=${
line#*=}addConfig "${key}" "${value}"
doneecho "========================================================================="
echo " Complete initialization parameters, total-count:$count , failure-count:$failCount "
echo "========================================================================="if [ ${
failCount} -eq 0 ]; thenecho " Init nacos config finished, please start seata-server. "
elseecho " init nacos config fail. "
fi
2、执行脚本
sh nacos-config.sh -h ip -t namespace
执行脚本要注意,需要把下载的脚本放到配置文件的下级目录里再去执行。
官网给的配置文件里面有一些参数是空的,如果你不给那些参数填值,执行脚本会报错的,如果用不上那个参数,报错就报错吧。
3、配置文件
## Netty配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=true
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
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.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
## store
store.mode=db
store.lock.mode=file
store.session.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://139.155.49.250:3306/seata?useSSL=false&&serverTimezone=GMT%2B8&useInformationSchema=true
store.db.user=username
store.db.password=password
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
store.redis.mode=single
store.redis.single.host=127.0.0.1
store.redis.single.port=6379
store.redis.sentinel.masterName=
store.redis.sentinel.sentinelHosts=
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
server.distributedLockExpireTime=10000
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
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
三.服务端SQL脚本(1.4.2版本)
源链接
https://github.com/seata/seata/tree/develop/script/server
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 = InnoDBDEFAULT 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 = InnoDBDEFAULT 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),`gmt_create` DATETIME,`gmt_modified` DATETIME,PRIMARY KEY (`row_key`),KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;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 = InnoDBDEFAULT 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);
四.客户端SQL脚本(1.4.2版本)
客户端SQL脚本根据不同的模式有不同的SQL脚本
https://github.com/seata/seata/tree/develop/script/client
1、AT模式
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
2、TCC模式
-- -------------------------------- The script use tcc fence --------------------------------
CREATE TABLE IF NOT EXISTS `tcc_fence_log`
(`xid` VARCHAR(128) NOT NULL COMMENT 'global id',`branch_id` BIGINT NOT NULL COMMENT 'branch id',`action_name` VARCHAR(64) NOT NULL COMMENT 'action name',`status` TINYINT NOT NULL COMMENT 'status(tried:1;committed:2;rollbacked:3;suspended:4)',`gmt_create` DATETIME(3) NOT NULL COMMENT 'create time',`gmt_modified` DATETIME(3) NOT NULL COMMENT 'update time',PRIMARY KEY (`xid`, `branch_id`),KEY `idx_gmt_modified` (`gmt_modified`),KEY `idx_status` (`status`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
3、SAGA模式
-- -------------------------------- The script used for sage --------------------------------CREATE TABLE IF NOT EXISTS `seata_state_machine_def`
(`id` VARCHAR(32) NOT NULL COMMENT 'id',`name` VARCHAR(128) NOT NULL COMMENT 'name',`tenant_id` VARCHAR(32) NOT NULL COMMENT 'tenant id',`app_name` VARCHAR(32) NOT NULL COMMENT 'application name',`type` VARCHAR(20) COMMENT 'state language type',`comment_` VARCHAR(255) COMMENT 'comment',`ver` VARCHAR(16) NOT NULL COMMENT 'version',`gmt_create` DATETIME(3) NOT NULL COMMENT 'create time',`status` VARCHAR(2) NOT NULL COMMENT 'status(AC:active|IN:inactive)',`content` TEXT COMMENT 'content',`recover_strategy` VARCHAR(16) COMMENT 'transaction recover strategy(compensate|retry)',PRIMARY KEY (`id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;CREATE TABLE IF NOT EXISTS `seata_state_machine_inst`
(`id` VARCHAR(128) NOT NULL COMMENT 'id',`machine_id` VARCHAR(32) NOT NULL COMMENT 'state machine definition id',`tenant_id` VARCHAR(32) NOT NULL COMMENT 'tenant id',`parent_id` VARCHAR(128) COMMENT 'parent id',`gmt_started` DATETIME(3) NOT NULL COMMENT 'start time',`business_key` VARCHAR(48) COMMENT 'business key',`start_params` TEXT COMMENT 'start parameters',`gmt_end` DATETIME(3) COMMENT 'end time',`excep` BLOB COMMENT 'exception',`end_params` TEXT COMMENT 'end parameters',`status` VARCHAR(2) COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`compensation_status` VARCHAR(2) COMMENT 'compensation status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`is_running` TINYINT(1) COMMENT 'is running(0 no|1 yes)',`gmt_updated` DATETIME(3) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `unikey_buz_tenant` (`business_key`, `tenant_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;CREATE TABLE IF NOT EXISTS `seata_state_inst`
(`id` VARCHAR(48) NOT NULL COMMENT 'id',`machine_inst_id` VARCHAR(128) NOT NULL COMMENT 'state machine instance id',`name` VARCHAR(128) NOT NULL COMMENT 'state name',`type` VARCHAR(20) COMMENT 'state type',`service_name` VARCHAR(128) COMMENT 'service name',`service_method` VARCHAR(128) COMMENT 'method name',`service_type` VARCHAR(16) COMMENT 'service type',`business_key` VARCHAR(48) COMMENT 'business key',`state_id_compensated_for` VARCHAR(50) COMMENT 'state compensated for',`state_id_retried_for` VARCHAR(50) COMMENT 'state retried for',`gmt_started` DATETIME(3) NOT NULL COMMENT 'start time',`is_for_update` TINYINT(1) COMMENT 'is service for update',`input_params` TEXT COMMENT 'input parameters',`output_params` TEXT COMMENT 'output parameters',`status` VARCHAR(2) NOT NULL COMMENT 'status(SU succeed|FA failed|UN unknown|SK skipped|RU running)',`excep` BLOB COMMENT 'exception',`gmt_updated` DATETIME(3) COMMENT 'update time',`gmt_end` DATETIME(3) COMMENT 'end time',PRIMARY KEY (`id`, `machine_inst_id`)
) ENGINE = InnoDBDEFAULT CHARSET = utf8;