高級JAVA開發 分佈式事務部分

參考和摘自:
分佈式事務的4種模式

本地事務

ACID原則

A:原子性(Atomicity):
	一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。
	事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

C:一致性(Consistency):
	事務的一致性指的是在一個事務執行之前和執行之後數據庫都必須處於一致性狀態。
		如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。
		如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態。

I:隔離性(Isolation):
	指的是在併發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。
	由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。
	事務查看數據更新時,數據所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看到中間狀態的數據。

D:持久性(Durability):
	指的是隻要事務成功結束,它對數據庫所做的更新就必須保存下來。

隔離性:Mysql的4個事務隔離級別

1、讀未提交(Read Uncommited)(可能產生髒讀,幻讀,不可重複讀):
	該隔離級別允許髒讀取,其隔離級別最低;
	比如:
		事務A和事務B同時進行,事務A在整個執行階段,會將某數據的值從1開始一直加到10,然後進行事務提交,
		此時,事務B能夠看到這個數據項在事務A操作過程中的所有中間值(如1變成2,2變成3等)

2、讀已提交(Read Commited)(可能產生不可重複讀,幻讀):
	讀已提交只允許獲取已經提交的數據。
	比如:
		事務A和事務B同時進行,事務A進行+1操作。
		此時,事務B無法看到這個數據項在事務A操作過程中的所有中間值,只能看到最終的10。
		但是事務B可能出現第一讀取到1,第二次讀取到事務A提交的數據10,造成不可重複讀。
	不可重複讀指的是:同事務內,同一條數據兩次讀取到不同值的情況。
	幻讀指的是:同事務內,同一查詢條件兩次讀取到不同條數的情況。

3、可重複讀(Repeatable Read)(可能產生幻讀):
	就是保證在事務處理過程中,多次讀取同一個數據時,其值都和事務開始時刻是一致的,
	因此該事務級別禁止不可重複讀取和髒讀取,但是有可能出現幻影數據。

4、串行化:
	是最嚴格的事務隔離級別,它要求所有事務被串行執行,即事務只能一個接一個的進行處理,不能併發執行。

Mysql InnoDB引擎 RR隔離級別下產生幻讀例子

數據準備:
	DROP TABLE IF EXISTS `test`;
	CREATE TABLE `test` (
	  `id` int(1) NOT NULL AUTO_INCREMENT,
	  `name` varchar(8) DEFAULT NULL,
	  PRIMARY KEY (`id`)
	) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8;
	BEGIN;
		INSERT INTO `test` VALUES
			 ('0', '小羅'), ('5', '小黃'), ('10', '小明'), ('15', '小紅'), ('20', '小紫'), ('25', '小黑');
	COMMIT;

操作例子:
---- | -------------------------------------------------- | -----------------------------------------------------
Time |                   Session1 TX1                     |             Session2 TX2 
---- | -------------------------------------------------- | -----------------------------------------------------
     | BEGIN;                                             |
  t1 | -- return row num 0	                              |
	 | SELECT * FROM `test` WHERE `id`> 10 AND `id` < 15; |
---- | -------------------------------------------------- | -----------------------------------------------------
	 |                                                    | BEGIN;
  t2 |                                                    | INSERT INTO `test` (`id`, `name`) VALUES (12, '李西');
	 |                                                    | COMMIT;
---- | -------------------------------------------------- | -----------------------------------------------------
     | -- return row num 0	未幻讀                         | 
     | SELECT * FROM `test` WHERE `id`> 10 AND `id` < 15; |   
	 |                                                    | 
     | --  update TX2 提交的數據                            | 
  t3 | update `test` set `name` = 'aaa' where id = 12 ;   |
 	 |                                                    | 
     | -- return row num 1	產生幻讀                       | 
     | SELECT * FROM `test` WHERE `id`> 10 AND `id` < 15; |
     | COMMIT;                                            |
---- | -------------------------------------------------- | -----------------------------------------------------

