前言
在分布式的微服務架構中,鑒于服務單一職責性,各個微服務都分布在不同的服務器節點,且每1個微服務是獨立的;
在后端每個微服務都是分散和獨立的,可能使用不同編程語言,使用不同的資料庫,通過RPC呼叫完成前端用戶發送的請求(任務);
假設1個用戶在1個分布式微服務架構的電商網站購物,購買了1件商品點擊了下單,后臺需要一組微服務協作完成下單操作;
即:訂單服務創建訂單--->庫存服務減少庫存---->用戶服務減少余額;
在這3個環節執行程序中,如果其中1個微服務不可用,都無法滿足當前用戶下單的需求;

那如何保證用戶的1次操作具備資料庫事務的ACID特性呢?
這就需要分布式事務;
一、CAP理論
2000年加州理工大學伯克利分校的EricBrewer教授提出在分布式架構中應當具備3個理想的指標
- Consistency(一致性)
- Availability(可用性)
- Partition tolerance (磁區容錯性)
稱為CAP理論;

1.一致性
指分布式架構中資料的強一致性, 即使資料分布在不同節點上,1個節點寫操作之后,在另1個節點的讀操作,也可以讀到最新狀態的資料(MySQL主從之間資料同步),
2.可用性
指服務處在100%可用的狀態,用戶訪問分布式集群中任意1個服務 ,即使該服務故障,用戶依然能得到回應,而不是超時或拒絕,
3.磁區容錯
指分布式架構中物理節點, 即使遇到網路故障或其它原因導致分布式系統中的部分節點與其它節點失去連接無法通信,故障節點可以形成獨立磁區,不影響全域,
二、BASE理論
在分布式微服務架構中,各個微服務之間通過網路進行RPC呼叫,CAP理論中的可用性和一致性就是相互矛盾的,無法同時成立;
1.CAP理論的問題
在分布式微架構中,如果想要實作一致性就需要在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(最終一致性)
雖然無法保證強一致性,但是在軟狀態結束后,最終達到資料一致,
三、分布式事務解決思路
分布式事務在設計之初借鑒CAP、BASE理論,有兩種解決思路:
-
AP模式:各子事務分別執行和提交,允許出現結果不一致,然后采用彌補措施恢復資料即可,實作最終一致,
-
CP模式:各個子事務執行后互相等待,同時提交,同時回滾,達成強一致,但事務等待程序中,處于弱可用狀態,
但不管是哪一種模式,想要在分布式架構中控制多個事物都需要1個事務協調者(TC):
以下下單、扣款、商品出庫環節稱為分支事務,整個環節稱為全域事務;

四、初識Seata
Seata是 2019 年1 月份螞蟻金服和阿里巴巴共同開源的分布式事務解決方案,
致力于提供高性能和簡單易用的分布式事務服務,為用戶打造一站式的分布式事務解決方案,
官網地址:http://seata.io/,其中的檔案、播客中提供了大量的使用說明、原始碼分析,
1.Seata的架構
Seata事務管理中有3個重要的角色:
-
TC (Transaction Coordinator) - 事務協調者:搭建好的Seata服務器,所有的RM和TM都要想TC上報,當前分支事務執行的狀態,以便于TC作出全域事務的回滾/提交操作(CEO);
-
TM (Transaction Manager) - 事務管理器:代表整個業務邏輯,決定全域事務的范圍和邊界,從哪個環節開始全域事務?在哪個環節提交或回滾全域事務?從哪個環節結束全域事務?(經理)
-
RM (Resource Manager) - 資源管理器:具體執行分支事務的微服務,(員工)

Seata基于上述架構提供了四種不同的分布式事務解決方案:
-
XA模式:強一致性分階段事務模式,犧牲了一定的可用性,無業務侵入
-
TCC模式:最終一致的分階段事務模式,有業務侵入
-
AT模式:最終一致的分階段事務模式,無業務侵入,也是Seata的默認模式
-
SAGA模式:長事務模式,有業務侵入
無論采用哪1種分布式事務解決方案,都需要先搭建Seata服務器,也就是事務的協調者(TC),
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.nacos添加配置
特別注意,為了讓tc服務的集群可以共享配置,我們選擇了nacos作為統一配置中心,
因此服務端組態檔seataServer.properties檔案需要在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" } }seataServer.properties
2.3.創建資料庫表
TC服務在管理分布式事務時,需要記錄事務相關資料到資料庫中,你需要提前創建好這些表,
新建一個名為seata的資料庫,seata資料庫中創建branch_table和global_table兩張表;

