簡介
Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。
我們項目中使用AT模式,AT模式分爲兩個階段:
一階段:
業務數據和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連接資源。
二階段:
提交異步化,非常快速地完成。
回滾通過一階段的回滾日誌進行反向補償。
1. 開啓seata事務的工程中引入相關依賴
pom.xml中引入seata的jar包,1.4.2之前的版本,都不支持一個data id的方式存放所有的seata服務器配置信息,從1.4.2後支持一個配置文件的方式,所以此處排除默認引入的1.3的包,需引入1.4.2。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!-- 要與seata服務端版本一直,所以把自帶的替換掉 -->
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--使用1.4.2版本,對配置可以使用data-id一個配置文件包含其他所有的配置信息-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
關於spring cloud alibaba生態的版本參考地址:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
2. seata server服務器搭建
服務器下載地址:https://github.com/seata/seata/releases,對下載的工程進行解壓、配置。
配置的模板文件下載地址:https://github.com/seata/seata/tree/1.4.2/script。
(1)seata/conf/file.conf
此配置項爲seata 服務器的存儲配置,存儲方式選擇db,再配置數據庫的連接信息,以及處理事務的全局性表(表名使用默認的就可以)。
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## datasource = "dbcp"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
## mysql 5.xx
## driverClassName = "com.mysql.jdbc.Driver"
## mysql 8.0
driverClassName = "com.mysql.cj.jdbc.Driver"
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
url = "jdbc:mysql://ip:port/umapp_appcenter?rewriteBatchedStatements=true"
## url = "jdbc:mysql://ip:port/umapp_appcenter?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=Asia/Shanghai"
user = "appcenter"
password = "123456"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
注意點:
driverClassName驅動的配置需要根據mysql的版本決定:
mysql5.+使用 driverClassName = "com.mysql.jdbc.Driver"
mysql8使用 driverClassName = "com.mysql.cj.jdbc.Driver"
(2)seata/conf/registry.conf
需要配置選用的註冊中心類型(nacos),註冊中心的連接信息;配置中心的類型,配置中心的連接信息。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:7500"
group = "SEATA_GROUP"
namespace = "public"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:7500"
namespace = "public"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
注意點:
①當nacos開啓安全配置(在nacos的conf/application.properties中配置nacos.core.auth.enabled=true)後,對nacos的連接信息都要帶上用戶名、密碼等信息
②在seata1.4.2後纔可以使用dataId = "seataServer.properties"的方式讀取配置信息
(3)script/config-center/config.txt:
此配置信息是seata事務的相關屬性,在nacos中創建data id 時,粘貼到文本值的內容,即seataServer.properties的配置項,seata使用1.4.2版本,新建的data id文件類型選擇properties。若是使用seata1.4.2之前的版本,以下的每個配置項在nacos中就是一個條目,需要使用script/config-center/nacos/下的nacos-config.sh(linux或者windows下裝git)或者nacos-config.py(python腳本)執行上傳註冊,可以參考https://blog.csdn.net/ZHANGLIZENG/article/details博客
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
transport.serialization=seata
transport.compressor=none
# server
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.distributedLockExpireTime=10000
# store
#model改爲db
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.cj.jdbc.Driver
# 改爲上面創建的seata服務數據庫
store.db.url=jdbc:mysql://ip:port/umapp_appcenter?useUnicode=true&rewriteBatchedStatements=true
# 改爲自己的數據庫用戶名
store.db.user=appcenter
# 改爲自己的數據庫密碼
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
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
# log
log.exceptionRate=100
# metrics
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
# service
# 自己命名一個vgroupMapping
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=ip:port
service.enableDegrade=false
service.disableGlobalTransaction=false
# 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=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
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
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
注意點
①.service.vgroupMapping.my_test_tx_group=default
中的my_test_tx_group需要與bootstrap.yml中配置的seata.tx-service-group的值一致。
②.service.vgroupMapping.my_test_tx_group=default
配置的default必須要等於registry.conf中配置的cluster="default"。
③.store.mode=db配置爲db的方式,則需要配置db數據庫方式的連接信息
store.db.url、store.db.user、store.db.password,此數據庫存儲下存放的表
global_table、branch_table、lock_table,用於記錄全局性的事務信息
④.store.db.driverClassName的配置
mysql5.+使用 driverClassName = "com.mysql.jdbc.Driver"
mysql8使用 driverClassName = "com.mysql.cj.jdbc.Driver"
⑤.service.default.grouplist=ip:port爲訪問seata服務器的地址和端口(僅註冊中心爲file時使用),8091是默認端口,
也可以修改啓動端口,在啓動項目時加上端口:
seata-server.bat -p 18091
sh seata-server.sh -p 18091
⑥seata server需要配置集羣時,只需要在啓動seata server服務時指定不同的端口和節點序號即可,配置file.conf和registry.conf的內容一致,
windows下啓動:
seata-server.bat -p 18091 -n 1
seata-server.bat -p 8091 -n 2
linux下啓動:
sh seata-server.sh -p 18091 -n 1
sh seata-server.sh -p 8091 -n 2
⑦客戶端啓動時,可以看是否成功註冊到seata server服務器
3.創建需要的事務表
global_table:全局事務表
branch_table:分支信息表
lock_table:加鎖的表
以上三個表需要創建在seata服務器操作的db上,即file.conf中配置的數據庫。
--全局事務表--
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;
-- 分支表
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;
-- 鎖定表
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 = INNODB
DEFAULT CHARSET = utf8;
--seata新版本加的鎖表
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 = INNODB
DEFAULT 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);
undo_log:回滾日誌表
在每個需要開啓seata事務操作的數據庫下都需要建立此表。
--日誌文件表--
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 = INNODB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
注意點:
seata1.4.2之後,需要回滾的表日期類型不能使用datetime,可以使用timestamp
4.客戶端配置
需要開啓seata事務的客戶端,需要配置seata的註冊和配置中心,使用相關注解進行事務開啓。
(1)bootstrap.yml
向項目中添加配置信息,配置項的值與seata服務器的registry.conf中一致。
seata:
enabled: true
enable-auto-data-source-proxy: true #是否開啓數據源自動代理,默認爲true
tx-service-group: my_test_tx_group #要與配置文件中的vgroupMapping一致
registry: #registry根據seata服務端的registry配置
type: nacos #默認爲file
nacos:
application: seata-server #配置自己的seata服務
server-addr: #根據自己的seata服務配置
username: nacos #根據自己的seata服務配置
password: nacos #根據自己的seata服務配置
namespace: #根據自己的seata服務配置
cluster: default # 配置自己的seata服務cluster, 默認爲 default
group: SEATA_GROUP #根據自己的seata服務配置
config:
type: nacos #默認file,如果使用file不配置下面的nacos,直接配置seata.service
nacos:
server-addr: #配置自己的nacos地址
group: SEATA_GROUP #配置自己的dev
username: nacos #配置自己的username
password: nacos #配置自己的password
namespace: #配置自己的namespace
dataId: seataServer.properties #配置自己的dataId,由於搭建服務端時把客戶端的配置也寫在了seataServer.properties,所以這裏用了和服務端一樣的配置文件,實際客戶端和服務端的配置文件分離出來更好
(2)spring boot 啓動程序添加數據自動代理
使用註解:@EnableAutoDataSourceProxy
例子:
@EnableAutoDataSourceProxy
@EnableDiscoveryClient
@SpringBootApplication
public class UmappCloudServiceAppcenterApplication {
public static void main(String[] args) {
SpringApplication.run(UmappCloudServiceAppcenterApplication.class, args);
}
}
(3)使用註解開啓事務
@ApiOperation("添加測試數據")
@PostMapping("/addUmappTestSeata")
@GlobalTransactional
@GlobalLock
public Result<UmappTest> addUmappTestSeata(String name,Integer age,String sex) {
Result<UmappTest> result = umappTestService.addUmappTest(name,age,sex);
Result temp = seataTestFeign.addSeataTest();//添加seata測試數據
//int i = 1/0;
return result;