Tips:
	Mysql RR級別下有MVCC機制,解決了部分幻讀問題(第二次讀取)。
	TX1的更新操作是多步的:讀取id爲12的數據行,更新數據,寫入數據行。
	更新操作讀取時的操作是『當前讀』,也就是讀取到了TX2提交的最新數據,在此基礎上做更新操作。
	TX1第三次讀取時,可以讀取到本事務做的更新操作,這時也就產生了幻讀。

	如果TX1在第一次讀取時改成這樣:
		SELECT * FROM `test` WHERE `id`> 10 AND `id` < 15 FOR UPDATE;
	TX2在INSERT操作時會阻塞住,直到TX1 COMMIT,此時TX1 select 就不會產生幻讀問題。

分佈式事務

分佈式系統中的理論

CAP 原則(布魯爾定理)

在一個分佈式系統中,一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。CAP 原則指的是,這三個要素最多隻能同時實現兩點,不可能三者兼顧。

一致性(C):
	在分佈式系統中的所有數據備份,在同一時刻是否同樣的值。(等同於所有節點訪問同一份最新的數據副本)

可用性(A):
	在集羣中一部分節點故障後,集羣整體是否還能響應客戶端的讀寫請求。(對數據更新具備高可用性)

分區容錯性(P):
	分區容錯性是指系統能夠容忍節點之間的網絡通信的故障。
	以實際效果而言,分區相當於對通信的時限要求。
	系統如果不能在時限內達成數據一致性,就意味着發生了分區的情況,必須就當前操作在C和A之間做出選擇。

CAP 在分佈式系統中的權衡:

CA:
	在分佈式系統中,如果要求所有節點數據的一致性(C)又要求所有節點的可用性(A),
	那麼在發生分區現象時(分區無法避免,因爲存在多個節點)爲了保證一致性(C),
	分佈式系統整體只能拒絕所有請求停止服務,等待節點分區問題解決後再繼續提供服務。
	此時已經違背了可用性(A)。
	所以分佈式系統理論上不可能選擇 CA 架構,只能選擇 CP 或者 AP 架構。

CP:
	放棄可用性(A),追求一致性和分區容忍性。在產生分區問題時,放棄可用性。ZooKeeper 其實就是追求的強一致。

AP:
	放棄一致性(C)(這裏說的一致性是強一致性) 追求分區容錯性和可用性,這是很多分佈式系統設計時的選擇,比如 Eueaka。

CAP 理論中是忽略網絡延遲的,也就是當事務提交時,從節點 A 到節點 B 沒有延遲,但是在現實中這個是明顯不可能的,所以總會有一定的時間是不一致。

BASE 理論

什麼是BASE理論

是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。
是對 CAP 中 AP 的一個擴展,其核心思想是即使無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

基本可用:
	分佈式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。

軟狀態:
	允許系統中存在中間狀態,這個狀態不影響系統可用性,這裏指的是 CAP 中的不一致。

最終一致:
	最終一致是指經過一段時間後,所有節點數據都將會達到一致。

BASE 解決了 CAP 理論中沒有網絡延遲的情況,在 BASE 中用軟狀態和最終一致,保證了延遲後的一致性。
BASE 和 ACID 是相反的,它完全不同於 ACID 的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。

分佈式事務中的理論

XA規範

X/Open組織 定義的一套DTP分佈式事務的模型和規範。
XA DTP分佈式事務模型中的四個角色:

AP(Application Program,應用程序)
TM(Transaction Manager,事務管理器)
RM(Resource Manager,資源管理器)通常指數據庫
CRM(Communication Resource Manager,通信資源管理器)

XA是DTP模型定義TM和RM之前通訊的接口規範。XA接口函數由數據庫廠商提供。TM中間件用它來通知數據庫事務的開始、結束以及提交、回滾等。

2PC(tow phase commit) 兩階段提交

根據XA思想衍生出來的一致性協議。