這些表主要記錄全域事務、分支事務、全域鎖資訊:
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.啟動TC服務
進入bin目錄,運行其中的seata-server.bat即可
2.5.測驗
打開瀏覽器,訪問nacos地址:http://localhost:8848,然后進入服務串列頁面,可以看到seata-tc-server的資訊:

五、微服務集成Seata
此時Seata已經注冊到Nacos并可以從Nacos中讀取配置資訊;
我們就可以在微服務中集成使用Seata服務了;
1.引入依賴
<!--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>
2.配置TC地址
微服務通過application.yml中配置的Nacos獲取TC地址;
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.兩階段提交
XA是規范,目前主流資料庫都實作了這種規范,實作的原理都是基于兩階段提交,
正常情況:

例外情況:

一階段(事務預提交):
-
事務協調者通知每個事物參與者,去資料庫執行事務的預提交;
-
事務參與者預提交完成之后,向事務協調者報告事務預提交狀態;(此時事務不是真正的提交,繼續持有資料庫鎖)
二階段:
-
事務協調者基于一階段的報告來判斷下一步操作
-
如果一階段的分支事務全部預提交成功,則通知所有事務參與者去資料庫正真提交事務
-
如果一階1個事務參與者的預提交結果失敗,則通知所有事務參與者回滾事務
-
2.XA模式
幾乎所有主流的資料庫都XA 規范提供了支持;
Seata的XA模式是利用資料庫底層事務二階段提交的特性,封裝了二階段提交操作方法;
2.2.Seata的XA模型
Seata對原始的XA模式做了簡單的封裝和改造,以適應自己的事務模型,基本架構如圖:

RM一階段的作業:
① 注冊分支事務到TC
② 執行分支業務sql但不提交
③ 報告執行狀態到TC
TC二階段的作業:
-
TC檢測各分支事務執行狀態
a.如果都成功,通知所有RM提交事務
b.如果有失敗,通知所有RM回滾事務
RM二階段的作業:
-
接收TC指令,提交或回滾事務
2.3.優缺點
XA模式的優點是什么?
-
事務的強一致性,滿足ACID原則,
-
常用資料庫都支持,實作簡單,并且沒有代碼侵入
XA模式的缺點是什么?
-
因為一階段需要鎖定資料庫資源,等待二階段結束才釋放,性能較差
-
依賴關系型資料庫實作事務
2.4.實作XA模式
Seata的starter已經完成了XA模式的自動裝配,實作非常簡單,步驟如下:
2.4.1.修改application.yml檔案
每個參與事務的微服務都需要開啟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.重啟服務并測驗
重啟order-service,再次測驗,發現無論怎樣,3個微服務都能成功回滾,
3.AT模式
AT模式同樣是分階段提交的事務模型,不過缺彌補了XA模型中資源鎖定周期過長的缺陷,
AT模式就是模仿了資料庫底層XA二階段提交機制,把改功能抽離出來在Seata中再實作,這樣可以使Seata支持更多資料庫的分布式事務操作;

階段一RM的作業:
-
注冊分支事務
-
記錄undo-log(資料快照以保證事務提交失敗之后可以回滾)
-
執行業務sql并提交
-
報告事務狀態
階段二提交時RM的作業:
-
洗掉undo-log即可
階段二回滾時RM的作業:
-
根據undo-log恢復資料到更新前
3.1.AT與XA的區別
簡述AT模式與XA模式最大的區別是什么?
-
XA模式一階段不提交事務,鎖定資源;AT模式一階段直接提交,不鎖定資源,
-
XA模式依賴資料庫機制實作回滾;AT模式利用資料快照實作資料回滾,
-
XA模式強一致;AT模式最終一致
3.2.臟寫問題
在多執行緒并發訪問AT模式的分布式事務時,有可能出現臟寫問題,如圖:

