SEATA 配置
使用 nacos 做為配置中心配置 SEATA
當前 SEATA 版本: 1.4.1
TC (Transaction Coordinator) - 事務協調者
維護全域性和分支事務的狀態,驅動全域性事務提交或回滾。
配置引數
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.app-server-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=url
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.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=10
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
client.log.exceptionRate=100
transport.serialization=seata
transport.compressor=none
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
nacos bash 指令碼
nacos-config.sh
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 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
同步 config 配置到 nacos
進入 TC 伺服器
-
新建資料夾 seata-config
-
進入 seata-config
-
新建 config.txt 檔案並複製配置引數到 config.txt 檔案中
-
新建 nacos-config.sh 檔案,同時複製 nacos bash 指令碼到 nacos-config.sh 中
-
使用以下命令同步配置引數到 nacos
bash nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -t 3a2aea46-07c6-4e21-9a1e-8946cde9e2b3 -u nacos -w nacos
得到輸出
set nacosAddr=127.0.0.1:8848 set group=SEATA_GROUP Set transport.type=TCP successfully Set transport.server=NIO successfully . . . ========================================================================= Complete initialization parameters, total-count:80 , failure-count:0 ========================================================================= Init nacos config finished, please start seata-server.
使用 docker 部署 SEATA
Docker 部署 SEATA 官方文件
進入 TC 伺服器,並進入 seat-config 資料夾
-
新建 registry.conf 檔案,並新增以下內容,registry 配置參考
registry.conf
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { application = "seata-server" group = "SEATA_GROUP" serverAddr = "127.0.0.1" namespace = "3a2aea46-07c6-4e21-9a1e-8946cde9e2b3" cluster = "default" } } config { # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig type = "nacos" nacos { serverAddr = "127.0.0.1" namespace = "3a2aea46-07c6-4e21-9a1e-8946cde9e2b3" group = "SEATA_GROUP" username = "nacos" password = "nacos" } }
-
新建 file.conf 檔案並新增以下內容(可選,可通過 nacos 讀取)
file.conf
transport { # tcp udt unix-domain-socket type = "TCP" #NIO NATIVE server = "NIO" #enable heartbeat heartbeat = true # the client batch send request enable enableClientBatchSendRequest = true #thread factory for netty threadFactory { bossThreadPrefix = "NettyBoss" workerThreadPrefix = "NettyServerNIOWorker" serverExecutorThread-prefix = "NettyServerBizHandler" shareBossWorker = false clientSelectorThreadPrefix = "NettyClientSelector" clientSelectorThreadSize = 1 clientWorkerThreadPrefix = "NettyClientWorkerThread" # netty boss thread size,will not be used for UDT bossThreadSize = 1 #auto default pin or 8 workerThreadSize = "default" } shutdown { # when destroy server, wait seconds wait = 3 } serialization = "seata" compressor = "none" } service { #transaction service group mapping vgroupMapping.my_test_tx_group = "default" #only support when registry.type=file, please don't set multiple addresses default.grouplist = "127.0.0.1:8091" #degrade, current not support enableDegrade = false #disable seata disableGlobalTransaction = false } client { rm { asyncCommitBufferLimit = 10000 lock { retryInterval = 10 retryTimes = 30 retryPolicyBranchRollbackOnConflict = true } reportRetryCount = 5 tableMetaCheckEnable = false reportSuccessEnable = false } tm { commitRetryCount = 5 rollbackRetryCount = 5 } undo { dataValidation = true logSerialization = "jackson" logTable = "undo_log" } log { exceptionRate = 100 } }
-
執行 docker 命令
- 注意: 當在 config.txt 中配置 store.mode=db 時,需要在配置的資料庫連線中初始化表
global_table
、branch_table
、lock_table
,sql 傳送門。
docker run -d --name seata-server \ --net=host \ -p 8091:8091 \ -e SEATA_CONFIG_NAME=file:/root/seata-config/registry \ -v /root/seata-config:/root/seata-config \ seataio/seata-server:1.4.1
掛載目錄為 TC 伺服器配置目錄。
- 注意: 當在 config.txt 中配置 store.mode=db 時,需要在配置的資料庫連線中初始化表
TM (Transaction Manager) - 事務管理器
定義全域性事務的範圍:開始全域性事務、提交或回滾全域性事務。
例子:業務聚合服務
-
SEATA 包引入。pom 配置如下,當使用
spring-cloud-starter-openfeign
包時,需要移除spring-cloud-starter-openfeign
包,spring-cloud-starter-alibaba-seata
中已經包含了spring-cloud-starter-openfeign
,再次引入可能導致包衝突。pom.xml
<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-seata</artifactId> <version>2.2.1.RELEASE</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> </exclusion> </exclusions> </dependency>
-
新增 registry.conf。在工程中 resource 目錄下新增如下內容
registry.conf
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" namespace = "3a2aea46-07c6-4e21-9a1e-8946cde9e2b3" cluster = "default" username = "nacos" password = "nacos" } } config { # file、nacos 、apollo、zk、consul、etcd3、springCloudConfig type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "3a2aea46-07c6-4e21-9a1e-8946cde9e2b3" group = "SEATA_GROUP" username = "nacos" password = "nacos" } }
-
配置 bootstrap.properties,新增配置,內容如下,
seata.tx-service-group
和namespace
改為對應的值bootstrap.properties
... seata.tx-service-group=app-server-tx-group seata.config.type=nacos seata.config.nacos.server-addr=127.0.0.1:8848 seata.config.nacos.namespace=3a2aea46-07c6-4e21-9a1e-8946cde9e2b3 seata.config.nacos.group=SEATA_GROUP
在 TM 中通過 @GlobalTransactional
開啟全域性異常,示例程式碼:
@GlobalTransactional
@GetMapping({"create"})
public String create(String name,Integer age) {
...
return "建立成功";
}
RM (Resource Manager) - 資源管理器
管理分支事務處理的資源,與TC交談以註冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。
例子:被呼叫服務。
-
配置 bootstrap.properties,新增配置,內容如下,
seata.tx-service-group
和namespace
改為對應的值bootstrap.properties
... seata.tx-service-group=app-server-tx-group seata.config.type=nacos seata.config.nacos.server-addr=127.0.0.1:8848 seata.config.nacos.namespace=3a2aea46-07c6-4e21-9a1e-8946cde9e2b3 seata.config.nacos.group=SEATA_GROUP
-
對需要做回滾的業務標記
@Transactional(rollbackFor = Exception.class)
-
如果配置了全域性異常處理,使用 SEATA API 發起事務回滾
@ExceptionHandler(value = Exception.class) @ResponseBody public String exceptionHandler(Exception e) { ... try { String xid = RootContext.getXID(); if (StringUtils.isNotEmpty(xid)) { GlobalTransactionContext.reload(RootContext.getXID()).rollback(); } } catch (TransactionException transactionException) { transactionException.printStackTrace(); log.error("===TransactionException==={}", transactionException.getMessage()); } return e.getMessage(); }
或者通過 AOP 全域性處理回滾
/** * @author Zhang_Xiang * @since 2021/2/22 17:36:16 */ @Aspect @Component @Slf4j public class TxAspect { @Pointcut("execution(public * *(..))") public void publicMethod() { } @Pointcut("within(com.*.service.impl..*)") private void services() { } @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)") private void transactional() { } @Pointcut("within(com.*.webapi.controller..*)") private void actions() { } @Pointcut("@annotation(org.springframework.web.bind.annotation.ExceptionHandler))") private void validatedException() { } @Before(value = "validatedException()") public void beforeValidate(JoinPoint joinPoint) throws TransactionException { Object[] args = joinPoint.getArgs(); if (args == null || args.length == 0) { return; } Exception e = (Exception) args[0]; if (e instanceof MethodArgumentNotValidException || e instanceof BindException || e instanceof ConstraintViolationException) { globalRollback(); } } @AfterThrowing(throwing = "e", pointcut = "publicMethod()&&services()&&transactional()") public void doRecoveryMethods(Throwable e) throws TransactionException { log.info("===method throw===:{}", e.getMessage()); globalRollback(); } @AfterReturning(value = "publicMethod()&&actions()", returning = "result") public void afterReturning(RestResponse<?> result) throws TransactionException { log.info("===method finished===:{}", result); if (result.isFail()) { globalRollback(); } } //region private methods private void globalRollback() throws TransactionException { if (!StringUtils.isBlank(RootContext.getXID())) { log.info("===xid===:{}", RootContext.getXID()); GlobalTransactionContext.reload(RootContext.getXID()).rollback(); } } //endregion }
-
AT(Automatic Transaction) 模式下配置 undo_log 資料庫表,mysql 建表 sql 如下
CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(100) 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 = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
關閉 SEATA
seata.enabled=false