參與2PC的角色:

協調者(coordinator)
參與者(participants, 或cohort)

保證事務在提交時,協調者和參與者處於一致性狀態,如果其中有一個參與者出現了故障或者網絡問題,不能及時的迴應協調者,那麼這次事務就宣告失敗或者出現阻塞。

兩階段:

1、準備階段:
	事務協調者(事務管理器 TM)給每個參與者(資源管理器 RM)發送Prepare消息,
	每個參與者要麼直接返回失敗(如權限驗證失敗),要麼在本地執行事務,寫本地的redo和undo日誌,但不提交
	
2、提交階段
	如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(Rollback)消息;
	否則,發送提交(Commit)消息;
	參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源。
	(注意:必須在最後階段釋放鎖資源)

2PC的缺點:
二階段提交看起來確實能夠提供原子性的操作,但是不幸的是,二階段提交還是有幾個缺點的:

1、同步阻塞問題。
	在事務執行過程中,所有參與節點都是事務阻塞型的。
	參與者佔有公共資源時,其他第三方節點訪問公共資源則會處於阻塞。

2、單點故障。
	在2PC中由協調者進行協調,一旦協調者發生故障,參與者會阻塞。
	尤其在第二階段commit階段,協調者發生故障,那麼所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。
	注:如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因爲協調者宕機導致的參與者處於阻塞狀態的問題

3、數據不一致。
	在二階段提交的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常;
	或者在發送commit請求過程中協調者發生了故障,導致只有一部分參與者接受到了commit請求。
	而在這部分參與者接到commit請求之後就會執行commit操作。
	但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分佈式系統便出現了數據不一致性的現象。

4、二階段無法解決的問題:
	協調者發出commit消息,並且只有部分參與者收到消息,此時協調者和收到消息的參與者發生宕機。
	那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,集羣中不能判斷出事務是否被已經提交。

TCC(Try-Confirm-Cancel)補償事務

TCC 其實就是採用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。它分爲三個階段:

Try 階段:
	主要是對業務系統做檢測及資源預留。

Confirm 階段:
	主要是對業務系統做確認提交。
	Try 階段執行成功並開始執行 Confirm 階段時,默認 Confirm 階段是不會出錯的。
	即:只要 Try 成功,Confirm 一定成功。

Cancel 階段:
	主要是在業務執行錯誤需要回滾的狀態下執行的業務取消,預留資源釋放。

舉個栗子:

分佈式系統中一個訂單支付後會協調以下服務共同完成一系列操作:
	訂單服務 - 修改訂單狀態爲已支付
	庫存服務 - 庫存扣減
	積分服務 - 給用戶增加積分
	倉儲服務 - 創建銷售出庫單
如果其中一個環節操作失敗,那麼其餘服務應該回滾到訂單支付前的狀態。

如果應用TCC解決方案,那麼各個服務需要提供以下支持:
	訂單服務 
		- 提供修改訂單狀態爲 UPDATING 的接口		(Try)
		- 提供修改訂單狀態爲 PAYED 的接口			(Confirm)
		- 提供修改訂單狀態爲 UNPAID 的接口			(Cancel)
	庫存服務 
		- 提供 凍結庫存 接口						(Try)
		- 提供 凍結庫存提交 接口					(Confirm)
		- 提供 凍結庫存回滾 接口					(Cancel)
	積分服務 
		- 提供 增加積分 狀態爲 INEFFECTIVE 接口		(Try)
		- 提供 修改積分狀態爲 EFFECTIVE 接口		(Confirm)
		- 提供 刪除積分記錄 接口					(Cancel)
	倉儲服務
		- 提供 創建出庫單 狀態爲 INEFFECTIVE 接口	(Try)		
		- 提供 修改出庫單狀態爲 EFFECTIVE 接口		(Confirm)	
		- 提供 刪除出庫單 接口						(Cancel)