解決臟寫問題的思路就是引入了全域鎖的概念,
在釋放DB鎖之前,先拿到全域鎖,避免同一時刻有另外一個事務來操作當前資料,

3.3.優缺點
AT模式的優點:
-
一階段完成直接提交事務,釋放資料庫資源,性能比較好
-
利用全域鎖實作讀寫隔離
-
沒有代碼侵入,框架自動完成回滾和提交
AT模式的缺點:
-
兩階段之間屬于軟狀態,屬于最終一致
-
框架的快照功能會影響性能,但比XA模式要好很多
3.4.實作AT模式
3.4.1.匯入資料庫表,記錄全域鎖

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
3.4.2.修改application.yml檔案
將事務模式修改為AT模式即可
seata:
data-source-proxy-mode: AT # 默認就是AT
2.4.3.重啟服務并測驗

4.TCC模式
TCC是Try、Confirm、Cancel三個單詞的縮寫,屬于二階段提交事務;
TCC要求每1個分支事務實作3個操作或者3個階段:Try預處理、Confirm確認、Cancel撤銷;
Try操作做業務檢查以及資源預留
Confirm做業務確認
Cancel實作1個與Try相反的操作既回滾操作
通過人工編碼來實作資料恢復,1個業務至少需要實作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.慷訓滾
慷訓滾是針對Cancel操作的,
在未執行第1階段的Try操作時,先執行了第二階段的Cancel回滾操作,說白了就是還沒有執行Try操作就執行了Cancel回滾操作,就是慷訓滾,
2.慷訓滾解決方案
在執行Cancel操作之前,應當判斷Try中分支事務是否已經執行,如果尚未執行,則應該慷訓滾,
3.事務懸掛
事務懸掛是針對Try操作的;
對于已經慷訓滾的業務,之前被阻塞的try操作恢復,繼續執行try,就永遠不可能confirm或cancel ,事務一直處于中間狀態,這就是業務懸掛,
4.事務懸掛解決方案
在執行第1階段的Try操作時,應當判斷第2階段的Confirm/Cancel操作是否已經執行過了?那目前就是分支事務就處于慷訓滾狀態,此時Try操作無需執行;
如果第2階段的Cancel操作已經提前執行,那目前就是分支事務就處于慷訓滾狀態,此時第1階段的Try操作,應當阻止慷訓滾之后的Try操作,避免業務懸掛,一錯再錯;
3.實作TCC模式
想要實作TCC模式就需要選擇好業務邏輯在哪里?也就是在哪里實作1個TM;
1.創建表

