利用Nacos實現(xiàn)Seata事務模式(XA與AT)的快速配置與靈活切換
前言
接之前一篇《Seata如何實現(xiàn)兩階段提交(2PC)分布式事務》,實際開發(fā)中seata基本不會用file存儲和管理服務節(jié)點信息,下面小編將結合nacos來整合seata,實現(xiàn)XA和AT模式的靈活轉換。
實現(xiàn)
相關安裝包可以自行前往官網(wǎng)下載:
- nacos:https://github.com/alibaba/nacos/releases 版本2.1.0
- seata:https://github.com/seata/seata/releases 版本1.4.2
部署及配置seata
registry.conf:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"
nacos {
application = "seata-tc-server"
serverAddr = "127.0.0.1:8848"
group = "DEFAULT_GROUP"
namespace = ""
cluster = "BJ"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
seataServer.properties:
特別注意,為了讓TC服務的集群可以共享配置,同樣選擇nacos作為統(tǒng)一配置中心。
# 數(shù)據(jù)存儲方式,db代表數(shù)據(jù)庫
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://localhost:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root
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
# 事務、日志等配置
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.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客戶端與服務端傳輸方式
transport.serialization=seata
transport.compressor=none
# 關閉metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
sql腳本:
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
以上是XA模式所需腳本,AT模式追加以下腳本:
CREATE TABLE `lock_table` (
`row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`row_key`) USING BTREE,
INDEX `idx_branch_id`(`branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
其中l(wèi)ock_table導入到TC服務關聯(lián)的數(shù)據(jù)庫,undo_log表導入到微服務關聯(lián)的數(shù)據(jù)庫。
啟動seata:
圖片
XA模式
XA 規(guī)范 是 X/Open 組織定義的分布式事務處理(DTP,Distributed Transaction Processing)標準,XA 規(guī)范描述了全局的TM與局部的RM之間的接口,幾乎所有主流的數(shù)據(jù)庫都對 XA 規(guī)范提供了支持。
兩階段提交
XA是規(guī)范,目前主流數(shù)據(jù)庫都實現(xiàn)了這種規(guī)范,實現(xiàn)的原理都是基于兩階段提交。
- 正常情況:
圖片
- 異常情況:
圖片
一階段(準備階段):
- 協(xié)調發(fā)起:
事務協(xié)調者向所有參與此次分布式事務的本地事務參與者發(fā)出指令,要求它們開始執(zhí)行本地事務操作。
- 本地執(zhí)行與狀態(tài)反饋:
- 各個本地事務參與者接收到請求后,在各自的數(shù)據(jù)庫上啟動并執(zhí)行本地事務。
- 在完成本地事務操作后,各參與者并不立即提交事務,而是將各自的事務執(zhí)行狀態(tài)(成功或失敗)報告給事務協(xié)調者,并在此期間繼續(xù)保持對相關數(shù)據(jù)資源的鎖定以防止并發(fā)沖突。
二階段(提交/回滾階段):
- 全局提交決定:
- 如果事務協(xié)調者收到的所有參與者的反饋均表示本地事務已成功執(zhí)行,則向所有參與者發(fā)送“全局提交”指令。
- 全局提交執(zhí)行:
接收到“全局提交”指令的參與者將在本地正式提交其事務,釋放之前持有的數(shù)據(jù)庫鎖,并確保事務操作永久記錄于數(shù)據(jù)庫。
- 全局回滾決定與執(zhí)行:
- 若事務協(xié)調者接收到任何一個參與者反饋表明本地事務執(zhí)行失敗,或者出現(xiàn)其他異常情況,則會做出全局回滾的決定。
- 協(xié)調者隨即通知所有參與者進行“全局回滾”,每個參與者在收到此指令后,必須在其本地環(huán)境中撤銷已經(jīng)執(zhí)行但尚未提交的事務操作,釋放鎖定資源,并恢復至事務開始前的數(shù)據(jù)狀態(tài)。
Seata的XA模型
圖片
在分布式事務處理中,RM和TC按照兩階段提交協(xié)議執(zhí)行各自的工作:
RM在一階段的工作:
- 分支事務注冊:
- RM將參與的分支事務信息注冊到TC,為后續(xù)協(xié)調全局事務做好準備。
- 本地事務操作:
RM在數(shù)據(jù)庫或其他資源上執(zhí)行與分支事務相關的SQL操作,但并不提交這些變更,而是保持數(shù)據(jù)鎖定狀態(tài)以防止數(shù)據(jù)不一致。
- 狀態(tài)匯報:
完成本地操作后,RM向TC報告其分支事務的執(zhí)行狀態(tài),包括成功或失敗的信息。
TC在二階段的工作:
- 狀態(tài)檢測與決策制定:
若所有分支事務均執(zhí)行成功,TC通知所有RM提交各自的事務。
若存在任何一個分支事務執(zhí)行失敗,TC則指示所有RM回滾各自的事務,以保證分布式事務的一致性。
RM在二階段的工作:
- 接收指令并執(zhí)行:
如果是提交指令,則正式提交一階段執(zhí)行的SQL操作,并釋放之前持有的鎖。
如果是回滾指令,則撤銷一階段未提交的SQL操作,恢復至事務開始前的數(shù)據(jù)狀態(tài)。
代碼樣例
依賴引入:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.3.RELEASE</version>
<exclusions>
<!--版本較低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
配置引入:
server:
port: 8011
spring:
application:
name: bank-11
datasource:
url: jdbc:mysql://localhost:3306/bank1?characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
cloud:
nacos:
server-addr: 127.0.0.1:8848
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-tc-server # tc服務在nacos中的服務名稱
username: nacos
password: nacos
tx-service-group: order_tx_group #自定義事務組名稱需要與seata-server中的對應
data-source-proxy-mode: XA
service:
vgroup-mapping:
order_tx_group: BJ # TC 集群(必須與seata-server保持一致)
bank22服務參考bank11配置即可,bank11調用bank22服務
@GlobalTransactional
@Override
public void updateAccountBalance(String accountNo, Double amount) {
log.info("******** Bank1 Service Begin ... xid: {}" , RootContext.getXID());
//張三扣減金額
baseMapper.updateAccountBalance(accountNo,amount);
//向李四轉賬
String transfer = bank2Client.transfer(amount);
if("fallback".equals(transfer)){
//調用李四微服務異常
throw new RuntimeException("調用李四微服務異常");
}
//人為制造錯誤
if(amount > 100){
throw new RuntimeException("bank1 make exception amount > 100");
}
}
XA模式的優(yōu)點:
- 事務的強一致性,滿足ACID原則。
- 常用數(shù)據(jù)庫都支持,實現(xiàn)簡單,并且沒有代碼侵入
XA模式的缺點:
- 因為一階段需要鎖定數(shù)據(jù)庫資源,等待二階段結束才釋放,性能較差
- 依賴關系型數(shù)據(jù)庫實現(xiàn)事務
AT模式
AT模式同樣是分階段提交的事務模型,不過卻彌補了XA模型中資源鎖定周期過長的缺陷。
Seata的AT模型
階段一(準備階段)RM的工作:
- 注冊分支事務:
當全局事務開始后,RM會為該事務涉及的本地事務創(chuàng)建并注冊一個分支事務,將其納入到全局事務的管理范圍。
- 記錄undo-log(數(shù)據(jù)快照):
在執(zhí)行業(yè)務SQL之前,RM會對當前事務要修改的數(shù)據(jù)進行前鏡像的記錄,即生成undo-log。這樣在事務回滾時可以根據(jù)undo-log恢復到事務開始前的狀態(tài)。
- 執(zhí)行業(yè)務sql并提交:
RM按照業(yè)務需求執(zhí)行相應的SQL操作,并在本地將事務置為“預提交”狀態(tài),但不真正完成提交。
- 報告事務狀態(tài):
將分支事務的執(zhí)行結果(成功或失敗)匯報給協(xié)調者,等待協(xié)調者的下一步指令。
階段二(提交階段/回滾階段)RM的工作:
- 提交階段:
如果協(xié)調者收到所有RM的反饋均為“準備成功”,則指示RM正式提交事務。此時,RM刪除對應的undo-log,因為事務已成功提交,不再需要回滾信息。
- 回滾階段:
若協(xié)調者決定全局事務需要回滾,RM會根據(jù)先前記錄的undo-log恢復數(shù)據(jù)到事務開始前的狀態(tài),撤銷在事務過程中對數(shù)據(jù)的所有更改,確保數(shù)據(jù)一致性。
代碼樣例
修改配置:
seata:
data-source-proxy-mode: AT # 默認就是AT
AT模式的優(yōu)點:
- 一階段完成直接提交事務,釋放數(shù)據(jù)庫資源,性能比較好
- 利用全局鎖實現(xiàn)讀寫隔離
- 沒有代碼侵入,框架自動完成回滾和提交
AT模式的缺點:
- 兩階段之間屬于軟狀態(tài),屬于最終一致
- 框架的快照功能會影響性能,但比XA模式要好很多