第一階段:執行所有 Try 接口 預留資源
第二階段:如果有服務 Try 階段預留資源失敗,那麼執行 Try 成功服務的 Cancel 接口,反之執行所有 Confirm 接口。

TCC 事務框架要記錄一些分佈式事務的活動日誌,保存分佈式事務運行的各個階段和狀態。
比如發現某個服務的 Cancel 或者 Confirm 一直沒成功,會不停的重試調用它的 Cancel 或者 Confirm 邏輯,務必要它成功!

優勢與劣勢:

優勢:
	- TCC 分佈式事務的實現方式的在於,可以讓應用自己定義數據庫操作的粒度,使得降低鎖衝突、提高吞吐量成爲可能。
	
缺點:
	- 對應用的侵入性非常強,業務邏輯的每個分支都需要實現try、confirm、cancel三個操作。
	- 現難度較大,需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。
	  爲了滿足一致性的要求,confirm和cancel接口還必須實現冪等。

2PC 和 TCC 的異同點:

從模式上來將,TCC類似2PC。抽象成邏輯即:
	- 第一步試着操作數據,第二步則是在第一步基礎上做確認動作或取消動作。

但兩者從根本上來說是截然不同的:
	- 2PC更看重的是事務處理階段、RM提供底層支持(一般是兼容XA)、Prepare、Commit和Rollback。
	  一個完整的事務生命週期是:begin -> 業務邏輯 -> 逐個RM Prepare -> Commit/Rollback。
	- TCC則更看重在業務層面的分步處理。													
	  一個完整的事務生命週期是:begin -> 業務邏輯(逐個try業務) -> Comfirm業務/Cancel業務。

3PC(tow phase commit) 三階段提交

在兩階段提交的基礎上增加了CanCommit階段,並引入了超時機制。
一旦事務參與者遲遲沒有收到協調者的Commit請求,就會自動進行本地commit,
這樣相對有效地解決了協調者單點故障的問題。
但是性能問題和不一致問題仍然沒有根本解決。

CanCommit階段:
	事務的協調者向所有參與者詢問“你們是否可以完成本次事務?”,
	如果參與者節點認爲自身可以完成事務就返回“YES”,否則“NO”。
	而在實際的場景中參與者節點會對自身邏輯進行事務嘗試,
	其實說白了就是檢查下自身狀態的健康性,看有沒有能力進行事務操作。

PreCommit階段:
	在階段一中,如果所有的參與者都返回Yes的話,那麼就會進入PreCommit階段進行事務預提交。
	此時分佈式事務協調者會向所有的參與者節點發送PreCommit請求。
	參與者收到後開始執行事務操作,並將Undo和Redo信息記錄到事務日誌中。
	參與者執行完事務操作後(此時屬於未提交事務的狀態),就會向協調者反饋“Ack”表示我已經準備好提交了,並等待協調者的下一步指令。
	
	否則,如果階段一中有任何一個參與者節點返回的結果是No響應,
	或者協調者在等待參與者節點反饋的過程中超時(2PC中只有協調者可以超時,參與者沒有超時機制),
	整個分佈式事務就會中斷,協調者就會向所有的參與者發送“abort”請求。

DoCommit階段:
	在階段二中如果所有的參與者節點都可以進行PreCommit提交,那麼協調者就會從“預提交狀態”->“提交狀態”。
	然後向所有的參與者節點發送"doCommit"請求。
	參與者節點在收到提交請求後就會各自執行事務提交操作,
	並向協調者節點反饋“Ack”消息,協調者收到所有參與者的Ack消息後完成事務。
	
	相反,如果有一個參與者節點未完成PreCommit的反饋或者反饋超時,
	那麼協調者都會向所有的參與者節點發送abort請求,從而中斷事務。