account_freeze_tbl
CREATE TABLE `account_freeze_tbl` ( `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `freeze_money` int(11) UNSIGNED NULL DEFAULT 0, `state` int(1) NULL DEFAULT NULL COMMENT '事務狀態,0:try,1:confirm,2:cancel', PRIMARY KEY (`xid`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;account_freeze_tbl.sql
欄位說明
- xid:保存全域事務ID
- freeze_money:用來記錄用戶凍結金額
- state:用來記錄事務狀態
2.宣告TCC介面
TCC的Try、Confirm、Cancel方法都需要在介面中基于注解來宣告,
package cn.itcast.account.service; import io.seata.rm.tcc.api.BusinessActionContext; import io.seata.rm.tcc.api.BusinessActionContextParameter; import io.seata.rm.tcc.api.LocalTCC; import io.seata.rm.tcc.api.TwoPhaseBusinessAction; @LocalTCC public interface AccountService { /** * 從用戶賬戶中扣款 */ //void deduct(String userId, int money); //修改以上代碼為以下內容 @TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel") void deduct(@BusinessActionContextParameter(paramName = "userId") String userId, @BusinessActionContextParameter(paramName = "money")int money); boolean confirm(BusinessActionContext ctx); boolean cancel(BusinessActionContext ctx); }AccountService.interface
3.宣告TCC介面
package cn.itcast.account.service.impl; import cn.itcast.account.entity.AccountFreeze; import cn.itcast.account.mapper.AccountFreezeMapper; import cn.itcast.account.mapper.AccountMapper; import cn.itcast.account.service.AccountService; import io.seata.core.context.RootContext; import io.seata.rm.tcc.api.BusinessActionContext; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * @author 張根 */ @Slf4j @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; @Autowired private AccountFreezeMapper freezeMapper; @Override @Transactional //正常邏輯 public void deduct(String userId, int money) { // 0.獲取事務id String xid = RootContext.getXID(); // 查詢freeze,防止業務懸掛 AccountFreeze oldFreeze = freezeMapper.selectById(xid); if (oldFreeze != null) { // 拒絕業務 return; } // 1.扣減可用余額 accountMapper.deduct(userId, money); // 2.記錄凍結金額,事務狀態 AccountFreeze freeze = new AccountFreeze(); freeze.setUserId(userId); freeze.setFreezeMoney(money); freeze.setState(AccountFreeze.State.TRY); freeze.setXid(xid); freezeMapper.insert(freeze); } //全體事務沒有出現問題 @Override public boolean confirm(BusinessActionContext ctx) { // 1.獲取事務id String xid = ctx.getXid(); // 2.根據id洗掉凍結記錄 int count = freezeMapper.deleteById(xid); return count == 1; } //全體事務中其中1個環節出現問題-回滾 @Override @Transactional public boolean cancel(BusinessActionContext ctx) { // 0.查詢凍結記錄 String xid = ctx.getXid(); String userId = ctx.getActionContext("userId").toString(); AccountFreeze freeze = freezeMapper.selectById(xid); // 慷訓滾判斷 if (freeze == null) { freeze = new AccountFreeze(); freeze.setUserId(userId); freeze.setFreezeMoney(0); freeze.setState(AccountFreeze.State.CANCEL); freeze.setXid(xid); freezeMapper.insert(freeze); return true; } // 冪等判斷 if(freeze.getState() == AccountFreeze.State.CANCEL){ // 已經處理過了,無需重復cancel return true; } // 1.恢復可用余額 accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney()); // 2.將凍結金額清零,狀態改為CANCEL freeze.setFreezeMoney(0); freeze.setState(AccountFreeze.State.CANCEL); int count = freezeMapper.updateById(freeze); return count == 1; } }AccountServiceImpl.java
4.TCC模式優缺點
TCC模式的每個階段是做什么的?
-
Try:資源檢查和預留
-
Confirm:業務執行和提交
-
Cancel:預留資源的釋放
TCC的優點是什么?
-
一階段完成直接提交事務,釋放資料庫資源,性能好
-
相比AT模型,無需生成快照,無需使用全域鎖,性能最強
-
不依賴資料庫事務,而是依賴補償操作,可以用于非事務型資料庫
TCC的缺點是什么?
-
有代碼侵入,需要人為撰寫try、Confirm和Cancel介面,太麻煩
-
軟狀態,事務是最終一致
-
需要考慮Confirm和Cancel的失敗情況,做好冪等處理
5.SAGA模式
在 Saga 模式下,分布式事務內有多個參與者,每一個參與者都是一個沖正補償服務,需要用戶根據業務場景實作其正向操作和逆向回滾操作,
分布式事務執行程序中,依次執行各參與者的正向操作,如果所有正向操作均執行成功,那么分布式事務提交,如果任何一個正向操作執行失敗,那么分布式事務會去退回去執行前面各參與者的逆向回滾操作,回滾已提交的參與者,使分布式事務回到初始狀態,

Saga也分為兩個階段:
-
一階段:直接提交本地事務
-
二階段:成功則什么都不做;失敗則通過撰寫補償業務來回滾
5.1.優缺點
優點:
-
事務參與者可以基于事件驅動實作異步呼叫,吞吐高
-
一階段直接提交事務,無鎖,性能好
-
不用撰寫TCC中的三個階段,實作簡單
缺點:
-
軟狀態持續時間不確定,時效性差
-
沒有鎖,沒有事務隔離,會有臟寫
6.Seata四種模式對比
我們從以下幾個方面來對比四種實作:
-
一致性:能否保證事務的一致性?強一致還是最終一致?
-
隔離性:事務之間的隔離性如何?
-
代碼侵入:是否需要對業務代碼改造?
-
性能:有無性能損耗?
-
場景:常見的業務場景

參考
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/499913.html
標籤:其他
上一篇:設計模式遵循的設計原則
下一篇:如何實作一個狀態機?
