前言 在分散式的微服務架構中,鑒於服務單一職責性,各個微服務都分佈在不同的伺服器節點,且每1個微服務是獨立的; 在後端每個微服務都是分散和獨立的,可能使用不同編程語言,使用不同的資料庫,通過RPC調用完成前端用戶發送的請求(任務); 假設1個用戶在1個分散式微服務架構的電商網站購物,購買了1件商品點 ...
前言
在分散式的微服務架構中,鑒於服務單一職責性,各個微服務都分佈在不同的伺服器節點,且每1個微服務是獨立的;
在後端每個微服務都是分散和獨立的,可能使用不同編程語言,使用不同的資料庫,通過RPC調用完成前端用戶發送的請求(任務);
假設1個用戶在1個分散式微服務架構的電商網站購物,購買了1件商品點擊了下單,後臺需要一組微服務協作完成下單操作;
即:訂單服務創建訂單--->庫存服務減少庫存---->用戶服務減少餘額;
在這3個環節執行過程中,如果其中1個微服務不可用,都無法滿足當前用戶下單的需求;
那如何保證用戶的1次操作具備資料庫事務的ACID特性呢?
這就需要分散式事務;
一、CAP理論
2000年加州理工大學伯克利分校的EricBrewer教授提出在分散式架構中應當具備3個理想的指標
- Consistency(一致性)
- Availability(可用性)
指服務處在100%可用的狀態,用戶訪問分散式集群中任意1個服務 ,即使該服務故障,用戶依然能得到響應,而不是超時或拒絕。
3.分區容錯
在分散式微架構中,如果想要實現一致性就需要在2個節點之間通過網路同步數據;
假設G1和G2兩個節點要想實現數據一致性,就需要通過網路傳輸同步數據,但網路傳輸數據是需要時間的;
忠和孝無法兩全,如果G1更新了數據,在G1和G2節點通過網路傳輸同步數據的這1段時間里,G2節點是對外提供查詢服務呢?還是不提供服務呢?
- G2提供服務就不滿足一致性指標;
- G2阻塞用戶請求,不提供服務就無法滿足100%可用性指標;
以上是CAP理論存在的問題;
2.BASE理論
BASE理論是針對CAP理論存問題的1種解決方案,包含3個思想:
2.1.Basically Available (基本可用)
分散式系統在出現故障時,允許損失部分可用性,即保證核心可用。
2.2.Soft State(軟狀態)
在一定時間內,允許出現中間狀態,比如臨時的不一致狀態。
2.3.Eventually Consistent(最終一致性)
-
AP模式:各子事務分別執行和提交,允許出現結果不一致,然後採用彌補措施恢複數據即可,實現最終一致。
-
官網地址:http://seata.io/
-
TC (Transaction Coordinator) - 事務協調者:搭建好的Seata伺服器,所有的RM和TM都要想TC上報,當前分支事務執行的狀態,以便於TC作出全局事務的回滾/提交操作(CEO);
-
TM (Transaction Manager) - 事務管理器:代表整個業務邏輯,決定全局事務的範圍和邊界,從哪個環節開始全局事務?在哪個環節提交或回滾全局事務?從哪個環節結束全局事務?(經理)
-
RM (Resource Manager) - 資源管理器:具體執行分支事務的微服務
-
XA模式:強一致性分階段事務模式,犧牲了一定的可用性,無業務侵入
-
TCC模式:最終一致的分階段事務模式,有業務侵入
-
AT模式:最終一致的分階段事務模式,無業務侵入,也是Seata的預設模式
-
SAGA模式:長事務模式,有業務侵入
2.安裝Seata
2.1.Seata配置文件
使Seata服務註冊到Nacos中,並指定讀取Nacos中的配置文件;
registry { # tc服務的註冊中心類,這裡選擇nacos,也可以是eureka、zookeeper等 type = "nacos" nacos { # seata tc 服務註冊到 nacos的服務名稱,可以自定義 application = "seata-tc-server" serverAddr = "127.0.0.1:8848" group = "DEFAULT_GROUP" namespace = "" cluster = "SH" username = "nacos" password = "nacos" } } config { # 讀取tc服務端的配置文件的方式,這裡是從nacos配置中心讀取,這樣如果tc是集群,可以共用配置 type = "nacos" # 配置nacos地址等信息 nacos { serverAddr = "127.0.0.1:8848" namespace = "" group = "DEFAULT_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }registry.conf
2.2.
配置內容如下
registry { # tc服務的註冊中心類,這裡選擇nacos,也可以是eureka、zookeeper等 type = "nacos" nacos { # seata tc 服務註冊到 nacos的服務名稱,可以自定義 application = "seata-tc-server" serverAddr = "127.0.0.1:8848" group = "DEFAULT_GROUP" namespace = "" cluster = "SH" username = "nacos" password = "nacos" } } config { # 讀取tc服務端的配置文件的方式,這裡是從nacos配置中心讀取,這樣如果tc是集群,可以共用配置 type = "nacos" # 配置nacos地址等信息 nacos { serverAddr = "127.0.0.1:8848" namespace = "" group = "DEFAULT_GROUP" username = "nacos" password = "nacos" dataId = "seataServer.properties" } }seataServer.properties
TC服務在管理分散式事務時,需要記錄事務相關數據到資料庫中,你需要提前創建好這些表。
這些表主要記錄全局事務、分支事務、全局鎖信息:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- 分支事務表 -- ---------------------------- DROP TABLE IF EXISTS `branch_table`; 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; -- ---------------------------- -- 全局事務表 -- ---------------------------- DROP TABLE IF EXISTS `global_table`; 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; SET FOREIGN_KEY_CHECKS = 1;table.sql
2.4.
<!--seata--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <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> <!--seata starter 採用1.4.2版本--> <version>${seata.version}</version> </dependency>
seata:
registry: # TC服務註冊中心的配置,微服務根據這些信息去註冊中心獲取tc服務地址
type: nacos # 註冊中心類型 nacos
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
namespace: "" # namespace,預設為空
group: DEFAULT_GROUP # 分組,預設是DEFAULT_GROUP
application: seata-tc-server # seata服務名稱
username: nacos
password: nacos
tx-service-group: seata-demo # 事務組名稱
service:
vgroup-mapping: # 事務組與cluster的映射關係
seata-demo: SH
六、Seata的事務模式
Seata提供4種事務模式,幫我們實現分散式事務;
異常情況:
-
事務協調者通知每個事物參與者,去資料庫執行事務的預提交;
-
事務參與者預提交完成之後,向事務協調者報告事務預提交狀態;(此時事務不是真正的提交,繼續持有資料庫鎖)
二階段:
-
事務協調者基於一階段的報告來判斷下一步操作
-
如果一階段的分支事務全部預提交成功,則通知所有事務參與者去資料庫正真提交事務
-
如果一階1個事務參與者的預提交結果失敗,則通知所有事務參與者回滾事務
-
① 註冊分支事務到TC
② 執行分支業務sql但不提交
③ 報告執行狀態到TC
TC二階段的工作:
-
TC檢測各分支事務執行狀態
a.如果都成功,通知所有RM提交事務
b.如果有失敗,通知所有RM回滾事務
RM二階段的工作:
-
接收TC指令,提交或回滾事務
XA模式的優點是什麼?
-
事務的強一致性,滿足ACID原則。
-
常用資料庫都支持,實現簡單,並且沒有代碼侵入
XA模式的缺點是什麼?
-
因為一階段需要鎖定資料庫資源,等待二階段結束才釋放,性能較差
-
依賴關係型資料庫實現事務
seata:
data-source-proxy-mode: XA
2.4.2.添加註解
給發起全局事務的入口方法添加@GlobalTransactional註解
@Override @Transactional //給發起全局事務的入口方法添加@GlobalTransactional註解: @GlobalTransactional public Long create(Order order) { // 創建訂單 orderMapper.insert(order); try { // 扣用戶餘額 accountClient.deduct(order.getUserId(), order.getMoney()); // 扣庫存 storageClient.deduct(order.getCommodityCode(), order.getCount()); } catch (FeignException e) { log.error("下單失敗,原因:{}", e.contentUTF8(), e); throw new RuntimeException(e.contentUTF8(), e); } return order.getId(); }
2.4.3.
-
註冊分支事務
-
記錄undo-log(數據快照以保證事務提交失敗之後可以回滾)
-
執行業務sql並提交
-
報告事務狀態
階段二提交時RM的工作:
-
刪除undo-log即可
階段二回滾時RM的工作:
-
根據undo-log恢複數據到更新前
簡述AT模式與XA模式最大的區別是什麼?
-
XA模式一階段不提交事務,鎖定資源;AT模式一階段直接提交,不鎖定資源。
-
XA模式依賴資料庫機制實現回滾;AT模式利用數據快照實現數據回滾。
-
3.2.臟寫問題
在多線程併發訪問AT模式的分散式事務時,有可能出現臟寫問題,如圖:
解決臟寫問題的思路就是引入了全局鎖的概念。
在釋放DB鎖之前,先拿到全局鎖。避免同一時刻有另外一個事務來操作當前數據。
3.3.
-
一階段完成直接提交事務,釋放資料庫資源,性能比較好
-
利用全局鎖實現讀寫隔離
-
沒有代碼侵入,框架自動完成回滾和提交
AT模式的缺點:
-
兩階段之間屬於軟狀態,屬於最終一致
-
框架的快照功能會影響性能,但比XA模式要好很多
3.4.
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;lock_table.sql
------------------------------------------
DROP TABLE IF EXISTS `undo_log`; 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;undo_log.sql
seata:
data-source-proxy-mode: AT # 預設就是AT
2.4.3.重啟服務並測試
-
Try:資源的檢測和預留;
-
Confirm:完成資源操作業務;要求 Try 成功 Confirm 一定要能成功。
-
Cancel:預留資源釋放,可以理解為try的反向操作。
TCC模式的出現的空回歸和業務懸掛問題都是因為TM(全局事務管理器)會通過單獨的線程非同步調用Try、Cancel、Confirm這3個操作;
且會出現重覆調用現象,所以TCC模式中的Try、Cancel、Confirm這3個操作都需要實現冪等性、並且避免空回歸和業務懸掛;
1.TCC執行流程
- TM(業務邏輯方法)開始執行之後,立即自動向TC(Seat伺服器)開啟1個發起全局事務的通知;
- TC為當前全局事務分配1個全局事務編號;
- TM(業務邏輯方法)方法依次向下調用TM業務邏輯方法里包含的RM分支事務,RM向TC上報分支事務的執行狀態,TC為RM分配分支事務編號;
- TM業務邏輯方法調用完了所有RM之後,TM向TC上報執行成功/失敗通知,TC向所有RM下達分支事務的Commit/Rollback操作;
2.
在未執行第1階段的Try操作時,先執行了第二階段的Cancel回滾操作,說白了就是還沒有執行Try操作就執行了Cancel回滾操作,就是空回滾
事務懸掛是針對Try操作的;