相比較2PC而言,3PC對於協調者(Coordinator)和參與者(Partcipant)都設置了超時時間,而2PC只有協調者才擁有超時機制。
這個優化點主要是避免了參與者在長時間無法與協調者節點通訊(協調者掛掉了)的情況下,無法釋放資源的問題。因爲參與者自身擁有超時機制會在超時後,自動進行本地commit從而進行釋放資源。而這種機制也側面降低了整個事務的阻塞時間和範圍。
另外,通過CanCommit、PreCommit、DoCommit三個階段的設計,相較於2PC而言,多設置了一個緩衝階段保證了在最後提交階段之前各參與節點的狀態是一致的。

3PC依然沒有完全解決數據不一致的問題。

分佈式事務的實現方案

基於 JTA 實現的分佈式事務

何爲 JTA:

Java Transaction API,分佈式事務的編程 API,基於 XA DTP 模型和規範。
在J2EE中,單庫的事務是通過JDBC事務來支持的,如果是跨多個庫的事務,是通過 JTA API 來支持的。
通過 JTA API 可以協調和管理橫跨多個數據庫的分佈式事務。
狹義的說,JTA 只是一套接口。

開源的實現 JTA TM 的提供商:

Java Open Transaction Manager (JOTM)
JBoss TS
Bitronix Transaction Manager (BTM)
Atomikos
Narayana

JTA RM的提供商(XA接口):

一般數據庫都會提供實現,比如 Mysql、Oracle

處理流程:

- 事務管理器要求每個涉及到事務的數據庫預提交(precommit)此操作,並反映是否可以提交。
- 事務協調器要求每個數據庫提交數據,或者回滾數據。

優缺點:

優點:
	儘量保證了數據的強一致,實現成本較低,在各大主流數據庫都有自己實現,對於 MySQL 是從 5.5 開始支持。

缺點(同2PC缺點):
	同步阻塞:
		在準備就緒之後,資源管理器中的資源一直處於阻塞,直到提交完成,釋放資源。
	
	單點問題:
		事務管理器在整個流程中扮演的角色很關鍵,如果其宕機。
		比如:
			在第一階段已經完成,在第二階段正準備提交的時候事務管理器宕機,
			資源管理器就會一直阻塞,導致數據庫無法使用。
	
	數據不一致:
		兩階段提交協議雖然爲分佈式數據強一致性所設計,但仍然存在數據不一致性的可能。
		比如:
			在第二階段中,假設協調者發出了事務 Commit 的通知,
			但是因爲網絡問題該通知僅被一部分參與者所收到並執行了 Commit 操作,
			其餘的參與者則因爲沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。

總的來說,XA 協議比較簡單,成本較低,但是其單點問題,以及不能支持高併發(由於同步阻塞)依然是其最致命的弱點。

實現栗子移步文章:https://blog.csdn.net/zhouhao88410234/article/details/91872872

Seata 提供的分佈式事務解決方案

轉載和參考自:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
https://juejin.im/post/5d54effe6fb9a06aeb10b646
https://www.jianshu.com/p/0ed828c2019a

Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。

Seata 中有三大模塊,分別是 TM(Transaction Manager)、RM(Resource Manager) 和 TC(Transaction Coordinator):

TC - 事務協調者
維護全局和分支事務的狀態,驅動全局事務提交或回滾。

TM - 事務管理器
定義全局事務的範圍:開始全局事務、提交或回滾全局事務。

RM - 資源管理器
管理分支事務處理的資源,與TC交談以註冊分支事務和報告分支事務的狀態,並驅動分支事務提交或回滾。

其中 TM 和 RM 是作爲 Seata 的客戶端與業務系統集成在一起,TC 作爲 Seata 的服務端獨立部署。

在 Seata 中,分佈式事務的執行流程:

1. TM 開啓分佈式事務(TM 向 TC 註冊全局事務記錄);

2. 按業務場景,編排數據庫、服務等事務內資源(RM 向 TC 彙報資源準備狀態);
 
3. TM 結束分佈式事務,事務一階段結束(TM 通知 TC 提交/回滾分佈式事務);
 
4. TC 彙總事務信息,決定分佈式事務是提交還是回滾;
 
5. TC 通知所有 RM 提交/回滾 資源,事務二階段結束;

事務模型

在這裏插入圖片描述

  • TM 定義全局事務的邊界。
  • RM 負責定義分支事務的邊界和行爲。
  • TC 跟 TM 和 RM 交互(開啓、提交、回滾全局事務;分支註冊、狀態上報和分支的提交、回滾),做全局的協調。

AT (Automatic Transaction)模式

AT 模式 的前提:
基於支持本地 ACID 事務的關係型數據庫。
Java 應用,通過 JDBC 訪問數據庫。
整體機制

整體分爲兩階段:執行階段、完成階段。是兩階段提交協議的演變:

一階段(執行階段):
	業務數據和回滾日誌記錄在同一個本地事務中提交,釋放本地鎖和連接資源。
二階段(完成階段):
	提交異步化,非常快速地完成。
	回滾通過一階段的回滾日誌進行反向補償。

一階段(執行階段)
在這裏插入圖片描述
Seata 的 JDBC 數據源代理通過對業務 SQL 的解析,把業務數據在更新前後的數據鏡像組織成回滾日誌,利用 本地事務 的 ACID 特性,將業務數據的更新和回滾日誌的寫入在同一個 本地事務 中提交。
這樣,可以保證:任何提交的業務數據的更新一定有相應的回滾日誌存在。
基於這樣的機制,分支的本地事務便可以在全局事務的 執行階段 提交,馬上釋放本地事務鎖定的資源。

二階段(完成階段)

  • 如果決議是全局提交,此時分支事務此時已經完成提交,不需要同步協調處理(只需要異步清理回滾日誌),完成階段 可以非常快速地結束。
    在這裏插入圖片描述

  • 如果決議是全局回滾,RM 收到協調器發來的回滾請求,通過 XID 和 Branch ID 找到相應的回滾日誌記錄,通過回滾記錄生成反向的更新 SQL 並執行,以完成分支的回滾。
    在這裏插入圖片描述

隔離性
寫隔離
一階段本地事務提交前,需要確保先拿到 全局鎖 。
拿不到 全局鎖 ,不能提交本地事務。
拿 全局鎖 的嘗試被限制在一定範圍內,超出範圍將放棄,並回滾本地事務,釋放本地鎖。

以一個示例來說明:

兩個全局事務 tx1 和 tx2,分別對 a 表的 m 字段進行更新操作,m 的初始值 1000。

tx1 先開始,開啓本地事務,拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務提交前,先拿到該記錄的 全局鎖 ,本地提交釋放本地鎖。 tx2 後開始,開啓本地事務,拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務提交前,嘗試拿該記錄的 全局鎖 ,tx1 全局提交前,該記錄的全局鎖被 tx1 持有,tx2 需要重試等待 全局鎖 。

在這裏插入圖片描述
tx1 二階段全局提交,釋放 全局鎖 。tx2 拿到 全局鎖 提交本地事務。

在這裏插入圖片描述
如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數據的本地鎖,進行反向補償的更新操作,實現分支的回滾。

此時,如果 tx2 仍在等待該數據的 全局鎖,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的 全局鎖 等鎖超時,放棄 全局鎖 並回滾本地事務釋放本地鎖,tx1 的分支回滾最終成功。

因爲整個過程 全局鎖 在 tx1 結束前一直是被 tx1 持有的,所以不會發生 髒寫 的問題。

讀隔離

在數據庫本地事務隔離級別 讀已提交(Read Committed) 或以上的基礎上,Seata(AT 模式)的默認全局隔離級別是 讀未提交(Read Uncommitted) 。

如果應用在特定場景下,必需要求全局的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。

在這裏插入圖片描述
SELECT FOR UPDATE 語句的執行會申請 全局鎖 ,如果 全局鎖 被其他事務持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執行)並重試。這個過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關數據是 已提交 的,才返回。

出於總體性能上的考慮,Seata 目前的方案並沒有對所有 SELECT 語句都進行代理,僅針對 FOR UPDATE 的 SELECT 語句。

全局鎖是由 TC 也就是 server 來集中維護,而不是在數據庫維護的。這樣做有兩點好處:

一方面:鎖的釋放非常快
	  尤其是在全局提交的情況下,收到全局提交的請求,鎖馬上就釋放掉了,不需要與 RM 或數據庫進行一輪交互。
另外一方面:因爲鎖不是數據庫維護的,從數據庫層面看,數據沒有鎖定。
      這也就是給極端情況下,業務 降級 提供了方便,事務協調器異常導致的一部分異常事務,不會 block 後面業務的繼續進行。

TCC 模式

TCC模式需要對業務模型進行拆分,把原一次操作成功的業務拆分成兩階段實現。

TCC設計舉例

以“扣錢”場景爲例,在接入 TCC 前,對 A 賬戶的扣錢,只需一條更新賬戶餘額的 SQL 便能完成;但是在接入 TCC 之後,用戶就需要考慮如何將原來一步就能完成的扣錢操作,拆成兩階段,實現成三個方法,並且保證一階段 Try 成功的話 二階段 Confirm 一定能成功。

在這裏插入圖片描述
如上圖所示,Try 方法作爲一階段準備方法,需要做資源的檢查和預留。在扣錢場景下,Try 要做的事情是就是檢查賬戶餘額是否充足,預留轉賬資金,預留的方式就是凍結 A 賬戶的 轉賬資金。Try 方法執行之後,賬號 A 餘額雖然還是 100,但是其中 30 元已經被凍結了,不能被其他事務使用。
二階段 Confirm 方法執行真正的扣錢操作。Confirm 會使用 Try 階段凍結的資金,執行賬號扣款。Confirm 方法執行之後,賬號 A 在一階段中凍結的 30 元已經被扣除,賬號 A 餘額變成 70 元 。
如果二階段是回滾的話,就需要在 Cancel 方法內釋放一階段 Try 凍結的 30 元,使賬號 A 的回到初始狀態,100 元全部可用。
用戶接入 TCC 模式,最重要的事情就是考慮如何將業務模型拆成 2 階段,實現成 TCC 的 3 個方法,並且保證 Try 成功 Confirm 一定能成功。相對於 AT 模式,TCC 模式對業務代碼有一定的侵入性,但是 TCC 模式無 AT 模式的全局行鎖,TCC 性能會比 AT 模式高很多。

使用TCC模式需要注意的問題
允許空回滾
場景:
	- Try 未執行,Cancel 執行了
出現原因:
	- Try 超時(丟包)
	- 分佈式事務回滾觸發 Cancel
	- 未收到 Try,收到 Cancel

Cancel 接口設計時需要允許空回滾。

在 Try 接口因爲丟包時沒有收到,事務管理器會觸發回滾,這時會觸發 Cancel 接口,這時 Cancel 執行時發現沒有對應的事務 xid 或主鍵時,需要返回回滾成功。讓事務服務管理器認爲已回滾,否則會不斷重試,而 Cancel 又沒有對應的業務數據可以進行回滾。

防懸掛控制
場景:
	- Cancel 比 Try 先執行
出現原因:
	- Try超時(擁堵)
	- 分佈式事務回滾觸發 Cancel
	- 擁堵的 Try 到達

此時需要允許空回滾,但要拒絕空回滾後的Try操作

懸掛的意思是:Cancel 比 Try 接口先執行,出現的原因是 Try 由於網絡擁堵而超時,事務管理器生成回滾,觸發 Cancel 接口,而最終又收到了 Try 接口調用,但是 Cancel 比 Try 先到。按照前面允許空回滾的邏輯,回滾會返回成功,事務管理器認爲事務已回滾成功,則此時的 Try 接口不應該執行,否則會產生數據不一致,所以我們在 Cancel 空回滾返回成功之前先記錄該條事務 xid 或業務主鍵,標識這條記錄已經回滾過,Try 接口先檢查這條事務xid或業務主鍵如果已經標記爲回滾成功過,則不執行 Try 的業務操作。

冪等控制

Try、Confirm、Cancel 三個方法均需要保證冪等性。

冪等性的意思是:對同一個系統,使用同樣的條件,一次請求和重複的多次請求對系統資源的影響是一致的。因爲網絡抖動或擁堵可能會超時,事務管理器會對資源進行重試操作,所以很可能一個業務操作會被重複調用,爲了不因爲重複調用而多次佔用資源,需要對服務設計時進行冪等控制,通常我們可以用事務 xid 或業務主鍵判重來控制。

Saga 模式

http://seata.io/zh-cn/docs/user/saga.html

XA 模式

TODO

基於 MQ 實現的『最終一致性』分佈式事務解決方案

基於 MQ實現的『最終一致性』分佈式事務解決方案 不適用強一致性的場景。
比如 系統A是下單服務、系統B是倉儲服務。在下單服務成功後,倉儲服務消費到消息時點無法保證倉儲數據是否滿足要求。
此種解決方案適用於對時延要求不高的場景。比如。系統A爲下單服務、系統B爲積分服務。下單成功後總會保證積分累加成功、但什麼時候成功就不一定了。

基於『本地消息表』和 MQ 實現的『最終一致性』分佈式事務解決方案

在這裏插入圖片描述
執行步驟:

1. 系統A執行本地業務,寫入A系統消息表數據,生成唯一msgId,state設置爲待處理,重複投遞次數設置爲0,此操作保證在一個事務裏。
2. 如果步驟1執行成功,嘗試向MQ發送帶msgId消息,通知系統B進行下一步處理。
	此操作可能出現失敗(MQ宕機、MQ網絡超時等等),不過沒關係,我們已經保存住了任務存根(A系統消息表數據)。
3. 消費者接收數據。
4. 處理消息,在寫入DB時開啓事務,保證業務處理操作和B系統消息表操作同時成功,並且消息表的msgId爲唯一鍵,既能保證冪等,又能記錄消費成功的消息。
5. 定時任務系統C輪詢查詢A系統消息表 state 爲待處理的消息 與 B系統消息表比對。
	A、B同時存在的消息:
		處理成功的消息,修改A表消息狀態爲已處理。
	A存在、B不存在的消息:重複投遞消息,增加A消息表重複投遞次數,次數超過閾值報警。
		可能B處理失敗
		可能A投遞消息失敗
		可能B還沒來得及處理積壓在MQ中
		可能B正在處理消息還沒來得及提交事務

基於『可靠消息隊列(RocketMQ)』實現的『最終一致性』分佈式事務解決方案

在這裏插入圖片描述
執行步驟:

首先,MQ需要開啓持久化,保證消息在MQ環節不丟失

1. 註冊回查監聽
		如果步驟4執行失敗,MQ會定時發送通知詢問是否需要提交或者回滾
		在此監聽中實現查詢步驟3的業務狀態返回給MQ
2. 向MQ發送消息
		消息處於Prepared狀態,拿到msgId
3. 步驟2執行成功後開啓本地事務,執行本地業務
   步驟2執行失敗則流程結束
4. 步驟3執行成功向MQ發送Commit消息,表示可以向下遊投遞
   步驟3執行失敗向MQ發送Rollback消息,取消投遞
		如果步驟4執行失敗,則依靠步驟1中的回查機制來確認消息是否需要投遞
5. 消費者接收到消息投遞
		如果步驟6或者7失敗,這裏會收到重複投遞的消息
6. 消費者開啓本地事務處理消息,並且保證消息的冪等性
7. 手動ACK給MQ,確認消息消費成功
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章