分布式事務( 圖解 + 秒懂 + 史上最全 )


文章很長,建議收藏起來,慢慢讀! Java 高並發 發燒友社群:瘋狂創客圈 奉上以下珍貴的學習資源:


推薦:入大廠 、做架構、大力提升Java 內功 的 精彩博文

入大廠 、做架構、大力提升Java 內功 必備的精彩博文 2021 秋招漲薪1W + 必備的精彩博文
1:Redis 分布式鎖 (圖解-秒懂-史上最全) 2:Zookeeper 分布式鎖 (圖解-秒懂-史上最全)
3: Redis與MySQL雙寫一致性如何保證? (面試必備) 4: 面試必備:秒殺超賣 解決方案 (史上最全)
5:面試必備之:Reactor模式 6: 10分鍾看懂, Java NIO 底層原理
7:TCP/IP(圖解+秒懂+史上最全) 8:Feign原理 (圖解)
9:DNS圖解(秒懂 + 史上最全 + 高薪必備) 10:CDN圖解(秒懂 + 史上最全 + 高薪必備)
11: 分布式事務( 圖解 + 史上最全 + 吐血推薦 ) 12:限流:計數器、漏桶、令牌桶
三大算法的原理與實戰(圖解+史上最全)
13:架構必看:12306搶票系統億級流量架構
(圖解+秒懂+史上最全)
14:seata AT模式實戰(圖解+秒懂+史上最全)
15:seata 源碼解讀(圖解+秒懂+史上最全) 16:seata TCC模式實戰(圖解+秒懂+史上最全)

Java 面試題 30個專題 , 史上最全 , 面試必刷 阿里、京東、美團... 隨意挑、橫着走!!!
1: JVM面試題(史上最強、持續更新、吐血推薦) 2:Java基礎面試題(史上最全、持續更新、吐血推薦
3:架構設計面試題 (史上最全、持續更新、吐血推薦) 4:設計模式面試題 (史上最全、持續更新、吐血推薦)
17、分布式事務面試題 (史上最全、持續更新、吐血推薦) 一致性協議 (史上最全)
29、多線程面試題(史上最全) 30、HR面經,過五關斬六將后,小心陰溝翻船!
9.網絡協議面試題(史上最全、持續更新、吐血推薦) 更多專題, 請參見【 瘋狂創客圈 高並發 總目錄

SpringCloud 微服務 精彩博文
nacos 實戰(史上最全) sentinel (史上最全+入門教程)
SpringCloud gateway (史上最全) 更多專題, 請參見【 瘋狂創客圈 高並發 總目錄

一道問過無數次的面試題

現在Java面試,分布式系統、分布式事務幾乎是標配。而分布式系統、分布式事務本身比較復雜,大家學起來也非常頭疼。

 面試題:分布式事務了解嗎?你們是如何解決分布式事務問題的?  (標准答案:見末尾) 

友情提示:

看完此文,在分布式事務這塊,基本可以做到吊打面試官了。

一圖解讀分布式事務

首先奉上一張全網最為牛逼的圖,給大家做個總覽:

在這里插入圖片描述

名詞解釋

  • 事務:事務是由一組操作構成的可靠的獨立的工作單元,事務具備ACID的特性,即原子性、一致性、隔離性和持久性。
  • 本地事務:當事務由資源管理器本地管理時被稱作本地事務。本地事務的優點就是支持嚴格的ACID特性,高效,可靠,狀態可以只在資源管理器中維護,而且應用編程模型簡單。但是本地事務不具備分布式事務的處理能力,隔離的最小單位受限於資源管理器。
  • 全局事務:當事務由全局事務管理器進行全局管理時成為全局事務,事務管理器負責管理全局的事務狀態和參與的資源,協同資源的一致提交回滾。
  • TX協議:應用或者應用服務器與事務管理器的接口。
  • XA協議:全局事務管理器與資源管理器的接口。XA是由X/Open組織提出的分布式事務規范。該規范主要定義了全局事務管理器和局部資源管理器之間的接口。主流的數據庫產品都實現了XA接口。XA接口是一個雙向的系統接口,在事務管理器以及多個資源管理器之間作為通信橋梁。之所以需要XA是因為在分布式系統中從理論上講兩台機器是無法達到一致性狀態的,因此引入一個單點進行協調。由全局事務管理器管理和協調的事務可以跨越多個資源和進程。全局事務管理器一般使用XA二階段協議與數據庫進行交互。
  • AP:應用程序,可以理解為使用DTP(Data Tools Platform)的程序。
  • RM:資源管理器,這里可以是一個DBMS或者消息服務器管理系統,應用程序通過資源管理器對資源進行控制,資源必須實現XA定義的接口。資源管理器負責控制和管理實際的資源。
  • TM:事務管理器,負責協調和管理事務,提供給AP編程接口以及管理資源管理器。事務管理器控制着全局事務,管理事務的生命周期,並且協調資源。
  • 兩階段提交協議:XA用於在全局事務中協調多個資源的機制。TM和RM之間采取兩階段提交的方案來解決一致性問題。兩節點提交需要一個協調者(TM)來掌控所有參與者(RM)節點的操作結果並且指引這些節點是否需要最終提交。兩階段提交的局限在於協議成本,准備階段的持久成本,全局事務狀態的持久成本,潛在故障點多帶來的脆弱性,准備后,提交前的故障引發一系列隔離與恢復難題。
  • BASE理論:BA指的是基本業務可用性,支持分區失敗,S表示柔性狀態,也就是允許短時間內不同步,E表示最終一致性,數據最終是一致的,但是實時是不一致的。原子性和持久性必須從根本上保障,為了可用性、性能和服務降級的需要,只有降低一致性和隔離性的要求。
  • CAP定理:對於共享數據系統,最多只能同時擁有CAP其中的兩個,任意兩個都有其適應的場景,真是的業務系統中通常是ACID與CAP的混合體。分布式系統中最重要的是滿足業務需求,而不是追求高度抽象,絕對的系統特性。C表示一致性,也就是所有用戶看到的數據是一樣的。A表示可用性,是指總能找到一個可用的數據副本。P表示分區容錯性,能夠容忍網絡中斷等故障。

分布式事務與分布式鎖的區別:

分布式鎖解決的是分布式資源搶占的問題;分布式事務和本地事務是解決流程化提交問題。

事務簡介

事務(Transaction)是操作數據庫中某個數據項的一個程序執行單元(unit)。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。

事務的四個特征:

1、Atomic原子性

事務必須是一個原子的操作序列單元,事務中包含的各項操作在一次執行過程中,要么全部執行成功,要么全部不執行,任何一項失敗,整個事務回滾,只有全部都執行成功,整個事務才算成功。

2、Consistency一致性

事務的執行不能破壞數據庫數據的完整性和一致性,事務在執行之前和之后,數據庫都必須處於一致性狀態。

3、Isolation隔離性

在並發環境中,並發的事務是相互隔離的,一個事務的執行不能被其他事務干擾。即不同的事務並發操縱相同的數據時,每個事務都有各自完整的數據空間,即一個事務內部的操作及使用的數據對其他並發事務是隔離的,並發執行的各個事務之間不能相互干擾。

SQL中的4個事務隔離級別:

(1)讀未提交

允許臟讀。如果一個事務正在處理某一數據,並對其進行了更新,但同時尚未完成事務,因此事務沒有提交,與此同時,允許另一個事務也能夠訪問該數據。例如A將變量n從0累加到10才提交事務,此時B可能讀到n變量從0到10之間的所有中間值。

(2)讀已提交

允許不可重復讀。只允許讀到已經提交的數據。即事務A在將n從0累加到10的過程中,B無法看到n的中間值,之中只能看到10。同時有事務C進行從10到20的累加,此時B在同一個事務內再次讀時,讀到的是20。

(3)可重復讀

允許幻讀。保證在事務處理過程中,多次讀取同一個數據時,其值都和事務開始時刻時是一致的。禁止臟讀、不可重復讀。幻讀即同樣的事務操作,在前后兩個時間段內執行對同一個數據項的讀取,可能出現不一致的結果。保證B在同一個事務內,多次讀取n的值,讀到的都是初始值0。幻讀,就是不同事務,讀到的n的數據可能是0,可能10,可能是20

(4)串行化

 最嚴格的事務,要求所有事務被串行執行,不能並發執行。

如果不對事務進行並發控制,我們看看數據庫並發操作是會有那些異常情形

  • (1)一類丟失更新:兩個事物讀同一數據,一個修改字段1,一個修改字段2,后提交的恢復了先提交修改的字段。
  • (2)二類丟失更新:兩個事物讀同一數據,都修改同一字段,后提交的覆蓋了先提交的修改。
  • (3)臟讀:讀到了未提交的值,萬一該事物回滾,則產生臟讀。
  • (4)不可重復讀:兩個查詢之間,被另外一個事務修改了數據的內容,產生內容的不一致。
  • (5)幻讀:兩個查詢之間,被另外一個事務插入或刪除了記錄,產生結果集的不一致。

4、Durability持久性

持久性(durability):持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中對應數據的狀態變更就應該是永久性的。

即使發生系統崩潰或機器宕機,只要數據庫能夠重新啟動,那么一定能夠將其恢復到事務成功結束時的狀態。

比方說:一個人買東西的時候需要記錄在賬本上,即使老板忘記了那也有據可查。

MySQL的本地事務實現方案

大多數場景下,我們的應用都只需要操作單一的數據庫,這種情況下的事務稱之為本地事務(Local Transaction)。本地事務的ACID特性是數據庫直接提供支持。

了解過MySQL事務的同學,就會知道,為了達成本地事務,MySQL做了很多的工作,比如回滾日志,重做日志,MVCC,讀寫鎖等。

MySQL數據庫的事務實現原理

以MySQL 的InnoDB (InnoDB 是 MySQL 的一個存儲引擎)為例,介紹一下單一數據庫的事務實現原理。

InnoDB 是通過 日志和鎖 來保證的事務的 ACID特性,具體如下:

(1)通過數據庫鎖的機制,保障事務的隔離性;

(2)通過 Redo Log(重做日志)來,保障事務的持久性;

(3)通過 Undo Log (撤銷日志)來,保障事務的原子性;

(4)通過 Undo Log (撤銷日志)來,保障事務的一致性;

Undo Log 如何保障事務的原子性呢?

具體的方式為:在操作任何數據之前,首先將數據備份到一個地方(這個存儲數據備份的地方稱為 Undo Log),然后進行數據的修改。如果出現了錯誤或者用戶執行了 Rollback 語句,系統可以利用 Undo Log 中的備份將數據恢復到事務開始之前的狀態。

Redo Log如何保障事務的持久性呢?

具體的方式為:Redo Log 記錄的是新數據的備份(和 Undo Log 相反)。在事務提交前,只要將 Redo Log 持久化即可,不需要將數據持久化。當系統崩潰時,雖然數據沒有持久化,但是 Redo Log 已經持久化。系統可以根據 Redo Log 的內容,將所有數據恢復到崩潰之前的狀態。

臟讀、幻讀、不可重復讀

在多個事務並發操作時,數據庫中會出現下面三種問題:臟讀,幻讀,不可重復讀

臟讀(Dirty Read

事務A讀到了事務B還未提交的數據:

事務A讀取的數據,事務B對該數據進行修改還未提交數據之前,事務A再次讀取數據會讀到事務B已經修改后的數據,如果此時事務B進行回滾或再次修改該數據然后提交,事務A讀到的數據就是臟數據,這個情況被稱為臟讀(Dirty Read)。

img

幻讀(Phantom Read

事務A進行范圍查詢時,事務B中新增了滿足該范圍條件的記錄,當事務A再次按該條件進行范圍查詢,會查到在事務B中提交的新的滿足條件的記錄(幻行 Phantom Row)。

img

不可重復讀(Unrepeatable Read

事務A在讀取某些數據后,再次讀取該數據,發現讀出的該數據已經在事務B中發生了變更或刪除。

img

幻讀和不可重復度的區別:

  • 幻讀:在同一事務中,相同條件下,兩次查詢出來的 記錄數 不一樣;
  • 不可重復讀:在同一事務中,相同條件下,兩次查詢出來的 數據 不一樣;

事務的隔離級別

為了解決數據庫中事務並發所產生的問題,在標准SQL規范中,定義了四種事務隔離級別,每一種級別都規定了一個事務中所做的修改,哪些在事務內和事務間是可見的,哪些是不可見的。

低級別的隔離級一般支持更高的並發處理,並擁有更低的系統開銷。

MySQL事務隔離級別https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

通過修改MySQL系統參數來控制事務的隔離級別,在MySQL8中該參數為 transaction_isolation ,在MySQL5中該參數為 tx_isolation :

MySQL8:
-- 查看系統隔離級別:
SELECT @@global.transaction_isolation;

-- 查看當前會話隔離級別
SELECT @@transaction_isolation;

-- 設置當前會話事務隔離級別
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 設置全局事務隔離級別
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

事務的四個隔離級別:

  • 未提交讀(READ UNCOMMITTED):所有事務都可以看到其他事務未提交的修改。一般很少使用;

  • 提交讀(READ COMMITTED):Oracle默認隔離級別,事務之間只能看到彼此已提交的變更修改;

  • 可重復讀(REPEATABLE READ):MySQL默認隔離級別,同一事務中的多次查詢會看到相同的數據行;可以解決不可重復讀,但可能出現幻讀;

  • 可串行化(SERIALIZABLE):最高的隔離級別,事務串行的執行,前一個事務執行完,后面的事務會執行。讀取每條數據都會加鎖,會導致大量的超時和鎖爭用問題;

img

問:如何保證 REPEATABLE READ 級別絕對不產生幻讀?

:在SQL中加入 for update (排他鎖) 或 lock in share mode (共享鎖)語句實現。就是鎖住了可能造成幻讀的數據,阻止數據的寫入操作。

分布式事務的基本概念

分布式環境的事務復雜性

當本地事務要擴展到分布式時,它的復雜性進一步增加了。

存儲端的多樣性。

首先就是存儲端的多樣性。本地事務的情況下,所有數據都會落到同一個DB中,但是,在分布式的情況下,就會出現數據可能要落到多個DB,或者還會落到Redis,落到MQ等中。

存儲端多樣性, 如下圖所示:

在這里插入圖片描述

事務鏈路的延展性

本地事務的情況下,通常所有事務相關的業務操作,會被我們封裝到一個Service方法中。而在分布式的情況下,請求鏈路被延展,拉長,一個操作會被拆分成多個服務,它們呈現線狀或網狀,依靠網絡通信構建成一個整體。在這種情況下,事務無疑變得更復雜。

事務鏈路延展性, 如下圖所示:

在這里插入圖片描述

基於上述兩個復雜性,期望有一個統一的分布式事務方案,能夠像本地事務一樣,以幾乎無侵入的方式,滿足各種存儲介質,各種復雜鏈路,是不現實的。

至少,在當前,還沒有一個十分成熟的解決方案。所以,一般情況下,在分布式下,事務會被拆分解決,並根據不同的情況,采用不同的解決方案。

什么是分布式事務?

對於分布式系統而言,需要保證分布式系統中的數據一致性,保證數據在子系統中始終保持一致,避免業務出現問題。分布式系統中對數要么一起成功,要么一起失敗,必須是一個整體性的事務。

分布式事務指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分布式系統的不同節點之上。

簡單的說,在分布式系統上一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務節點上,且屬於不同的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗。

舉個例子:在電商網站中,用戶對商品進行下單,需要在訂單表中創建一條訂單數據,同時需要在庫存表中修改當前商品的剩余庫存數量,兩步操作一個添加,一個修改,我們一定要保證這兩步操作一定同時操作成功或失敗,否則業務就會出現問題。

任何事務機制在實現時,都應該考慮事務的ACID特性,包括:本地事務、分布式事務。對於分布式事務而言,即使不能都很好的滿足,也要考慮支持到什么程度。

典型的分布式事務場景:

1. 跨庫事務

跨庫事務指的是,一個應用某個功能需要操作多個庫,不同的庫中存儲不同的業務數據。筆者見過一個相對比較復雜的業務,一個業務中同時操作了9個庫。

下圖演示了一個服務同時操作2個庫的情況:
在這里插入圖片描述

2. 分庫分表

通常一個庫數據量比較大或者預期未來的數據量比較大,都會進行水平拆分,也就是分庫分表。

如下圖,將數據庫B拆分成了2個庫:

在這里插入圖片描述

對於分庫分表的情況,一般開發人員都會使用一些數據庫中間件來降低sql操作的復雜性。

如,對於sql:insert into user(id,name) values (1,"tianshouzhi"),(2,"wangxiaoxiao")。這條sql是操作單庫的語法,單庫情況下,可以保證事務的一致性。

但是由於現在進行了分庫分表,開發人員希望將1號記錄插入分庫1,2號記錄插入分庫2。所以數據庫中間件要將其改寫為2條sql,分別插入兩個不同的分庫,此時要保證兩個庫要不都成功,要不都失敗,因此基本上所有的數據庫中間件都面臨着分布式事務的問題。

3. 微服務化

微服務架構是目前一個比較一個比較火的概念。例如上面筆者提到的一個案例,某個應用同時操作了9個庫,這樣的應用業務邏輯必然非常復雜,對於開發人員是極大的挑戰,應該拆分成不同的獨立服務,以簡化業務邏輯。拆分后,獨立服務之間通過RPC框架來進行遠程調用,實現彼此的通信。下圖演示了一個3個服務之間彼此調用的架構:

在這里插入圖片描述

Service A完成某個功能需要直接操作數據庫,同時需要調用Service B和Service C,而Service B又同時操作了2個數據庫,Service C也操作了一個庫。需要保證這些跨服務的對多個數據庫的操作要不都成功,要不都失敗,實際上這可能是最典型的分布式事務場景。

分布式事務實現方案必須要考慮性能的問題,如果為了嚴格保證ACID特性,導致性能嚴重下降,那么對於一些要求快速響應的業務,是無法接受的。

CAP定理

分布式事務的理論基礎

數據庫事務ACID 四大特性,無法滿足分布式事務的實際需求,這個時候又有一些新的大牛提出一些新的理論。

CAP定理

CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出WEB服務無法同時滿足一下3個屬性:

  • 一致性(Consistency) : 客戶端知道一系列的操作都會同時發生(生效)
  • 可用性(Availability) : 每個操作都必須以可預期的響應結束
  • 分區容錯性(Partition tolerance) : 即使出現單個組件無法可用,操作依然可以完成

具體地講在分布式系統中,一個Web應用至多只能同時支持上面的兩個屬性。因此,設計人員必須在一致性與可用性之間做出選擇。

2000年7月Eric Brewer教授僅僅提出來的是一個猜想,2年后,麻省理工學院的Seth Gilbert和Nancy Lynch從理論上證明了CAP理論,並且而一個分布式系統最多只能滿足CAP中的2項。之后,CAP理論正式成為分布式計算領域的公認定理。

在這里插入圖片描述

所以,CAP定理在迄今為止的分布式系統中都是適用的!

CAP的一致性、可用性、分區容錯性 具體如下:

1、一致性

數據一致性指“all nodes see the same data at the same time”,即更新操作成功並返回客戶端完成后,所有節點在同一時間的數據完全一致,不能存在中間狀態。

分布式環境中,一致性是指多個副本之間能否保持一致的特性。在一致性的需求下,當一個系統在數據一致的狀態下執行更新操作后,應該保證系統的數據仍然處理一致的狀態。

例如對於電商系統用戶下單操作,庫存減少、用戶資金賬戶扣減、積分增加等操作必須在用戶下單操作完成后必須是一致的。不能出現類似於庫存已經減少,而用戶資金賬戶尚未扣減,積分也未增加的情況。如果出現了這種情況,那么就認為是不一致的。

數據一致性分為強一致性、弱一致性、最終一致性

  • 如果的確能像上面描述的那樣時刻保證客戶端看到的數據都是一致的,那么稱之為強一致性。
  • 如果允許存在中間狀態,只要求經過一段時間后,數據最終是一致的,則稱之為最終一致性。
  • 此外,如果允許存在部分數據不一致,那么就稱之為弱一致性。
面試題:什么是數據一致性?  現在知道怎么回答了吧!

2、可用性

系統提供的服務必須一直處於可用的狀態,對於用戶的每一個操作請求總是能夠在有限的時間內返回結果。

兩個度量的維度:

(1)有限時間內
對於用戶的一個操作請求,系統必須能夠在指定的時間(響應時間)內返回對應的處理結果,如果超過了這個時間范圍,那么系統就被認為是不可用的。即這個響應時間必須在一個合理的值內,不讓用戶感到失望。

試想,如果一個下單操作,為了保證分布式事務的一致性,需要10分鍾才能處理完,那么用戶顯然是無法忍受的。

(2)返回正常結果
要求系統在完成對用戶請求的處理后,返回一個正常的響應結果。正常的響應結果通常能夠明確地反映出對請求的處理結果,即成功或失敗,而不是一個讓用戶感到困惑的返回結果。比如返回一個系統錯誤如OutOfMemory,則認為系統是不可用的。

“返回結果”是可用性的另一個非常重要的指標,它要求系統在完成對用戶請求的處理后,返回一個正常的響應結果,不論這個結果是成功還是失敗。

3、分區容錯性

即分布式系統在遇到任何網絡分區故障時,仍然需要能夠保證對外提供滿足一致性和可用性的服務,除非是整個網絡環境都發生了故障。

網絡分區,是指分布式系統中,不同的節點分布在不同的子網絡(機房/異地網絡)中,由於一些特殊的原因導致這些子網絡之間出現網絡不連通的狀態,但各個子網絡的內部網絡是正常的,從而導致整個系統的網絡環境被切分成了若干孤立的區域。組成一個分布式系統的每個節點的加入與退出都可以看做是一個特殊的網絡分區。

CAP的應用

1、放棄P

放棄分區容錯性的話,則放棄了分布式,放棄了系統的可擴展性

2、放棄A

放棄可用性的話,則在遇到網絡分區或其他故障時,受影響的服務需要等待一定的時間,再此期間無法對外提供政策的服務,即不可用

3、放棄C

放棄一致性的話(這里指強一致),則系統無法保證數據保持實時的一致性,在數據達到最終一致性時,有個時間窗口,在時間窗口內,數據是不一致的。

對於分布式系統來說,P是不能放棄的,因此架構師通常是在可用性和一致性之間權衡。

CAP 理論告訴我們:

目前很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的話題。

基於 CAP理論,很多系統在設計之初就要對這三者做出取舍:

任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性,系統往往只需要保證最終一致性。

問:為什么分布式系統中無法同時保證一致性和可用性?

答:首先一個前提,對於分布式系統而言,分區容錯性是一個最基本的要求,因此基本上我們在設計分布式系統的時候只能從一致性(C)和可用性(A)之間進行取舍。

如果保證了一致性(C):對於節點N1和N2,當往N1里寫數據時,N2上的操作必須被暫停,只有當N1同步數據到N2時才能對N2進行讀寫請求,在N2被暫停操作期間客戶端提交的請求會收到失敗或超時。顯然,這與可用性是相悖的。

如果保證了可用性(A):那就不能暫停N2的讀寫操作,但同時N1在寫數據的話,這就違背了一致性的要求。

CAP 權衡

通過 CAP 理論,我們知道無法同時滿足一致性、可用性和分區容錯性這三個特性,那要舍棄哪個呢?

對於多數大型互聯網應用的場景,主機眾多、部署分散,而且現在的集群規模越來越大,所以節點故障、網絡故障是常態,而且要保證服務可用性達到 N 個 9,即保證 P 和 A,舍棄C(退而求其次保證最終一致性)。雖然某些地方會影響客戶體驗,但沒達到造成用戶流程的嚴重程度。

對於涉及到錢財這樣不能有一絲讓步的場景,C 必須保證。網絡發生故障寧可停止服務,這是保證 CA,舍棄 P。貌似這幾年國內銀行業發生了不下 10 起事故,但影響面不大,報道也不多,廣大群眾知道的少。還有一種是保證 CP,舍棄 A。例如網絡故障是只讀不寫。

CAP和ACID中的A和C是完全不一樣的

A的區別:

  • ACID中的A指的是原子性(Atomicity),是指事務被視為一個不可分割的最小工作單元,事務中的所有操作要么全部提交成功,要么全部失敗回滾;
  • CAP中的A指的是可用性(Availability),是指集群中一部分節點故障后,集群整體是否還能響應客戶端的讀寫請求;

C的區別:

  • ACID一致性是有關數據庫規則,數據庫總是從一個一致性的狀態轉換到另外一個一致性的狀態;
  • CAP的一致性是分布式多服務器之間復制數據令這些服務器擁有同樣的數據,由於網速限制,這種復制在不同的服務器上所消耗的時間是不固定的,集群通過組織客戶端查看不同節點上還未同步的數據維持邏輯視圖,這是一種分布式領域的一致性概念;

總之:

ACID里的一致性指的是事務執行前后,數據庫完整性,而CAP的一致性,指的是分布式節點的數據的一致性。背景不同,無從可比

BASE定理

CAP是分布式系統設計理論,BASE是CAP理論中AP方案的延伸,對於C我們采用的方式和策略就是保證最終一致性;

eBay的架構師Dan Pritchett源於對大規模分布式系統的實踐總結,在ACM上發表文章提出BASE理論,BASE理論是對CAP理論的延伸,核心思想是即使無法做到強一致性(StrongConsistency,CAP的一致性就是強一致性),但應用可以采用適合的方式達到最終一致性(Eventual Consitency)。

BASE定理

BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的縮寫。BASE基於CAP定理演化而來,核心思想是即時無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性。

1、Basically Available(基本可用)

基本可用是指分布式系統在出現不可預知的故障的時候,允許損失部分可用性,但不等於系統不可用。

(1)響應時間上的損失

當出現故障時,響應時間增加

(2)功能上的損失

   當流量高峰期時,屏蔽一些功能的使用以保證系統穩定性(服務降級)

2、Soft state(軟狀態)

指允許系統中的數據存在中間狀態,並認為該中間狀態的存在不會影響系統的整體可用性。

與硬狀態相對,即是指允許系統中的數據存在中間狀態,並認為該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程存在延時。

3、Eventually consistent(最終一致性)

強調系統中所有的數據副本,在經過一段時間的同步后,最終能夠達到一個一致的狀態。其本質是需要系統保證最終數據能夠達到一致,而不需要實時保證系統數據的強一致性。

最終一致性可分為如下幾種:
  • (1)因果一致性(Causal consistency)
    即進程A在更新完數據后通知進程B,那么之后進程B對該項數據的范圍都是進程A更新后的最新值。
  • (2)讀己之所寫(Read your writes)
    進程A更新一項數據后,它自己總是能訪問到自己更新過的最新值。
  • (3)會話一致性(Session consistency)
    將數據一致性框定在會話當中,在一個會話當中實現讀己之所寫的一致性。即執行更新后,客戶端在同一個會話中始終能讀到該項數據的最新值
  • (4)單調讀一致性(Monotonic read consistency)
    如果一個進程從系統中讀取出一個數據項的某個值后,那么系統對於該進程后續的任何數據訪問都不應該返回更舊的值。
  • (5)單調寫一致性(Monotoic write consistency)
    一個系統需要保證來自同一個進程的寫操作被順序執行。

BASE理論是提出通過犧牲一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。

BASE理論的特點:

BASE理論面向的是大型高可用可擴展的分布式系統,和傳統的事物ACID特性是相反的。

它完全不同於ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。

但同時,在實際的分布式場景中,不同業務單元和組件對數據一致性的要求是不同的,因此在具體的分布式系統架構設計過程中,ACID特性和BASE理論往往又會結合在一起。

BASE理論與CAP的關系

BASE理論是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網系統分布式實踐的總結, 是基於CAP定理逐步演化而來的。BASE理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性

BASE理論其實就是對CAP理論的延伸和補充,主要是對AP的補充。犧牲數據的強一致性,來保證數據的可用性,雖然存在中間裝填,但數據最終一致。

ACID 和 BASE 的區別與聯系

ACID 是傳統數據庫常用的設計理念,追求強一致性模型。BASE 支持的是大型分布式系統,提出通過犧牲強一致性獲得高可用性。

ACID 和 BASE 代表了兩種截然相反的設計哲學,在分布式系統設計的場景中,系統組件對一致性要求是不同的,因此 ACID 和 BASE 又會結合使用。

分布式事務分類:柔性事務和剛性事務

分布式場景下,多個服務同時對服務一個流程,比如電商下單場景,需要支付服務進行支付、庫存服務扣減庫存、訂單服務進行訂單生成、物流服務更新物流信息等。如果某一個服務執行失敗,或者網絡不通引起的請求丟失,那么整個系統可能出現數據不一致的原因。

上述場景就是分布式一致性問題,追根到底,分布式一致性的根本原因在於數據的分布式操作,引起的本地事務無法保障數據的原子性引起。

分布式一致性問題的解決思路有兩種,一種是分布式事務,一種是盡量通過業務流程避免分布式事務。分布式事務是直接解決問題,而業務規避其實通過解決出問題的地方(解決提問題的人)。其實在真實業務場景中,如果業務規避不是很麻煩的前提,最優雅的解決方案就是業務規避。

分布式事務分類

分布式事務實現方案從類型上去分剛性事務、柔型事務:

  • 剛性事務滿足CAP的CP理論

  • 柔性事務滿足BASE理論(基本可用,最終一致)

剛性事務

剛性事務:通常無業務改造,強一致性,原生支持回滾/隔離性,低並發,適合短事務。

原則:剛性事務滿足足CAP的CP理論

剛性事務指的是,要使分布式事務,達到像本地式事務一樣,具備數據強一致性,從CAP來看,就是說,要達到CP狀態。

剛性事務:XA 協議(2PC、JTA、JTS)、3PC,但由於同步阻塞,處理效率低,不適合大型網站分布式場景。

柔性事務

柔性事務指的是,不要求強一致性,而是要求最終一致性,允許有中間狀態,也就是Base理論,換句話說,就是AP狀態。

與剛性事務相比,柔性事務的特點為:有業務改造,最終一致性,實現補償接口,實現資源鎖定接口,高並發,適合長事務。

柔性事務分為:

  • 補償型
  • 異步確保型
  • 最大努力通知型。

柔型事務:TCC/FMT、Saga(狀態機模式、Aop模式)、本地事務消息、消息事務(半消息)

剛性事務:XA模型、XA接口規范、XA實現

XA模型 或者 X/Open DTP模型

X/OPEN是一個組織.X/Open國際聯盟有限公司是一個歐洲基金會,它的建立是為了向UNIX環境提供標准。它主要的目標是促進對UNIX語言、接口、網絡和應用的開放式系統協議的制定。它還促進在不同的UNIX環境之間的應用程序的互操作性,以及支持對電氣電子工程師協會(IEEE)對UNIX的可移植操作系統接口(POSIX)規范。

X/Open DTP(Distributed Transaction Process) 是一個分布式事務模型。這個模型主要使用了兩段提交(2PC - Two-Phase-Commit)來保證分布式事務的完整性。

在X/Open DTP(Distributed Transaction Process)模型里面,有三個角色:

​ AP: Application,應用程序。也就是業務層。哪些操作屬於一個事務,就是AP定義的。

​ TM: Transaction Manager,事務管理器。接收AP的事務請求,對全局事務進行管理,管理事務分支狀態,協調RM的處理,通知RM哪些操作屬於哪些全局事務以及事務分支等等。這個也是整個事務調度模型的核心部分。

​ RM:Resource Manager,資源管理器。一般是數據庫,也可以是其他的資源管理器,如消息隊列(如JMS數據源),文件系統等。

在這里插入圖片描述

XA把參與事務的角色分成AP,RM,TM。

AP,即應用,也就是我們的業務服務。

RM指的是資源管理器,即DB,MQ等。

TM則是事務管理器。

AP自己操作TM,當需要事務時,AP向TM請求發起事務,TM負責整個事務的提交,回滾等。

XA規范主要定義了(全局)事務管理器(Transaction Manager)和(局部)資源管理器(Resource Manager)之間的接口。XA接口是雙向的系統接口,在事務管理器(Transaction Manager)以及一個或多個資源管理器(Resource Manager)之間形成通信橋梁。

XA之所以需要引入事務管理器是因為,在分布式系統中,從理論上講(參考Fischer等的論文),兩台機器理論上無法達到一致的狀態,需要引入一個單點進行協調。事務管理器控制着全局事務,管理事務生命周期,並協調資源。資源管理器負責控制和管理實際資源(如數據庫或JMS隊列)

在這里插入圖片描述

XA規范

什么是XA

用非常官方的話來說:

  • XA 規范 是 X/Open 組織定義的分布式事務處理(DTP,Distributed Transaction Processing)標准。

  • XA 規范 描述了全局的事務管理器與局部的資源管理器之間的接口。 XA規范 的目的是允許的多個資源(如數據庫,應用服務器,消息隊列等)在同一事務中訪問,這樣可以使 ACID 屬性跨越應用程序而保持有效。

  • XA 規范 使用兩階段提交(2PC,Two-Phase Commit)協議來保證所有資源同時提交或回滾任何特定的事務。

  • XA 規范 在上世紀 90 年代初就被提出。目前,幾乎所有主流的數據庫都對 XA 規范 提供了支持。

XA規范(XA Specification) 是X/OPEN 提出的分布式事務處理規范。XA則規范了TM與RM之間的通信接口,在TM與多個RM之間形成一個雙向通信橋梁,從而在多個數據庫資源下保證ACID四個特性。目前知名的數據庫,如Oracle, DB2,mysql等,都是實現了XA接口的,都可以作為RM。

XA是數據庫的分布式事務,強一致性,在整個過程中,數據一張鎖住狀態,即從prepare到commit、rollback的整個過程中,TM一直把持折數據庫的鎖,如果有其他人要修改數據庫的該條數據,就必須等待鎖的釋放,存在長事務風險。

以下的函數使事務管理器可以對資源管理器進行的操作
1)xa_open,xa_close:建立和關閉與資源管理器的連接。
2)xa_start,xa_end:開始和結束一個本地事務。
3)xa_prepare,xa_commit,xa_rollback:預提交、提交和回滾一個本地事務。
4)xa_recover:回滾一個已進行預提交的事務。
5)ax_開頭的函數使資源管理器可以動態地在事務管理器中進行注冊,並可以對XID(TRANSACTION IDS)進行操作。
6)ax_reg,ax_unreg;允許一個資源管理器在一個TMS(TRANSACTION MANAGER SERVER)中動態注冊或撤消注冊。

XA各個階段的處理流程

img

XA協議的實現

在這里插入圖片描述

2PC/3PC協議

兩階段提交(2PC)協議是XA規范定義的 數據一致性協議。

三階段提交(3PC)協議對 2PC協議的一種擴展。

Seata

Seata , 官網 , github , 1萬多星

Seata 是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務模式

在 Seata 開源之前,Seata 對應的內部版本在阿里經濟體內部一直扮演着分布式一致性中間件的角色,幫助經濟體平穩的度過歷年的雙11,對各BU業務進行了有力的支撐。商業化產品GTS 先后在阿里雲、金融雲進行售賣

Jta規范

作為java平台上事務規范 JTA(Java Transaction API)也定義了對XA事務的支持,實際上,JTA是基於XA架構上建模的,在JTA 中,事務管理器抽象為javax.transaction.TransactionManager接口,並通過底層事務服務(即JTS)實現。像很多其他的java規范一樣,JTA僅僅定義了接口,具體的實現則是由供應商(如J2EE廠商)負責提供,目前JTA的實現主要由以下幾種:
1.J2EE容器所提供的JTA實現(JBoss)
2.獨立的JTA實現:如JOTM,Atomikos.

這些實現可以應用在那些不使用J2EE應用服務器的環境里用以提供分布事事務保證。如Tomcat,Jetty以及普通的java應用。

JTS規范

事務是編程中必不可少的一項內容,基於此,為了規范事務開發,Java增加了關於事務的規范,即JTA和JTS

JTA定義了一套接口,其中約定了幾種主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,並定義了這些角色之間需要遵守的規范,如Transaction的委托給TransactionManager等。

JTS也是一組規范,上面提到JTA中需要角色之間的交互,那應該如何交互?JTS就是約定了交互細節的規范。

總體上來說JTA更多的是從框架的角度來約定程序角色的接口,而JTS則是從具體實現的角度來約定程序角色之間的接口,兩者各司其職。

Atomikos分布式事務實現

Atomikos公司旗下有兩款著名的分布事務產品:

  • TransactionEssentials:開源的免費產品
  • ExtremeTransactions:商業版,需要收費

這兩個產品的關系如下圖所示:

1625812180865

可以看到,在開源版本中支持JTA/XA、JDBC、JMS的事務。

atomikos也支持與spring事務整合。

spring事務管理器的頂級抽象是PlatformTransactionManager接口,其提供了個重要的實現類:

  • DataSourceTransactionManager:用於實現本地事務
  • JTATransactionManager:用於實現分布式事務

顯然,在這里,我們需要配置的是JTATransactionManager。

public class JTAService {  
    @Autowired   
    private UserMapper userMapper;//操作db_user庫   
    @Autowired  
    private AccountMapper accountMapper;//操作db_account庫  

    @Transactional   
    public void insert() {    
        User user = new User();     
        user.setName("wangxiaoxiao");     
        userMapper.insert(user);  
        //模擬異常,spring回滾后,db_user庫中user表中也不會插入記錄     
        Account account = new Account();     
        account.setUserId(user.getId());    
        account.setMoney(123456789);    
        accountMapper.insert(account); 
    }
}

XA的主要限制

  • 必須要拿到所有數據源,而且數據源還要支持XA協議。目前MySQL中只有InnoDB存儲引擎支持XA協議。
  • 性能比較差,要把所有涉及到的數據都要鎖定,是強一致性的,會產生長事務。

Seata AT 模式

Seata AT 模式是增強型2pc模式。

AT 模式: 兩階段提交協議的演變,沒有一直鎖表

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

LCN(2pc)

TX-LCN , 官方文檔github , 3千多星 , 5.0以后由於框架兼容了LCN(2pc)、TCC、TXC 三種事務模式,為了區分LCN模式,特此將LCN分布式事務改名為TX-LCN分布式事務框架。

TX-LCN定位於一款事務協調性框架,框架其本身並不生產事務,而是本地事務的協調者,從而達到事務一致性的效果。

TX-LCN 主要有兩個模塊,Tx-Client(TC) ,Tx-Manager™.

  • TM (Tx-Manager):是獨立的服務,是分布式事務的控制方,協調分布式事務的提交,回滾
  • TC(Tx-Client):由業務系統集成,事務發起方、參與方都由TxClient端來控制

2PC(標准XA模型)

2PC即Two-Phase Commit,二階段提交。

詳解:二個階段

廣泛應用在數據庫領域,為了使得基於分布式架構的所有節點可以在進行事務處理時能夠保持原子性和一致性。絕大部分關系型數據庫,都是基於2PC完成分布式的事務處理。

顧名思義,2PC分為兩個階段處理,階段一:提交事務請求、階段二:執行事務提交;

如果階段一超時或者出現異常,2PC的階段二:中斷事務

階段一:提交事務請求

  1. 事務詢問。協調者向所有參與者發送事務內容,詢問是否可以執行提交操作,並開始等待各參與者進行響應;
  2. 執行事務。各參與者節點,執行事務操作,並將Undo和Redo操作計入本機事務日志;
  3. 各參與者向協調者反饋事務問詢的響應。成功執行返回Yes,否則返回No。

階段二:執行事務提交

協調者在階段二決定是否最終執行事務提交操作。這一階段包含兩種情形:

執行事務提交
所有參與者reply Yes,那么執行事務提交。

  1. 發送提交請求。協調者向所有參與者發送Commit請求;
  2. 事務提交。參與者收到Commit請求后,會正式執行事務提交操作,並在完成提交操作之后,釋放在整個事務執行期間占用的資源;
  3. 反饋事務提交結果。參與者在完成事務提交后,寫協調者發送Ack消息確認;
  4. 完成事務。協調者在收到所有參與者的Ack后,完成事務。

事務提交示意圖

階段二:中斷事務

事情總會出現意外,當存在某一參與者向協調者發送No響應,或者等待超時。協調者只要無法收到所有參與者的Yes響應,就會中斷事務。

  1. 發送回滾請求。協調者向所有參與者發送Rollback請求;
  2. 回滾。參與者收到請求后,利用本機Undo信息,執行Rollback操作。並在回滾結束后釋放該事務所占用的系統資源;
  3. 反饋回滾結果。參與者在完成回滾操作后,向協調者發送Ack消息;
  4. 中斷事務。協調者收到所有參與者的回滾Ack消息后,完成事務中斷。

事務中斷示意圖

2pc解決的是分布式數據強一致性問題

顧名思義,兩階段提交在處理分布式事務時分為兩個階段:voting(投票階段,有的地方會叫做prepare階段)和commit階段。

2pc中存在兩個角色,事務協調者(seata、atomikos、lcn)和事務參與者,事務參與者通常是指應用的數據庫。

img

2PC二階段提交的特點

2PC方案比較適合單體應用

2PC 方案中,有一個事務管理器的角色,負責協調多個數據庫(資源管理器)的事務,事務管理器先問問各個數據庫你准備好了嗎?如果每個數據庫都回復 ok,那么就正式提交事務,在各個數據庫上執行操作;如果任何其中一個數據庫回答不 ok,那么就回滾事務。

在這里插入圖片描述

2PC 方案比較適合單體應用里,跨多個庫的分布式事務,而且因為嚴重依賴於數據庫層面來搞定復雜的事務,效率很低,絕對不適合高並發的場景。

2PC 方案實際很少用,一般來說某個系統內部如果出現跨多個庫的這么一個操作,是不合規的。我可以給大家介紹一下, 現在微服務,一個大的系統分成幾百個服務,幾十個服務。一般來說,我們的規定和規范,是要求每個服務只能操作自己對應的一個數據庫。

如果你要操作別的服務對應的庫,不允許直連別的服務的庫,違反微服務架構的規范,你隨便交叉胡亂訪問,幾百個服務的話,全體亂套,這樣的一套服務是沒法管理的,沒法治理的,可能會出現數據被別人改錯,自己的庫被別人寫掛等情況。

如果你要操作別人的服務的庫,你必須是通過調用別的服務的接口來實現,絕對不允許交叉訪問別人的數據庫。

2PC具有明顯的優缺點:

優點主要體現在實現原理簡單;
缺點比較多:

  • 2PC的提交在執行過程中,所有參與事務操作的邏輯都處於阻塞狀態,也就是說,各個參與者都在等待其他參與者響應,無法進行其他操作;
  • 協調者是個單點,一旦出現問題,其他參與者將無法釋放事務資源,也無法完成事務操作;
  • 數據不一致。當執行事務提交過程中,如果協調者向所有參與者發送Commit請求后,發生局部網絡異常或者協調者在尚未發送完Commit請求,即出現崩潰,最終導致只有部分參與者收到、執行請求。於是整個系統將會出現數據不一致的情形;
  • 保守。2PC沒有完善的容錯機制,當參與者出現故障時,協調者無法快速得知這一失敗,只能嚴格依賴超時設置來決定是否進一步的執行提交還是中斷事務。

實際上分布式事務是一件非常復雜的事情,兩階段提交只是通過增加了事務協調者(Coordinator)的角色來通過2個階段的處理流程來解決分布式系統中一個事務需要跨多個服務節點的數據一致性問題。但是從異常情況上考慮,這個流程也並不是那么的無懈可擊。

假設如果在第二個階段中Coordinator在接收到Partcipant的"Vote_Request"后掛掉了或者網絡出現了異常,那么此時Partcipant節點就會一直處於本地事務掛起的狀態,從而長時間地占用資源。當然這種情況只會出現在極端情況下,然而作為一套健壯的軟件系統而言,異常Case的處理才是真正考驗方案正確性的地方。

總結一下: XA-兩階段提交協議中會遇到的一些問題

  • 性能問題

從流程上我們可以看得出,其最大缺點就在於它的執行過程中間,節點都處於阻塞狀態。各個操作數據庫的節點此時都占用着數據庫資源,只有當所有節點准備完畢,事務協調者才會通知進行全局提交,參與者進行本地事務提交后才會釋放資源。這樣的過程會比較漫長,對性能影響比較大。

  • 協調者單點故障問題

事務協調者是整個XA模型的核心,一旦事務協調者節點掛掉,會導致參與者收不到提交或回滾的通知,從而導致參與者節點始終處於事務無法完成的中間狀態。

  • 丟失消息導致的數據不一致問題

在第二個階段,如果發生局部網絡問題,一部分事務參與者收到了提交消息,另一部分事務參與者沒收到提交消息,那么就會導致節點間數據的不一致問題。

3PC

針對2PC的缺點,研究者提出了3PC,即Three-Phase Commit。

作為2PC的改進版,3PC將原有的兩階段過程,重新划分為CanCommit、PreCommit和do Commit三個階段。

詳解:三個階段

三階段提交示意圖

階段一:CanCommit

  1. 事務詢問。協調者向所有參與者發送包含事務內容的canCommit的請求,詢問是否可以執行事務提交,並等待應答;
  2. 各參與者反饋事務詢問。正常情況下,如果參與者認為可以順利執行事務,則返回Yes,否則返回No。

階段二:PreCommit

在本階段,協調者會根據上一階段的反饋情況來決定是否可以執行事務的PreCommit操作。有以下兩種可能:

執行事務預提交

  1. 發送預提交請求。協調者向所有節點發出PreCommit請求,並進入prepared階段;
  2. 事務預提交。參與者收到PreCommit請求后,會執行事務操作,並將Undo和Redo日志寫入本機事務日志;
  3. 各參與者成功執行事務操作,同時將反饋以Ack響應形式發送給協調者,同事等待最終的Commit或Abort指令。

中斷事務
加入任意一個參與者向協調者發送No響應,或者等待超時,協調者在沒有得到所有參與者響應時,即可以中斷事務:

  1. 發送中斷請求。 協調者向所有參與者發送Abort請求;
  2. 中斷事務。無論是收到協調者的Abort請求,還是等待協調者請求過程中出現超時,參與者都會中斷事務;

階段三:doCommit

在這個階段,會真正的進行事務提交,同樣存在兩種可能。

執行提交

  1. 發送提交請求。假如協調者收到了所有參與者的Ack響應,那么將從預提交轉換到提交狀態,並向所有參與者,發送doCommit請求;
  2. 事務提交。參與者收到doCommit請求后,會正式執行事務提交操作,並在完成提交操作后釋放占用資源;
  3. 反饋事務提交結果。參與者將在完成事務提交后,向協調者發送Ack消息;
  4. 完成事務。協調者接收到所有參與者的Ack消息后,完成事務。

中斷事務
在該階段,假設正常狀態的協調者接收到任一個參與者發送的No響應,或在超時時間內,仍舊沒收到反饋消息,就會中斷事務:

  1. 發送中斷請求。協調者向所有的參與者發送abort請求;

  2. 事務回滾。參與者收到abort請求后,會利用階段二中的Undo消息執行事務回滾,並在完成回滾后釋放占用資源;

  3. 反饋事務回滾結果。參與者在完成回滾后向協調者發送Ack消息;

  4. 中端事務。協調者接收到所有參與者反饋的Ack消息后,完成事務中斷。

2PC和3PC的區別:

3PC有效降低了2PC帶來的參與者阻塞范圍,並且能夠在出現單點故障后繼續達成一致;
但3PC帶來了新的問題,在參與者收到preCommit消息后,如果網絡出現分區,協調者和參與者無法進行后續的通信,這種情況下,參與者在等待超時后,依舊會執行事務提交,這樣會導致數據的不一致。

2PC和3PC的區別:

三階段提交協議在協調者和參與者中都引入 超時機制,並且把兩階段提交協議的第一個階段拆分成了兩步:詢問,然后再鎖資源,最后真正提交。三階段提交的三個階段分別為:can_commit,pre_commit,do_commit。

img

在doCommit階段,如果參與者無法及時接收到來自協調者的doCommit或者abort請求時,會在等待超時之后,繼續進行事務的提交。(其實這個應該是基於概率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那么協調者產生PreCommit請求的前提條件是他在第二階段開始之前,收到所有參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了)所以,一句話概括就是,當進入第三階段時, 由於網絡超時等原因,雖然參與者沒有收到commit或者abort響應,但是他有理由相信:成功提交的幾率很大。 )

3PC主要解決的單點故障問題:

相對於2PC,3PC主要解決的單點故障問題,並減少阻塞, 因為一旦參與者無法及時收到來自協調者的信息之后,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態

但是這種機制也會導致數據一致性問題,因為,由於網絡原因,協調者發送的abort響應沒有及時被參與者接收到,那么參與者在等待超時之后執行了commit操作。這樣就和其他接到abort命令並執行回滾的參與者之間存在數據不一致的情況。

"3PC相對於2PC而言到底優化了什么地方呢?"

相比較2PC而言,3PC對於協調者(Coordinator)和參與者(Partcipant)都設置了超時時間,而2PC只有協調者才擁有超時機制。這解決了一個什么問題呢?

這個優化點,主要是避免了參與者在長時間無法與協調者節點通訊(協調者掛掉了)的情況下,無法釋放資源的問題,因為參與者自身擁有超時機制會在超時后,自動進行本地commit從而進行釋放資源。而這種機制也側面降低了整個事務的阻塞時間和范圍。

另外,通過CanCommit、PreCommit、DoCommit三個階段的設計,相較於2PC而言,多設置了一個緩沖階段保證了在最后提交階段之前各參與節點的狀態是一致的。

以上就是3PC相對於2PC的一個提高(相對緩解了2PC中的前兩個問題),但是3PC依然沒有完全解決數據不一致的問題。假如在 DoCommit 過程,參與者A無法接收協調者的通信,那么參與者A會自動提交,但是提交失敗了,其他參與者成功了,此時數據就會不一致。

XA規范的問題

但是XA規范在1994年就出現了,至今沒有大規模流行起來,必然有他一定的缺陷:

  1. 數據鎖定:數據在事務未結束前,為了保障一致性,根據數據隔離級別進行鎖定。

  2. 協議阻塞:本地事務在全局事務 沒 commit 或 callback前都是阻塞等待的。

  3. 性能損耗高:主要體現在事務協調增加的RT成本,並發事務數據使用鎖進行競爭阻塞。

XA協議比較簡單,而且一旦商業數據庫實現了XA協議,使用分布式事務的成本也比較低。但是,XA也有致命的缺點,那就是性能不理想,特別是在交易下單鏈路,往往並發量很高,XA無法滿足高並發場景。XA目前在商業數據庫支持的比較理想,在mysql數據庫中支持的不太理想,mysql的XA實現,沒有記錄prepare階段日志,主備切換回導致主庫與備庫數據不一致。許多nosql也沒有支持XA,這讓XA的應用場景變得非常狹隘。

其實也並非不用,例如在IBM大型機上基於CICS很多跨資源是基於XA協議實現的分布式事務,事實上XA也算分布式事務處理的規范了,但在為什么互聯網中很少使用,究其原因有以下幾個:

  • 性能(阻塞性協議,增加響應時間、鎖時間、死鎖);
  • 數據庫支持完善度(MySQL 5.7之前都有缺陷);
  • 協調者依賴獨立的J2EE中間件(早期重量級Weblogic、Jboss、后期輕量級Atomikos、Narayana和Bitronix);
  • 運維復雜,DBA缺少這方面經驗;
  • 並不是所有資源都支持XA協議;

准確講XA是一個規范、協議,它只是定義了一系列的接口,只是目前大多數實現XA的都是數據庫或者MQ,所以提起XA往往多指基於資源層的底層分布式事務解決方案。其實現在也有些數據分片框架或者中間件也支持XA協議,畢竟它的兼容性、普遍性更好。

柔性事務的分類

在電商領域等互聯網場景下,剛性事務在數據庫性能和處理能力上都暴露出了瓶頸。

柔性事務有兩個特性:基本可用和柔性狀態。

  • 基本可用是指分布式系統出現故障的時候允許損失一部分的可用性。
  • 柔性狀態是指允許系統存在中間狀態,這個中間狀態不會影響系統整體的可用性,比如數據庫讀寫分離的主從同步延遲等。柔性事務的一致性指的是最終一致性。

柔性事務主要分為補償型通知型

補償型事務又分:TCC、Saga;

通知型事務分:MQ事務消息、最大努力通知型。

補償型事務都是同步的,通知型事務都是異步的。

通知型事務

通知型事務的主流實現是通過MQ(消息隊列)來通知其他事務參與者自己事務的執行狀態,引入MQ組件,有效的將事務參與者進行解耦,各參與者都可以異步執行,所以通知型事務又被稱為異步事務

通知型事務主要適用於那些需要異步更新數據,並且對數據的實時性要求較低的場景,主要包含:

異步確保型事務最大努力通知事務兩種。

  • 異步確保型事務:主要適用於內部系統的數據最終一致性保障,因為內部相對比較可控,如訂單和購物車、收貨與清算、支付與結算等等場景;

  • 最大努力通知:主要用於外部系統,因為外部的網絡環境更加復雜和不可信,所以只能盡最大努力去通知實現數據最終一致性,比如充值平台與運營商、支付對接等等跨網絡系統級別對接;

在這里插入圖片描述

異步確保型事務

指將一系列同步的事務操作修改為基於消息隊列異步執行的操作,來避免分布式事務中同步阻塞帶來的數據操作性能的下降。

MQ事務消息方案

基於MQ的事務消息方案主要依靠MQ的半消息機制來實現投遞消息和參與者自身本地事務的一致性保障。半消息機制實現原理其實借鑒的2PC的思路,是二階段提交的廣義拓展。

半消息:在原有隊列消息執行后的邏輯,如果后面的本地邏輯出錯,則不發送該消息,如果通過則告知MQ發送;

流程

在這里插入圖片描述

  1. 事務發起方首先發送半消息到MQ;
  2. MQ通知發送方消息發送成功;
  3. 在發送半消息成功后執行本地事務;
  4. 根據本地事務執行結果返回commit或者是rollback;
  5. 如果消息是rollback, MQ將丟棄該消息不投遞;如果是commit,MQ將會消息發送給消息訂閱方;
  6. 訂閱方根據消息執行本地事務;
  7. 訂閱方執行本地事務成功后再從MQ中將該消息標記為已消費;
  8. 如果執行本地事務過程中,執行端掛掉,或者超時,MQ服務器端將不停的詢問producer來獲取事務狀態;
  9. Consumer端的消費成功機制有MQ保證;

異步確保型事務使用示例

舉個例子,假設存在業務規則:某筆訂單成功后,為用戶加一定的積分。

在這條規則里,管理訂單數據源的服務為事務發起方,管理積分數據源的服務為事務跟隨者。

從這個過程可以看到,基於消息隊列實現的事務存在以下操作:

  • 訂單服務創建訂單,提交本地事務
  • 訂單服務發布一條消息
  • 積分服務收到消息后加積分

在這里插入圖片描述

我們可以看到它的整體流程是比較簡單的,同時業務開發工作量也不大:

  • 編寫訂單服務里訂單創建的邏輯
  • 編寫積分服務里增加積分的邏輯

可以看到該事務形態過程簡單,性能消耗小,發起方與跟隨方之間的流量峰谷可以使用隊列填平,同時業務開發工作量也基本與單機事務沒有差別,都不需要編寫反向的業務邏輯過程

因此基於消息隊列實現的事務是我們除了單機事務外最優先考慮使用的形態。

基於阿里 RocketMQ實現MQ異步確保型事務

有一些第三方的MQ是支持事務消息的,這些消息隊列,支持半消息機制,比如RocketMQ,ActiveMQ。但是有一些常用的MQ也不支持事務消息,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中間件為例,其思路大致為:

1.producer(本例中指A系統)發送半消息到broker,這個半消息不是說消息內容不完整, 它包含完整的消息內容, 在producer端和普通消息的發送邏輯一致

2.broker存儲半消息,半消息存儲邏輯與普通消息一致,只是屬性有所不同,topic是固定的RMQ_SYS_TRANS_HALF_TOPIC,queueId也是固定為0,這個tiopic中的消息對消費者是不可見的,所以里面的消息永遠不會被消費。這就保證了在半消息提交成功之前,消費者是消費不到這個半消息的

3.broker端半消息存儲成功並返回后,A系統執行本地事務,並根據本地事務的執行結果來決定半消息的提交狀態為提交或者回滾

4.A系統發送結束半消息的請求,並帶上提交狀態(提交 or 回滾)

5.broker端收到請求后,首先從RMQ_SYS_TRANS_HALF_TOPIC的queue中查出該消息,設置為完成狀態。如果消息狀態為提交,則把半消息從RMQ_SYS_TRANS_HALF_TOPIC隊列中復制到這個消息原始topic的queue中去(之后這條消息就能被正常消費了);如果消息狀態為回滾,則什么也不做。

6.producer發送的半消息結束請求是 oneway 的,也就是發送后就不管了,只靠這個是無法保證半消息一定被提交的,rocketMq提供了一個兜底方案,這個方案叫消息反查機制,Broker啟動時,會啟動一個TransactionalMessageCheckService 任務,該任務會定時從半消息隊列中讀出所有超時未完成的半消息,針對每條未完成的消息,Broker會給對應的Producer發送一個消息反查請求,根據反查結果來決定這個半消息是需要提交還是回滾,或者后面繼續來反查

7.consumer(本例中指B系統)消費消息,執行本地數據變更(至於B是否能消費成功,消費失敗是否重試,這屬於正常消息消費需要考慮的問題)

在這里插入圖片描述

在rocketMq中,不論是producer收到broker存儲半消息成功返回后執行本地事務,還是broker向producer反查消息狀態,都是通過回調機制完成,我把producer端的代碼貼出來你就明白了:

img

半消息發送時,會傳入一個回調類TransactionListener,使用時必須實現其中的兩個方法,executeLocalTransaction 方法會在broker返回半消息存儲成功后執行,我們會在其中執行本地事務;checkLocalTransaction方法會在broker向producer發起反查時執行,我們會在其中查詢庫表狀態。兩個方法的返回值都是消息狀態,就是告訴broker應該提交或者回滾半消息

在這里插入圖片描述

本地消息表方案

有時候我們目前的MQ組件並不支持事務消息,或者我們想盡量少的侵入業務方。這時我們需要另外一種方案“基於DB本地消息表“。

本地消息表最初由eBay 提出來解決分布式事務的問題。是目前業界使用的比較多的方案之一,它的核心思想就是將分布式事務拆分成本地事務進行處理。

本地消息表流程

img

發送消息方:

  • 需要有一個消息表,記錄着消息狀態相關信息。
  • 業務數據和消息表在同一個數據庫,要保證它倆在同一個本地事務。直接利用本地事務,將業務數據和事務消息直接寫入數據庫。
  • 在本地事務中處理完業務數據和寫消息表操作后,通過寫消息到 MQ 消息隊列。使用專門的投遞工作線程進行事務消息投遞到MQ,根據投遞ACK去刪除事務消息表記錄
  • 消息會發到消息消費方,如果發送失敗,即進行重試。

消息消費方:

  • 處理消息隊列中的消息,完成自己的業務邏輯。
  • 如果本地事務處理成功,則表明已經處理成功了。
  • 如果本地事務處理失敗,那么就會重試執行。
  • 如果是業務層面的失敗,給消息生產方發送一個業務補償消息,通知進行回滾等操作。

生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

本地消息表優缺點:

優點:

  • 本地消息表建設成本比較低,實現了可靠消息的傳遞確保了分布式事務的最終一致性。
  • 無需提供回查方法,進一步減少的業務的侵入。
  • 在某些場景下,還可以進一步利用注解等形式進行解耦,有可能實現無業務代碼侵入式的實現。

缺點:

  • 本地消息表與業務耦合在一起,難於做成通用性,不可獨立伸縮。
  • 本地消息表是基於數據庫來做的,而數據庫是要讀寫磁盤IO的,因此在高並發下是有性能瓶頸的

MQ事務消息 VS 本地消息表

二者的共性:
1、 事務消息都依賴MQ進行事務通知,所以都是異步的。
2、 事務消息在投遞方都是存在重復投遞的可能,需要有配套的機制去降低重復投遞率,實現更友好的消息投遞去重。
3、 事務消息的消費方,因為投遞重復的無法避免,因此需要進行消費去重設計或者服務冪等設計。

二者的區別:

MQ事務消息:

  • 需要MQ支持半消息機制或者類似特性,在重復投遞上具有比較好的去重處理;
  • 具有比較大的業務侵入性,需要業務方進行改造,提供對應的本地操作成功的回查功能;

DB本地消息表:

  • 使用了數據庫來存儲事務消息,降低了對MQ的要求,但是增加了存儲成本;
  • 事務消息使用了異步投遞,增大了消息重復投遞的可能性;

在這里插入圖片描述

最大努力通知

最大努力通知方案的目標,就是發起通知方通過一定的機制,最大努力將業務處理結果通知到接收方。

最大努力通知型的最終一致性:

本質是通過引入定期校驗機制實現最終一致性,對業務的侵入性較低,適合於對最終一致性敏感度比較低、業務鏈路較短的場景。

最大努力通知事務主要用於外部系統,因為外部的網絡環境更加復雜和不可信,所以只能盡最大努力去通知實現數據最終一致性,比如充值平台與運營商、支付對接、商戶通知等等跨平台、跨企業的系統間業務交互場景

異步確保型事務主要適用於內部系統的數據最終一致性保障,因為內部相對比較可控,比如訂單和購物車、收貨與清算、支付與結算等等場景。

在這里插入圖片描述

普通消息是無法解決本地事務執行和消息發送的一致性問題的。因為消息發送是一個網絡通信的過程,發送消息的過程就有可能出現發送失敗、或者超時的情況。超時有可能發送成功了,有可能發送失敗了,消息的發送方是無法確定的,所以此時消息發送方無論是提交事務還是回滾事務,都有可能不一致性出現。

所以,通知型事務的難度在於: 投遞消息和參與者本地事務的一致性保障

因為核心要點一致,都是為了保證消息的一致性投遞,所以,最大努力通知事務在投遞流程上跟異步確保型是一樣的,因此也有兩個分支

  • 基於MQ自身的事務消息方案

  • 基於DB的本地事務消息表方案

MQ事務消息方案

要實現最大努力通知,可以采用 MQ 的 ACK 機制。

最大努力通知事務在投遞之前,跟異步確保型流程都差不多,關鍵在於投遞后的處理。

因為異步確保型在於內部的事務處理,所以MQ和系統是直連並且無需嚴格的權限、安全等方面的思路設計。最大努力通知事務在於第三方系統的對接,所以最大努力通知事務有幾個特性:

  • 業務主動方在完成業務處理后,向業務被動方(第三方系統)發送通知消息,允許存在消息丟失。

  • 業務主動方提供遞增多擋位時間間隔(5min、10min、30min、1h、24h),用於失敗重試調用業務被動方的接口;在通知N次之后就不再通知,報警+記日志+人工介入。

  • 業務被動方提供冪等的服務接口,防止通知重復消費。

  • 業務主動方需要有定期校驗機制,對業務數據進行兜底;防止業務被動方無法履行責任時進行業務回滾,確保數據最終一致性。

img

  1. 業務活動的主動方,在完成業務處理之后,向業務活動的被動方發送消息,允許消息丟失。
  2. 主動方可以設置時間階梯型通知規則,在通知失敗后按規則重復通知,直到通知N次后不再通知。
  3. 主動方提供校對查詢接口給被動方按需校對查詢,用於恢復丟失的業務消息。
  4. 業務活動的被動方如果正常接收了數據,就正常返回響應,並結束事務。
  5. 如果被動方沒有正常接收,根據定時策略,向業務活動主動方查詢,恢復丟失的業務消息。

特點

  1. 用到的服務模式:可查詢操作、冪等操作;
  2. 被動方的處理結果不影響主動方的處理結果;
  3. 適用於對業務最終一致性的時間敏感度低的系統;
  4. 適合跨企業的系統間的操作,或者企業內部比較獨立的系統間的操作,比如銀行通知、商戶通知等;

本地消息表方案

要實現最大努力通知,可以采用 定期檢查本地消息表的機制 。

在這里插入圖片描述

發送消息方:

  • 需要有一個消息表,記錄着消息狀態相關信息。
  • 業務數據和消息表在同一個數據庫,要保證它倆在同一個本地事務。直接利用本地事務,將業務數據和事務消息直接寫入數據庫。
  • 在本地事務中處理完業務數據和寫消息表操作后,通過寫消息到 MQ 消息隊列。使用專門的投遞工作線程進行事務消息投遞到MQ,根據投遞ACK去刪除事務消息表記錄
  • 消息會發到消息消費方,如果發送失敗,即進行重試。
  • 生產方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

最大努力通知事務在於第三方系統的對接,所以最大努力通知事務有幾個特性:

  • 業務主動方在完成業務處理后,向業務被動方(第三方系統)發送通知消息,允許存在消息丟失。

  • 業務主動方提供遞增多擋位時間間隔(5min、10min、30min、1h、24h),用於失敗重試調用業務被動方的接口;在通知N次之后就不再通知,報警+記日志+人工介入。

  • 業務被動方提供冪等的服務接口,防止通知重復消費。

  • 業務主動方需要有定期校驗機制,對業務數據進行兜底;防止業務被動方無法履行責任時進行業務回滾,確保數據最終一致性。

最大努力通知事務 VS 異步確保型事務

最大努力通知事務在我認知中,其實是基於異步確保型事務發展而來適用於外部對接的一種業務實現。他們主要有的是業務差別,如下:
• 從參與者來說:最大努力通知事務適用於跨平台、跨企業的系統間業務交互;異步確保型事務更適用於同網絡體系的內部服務交付。
• 從消息層面說:最大努力通知事務需要主動推送並提供多檔次時間的重試機制來保證數據的通知;而異步確保型事務只需要消息消費者主動去消費。
• 從數據層面說:最大努力通知事務還需額外的定期校驗機制對數據進行兜底,保證數據的最終一致性;而異步確保型事務只需保證消息的可靠投遞即可,自身無需對數據進行兜底處理。

通知型事務的問題

通知型事務,是無法解決本地事務執行和消息發送的一致性問題的。

因為消息發送是一個網絡通信的過程,發送消息的過程就有可能出現發送失敗、或者超時的情況。超時有可能發送成功了,有可能發送失敗了,消息的發送方是無法確定的,所以此時消息發送方無論是提交事務還是回滾事務,都有可能不一致性出現。

消息發送一致性

消息中間件在分布式系統中的核心作用就是異步通訊、應用解耦和並發緩沖(也叫作流量削峰)。在分布式環境下,需要通過網絡進行通訊,就引入了數據傳輸的不確定性,也就是CAP理論中的分區容錯性。

1233356-44395b81aed884dd.png

消息發送一致性是指產生消息的業務動作與消息發送動作一致,也就是說如果業務操作成功,那么由這個業務操作所產生的消息一定要發送出去,否則就丟失。

常規MQ消息處理流程和特點

1233356-5f396e612460cc4c.png

常規的MQ隊列處理流程無法實現消息的一致性。所以,需要借助半消息、本地消息表,保障一致性。

消息重復發送問題和業務接口冪等性設計

1233356-7f06f451b8bfdb8c.png

對於未確認的消息,采用按規則重新投遞的方式進行處理。

對於以上流程,消息重復發送會導致業務處理接口出現重復調用的問題。消息消費過程中消息重復發送的主要原因就是消費者成功接收處理完消息后,消息中間件沒有及時更新投遞狀態導致的。如果允許消息重復發送,那么消費方應該實現業務接口的冪等性設計。

補償型

但是基於消息實現的事務並不能解決所有的業務場景,例如以下場景:某筆訂單完成時,同時扣掉用戶的現金。

這里事務發起方是管理訂單庫的服務,但對整個事務是否提交並不能只由訂單服務決定,因為還要確保用戶有足夠的錢,才能完成這筆交易,而這個信息在管理現金的服務里。這里我們可以引入基於補償實現的事務,其流程如下:

  • 創建訂單數據,但暫不提交本地事務
  • 訂單服務發送遠程調用到現金服務,以扣除對應的金額
  • 上述步驟成功后提交訂單庫的事務

以上這個是正常成功的流程,異常流程需要回滾的話,將額外發送遠程調用到現金服務以加上之前扣掉的金額。

以上流程比基於消息隊列實現的事務的流程要復雜,同時開發的工作量也更多:

  • 編寫訂單服務里創建訂單的邏輯
  • 編寫現金服務里扣錢的邏輯
  • 編寫現金服務里補償返還的邏輯

可以看到,該事務流程相對於基於消息實現的分布式事務更為復雜,需要額外開發相關的業務回滾方法,也失去了服務間流量削峰填谷的功能。但其僅僅只比基於消息的事務復雜多一點,若不能使用基於消息隊列的最終一致性事務,那么可以優先考慮使用基於補償的事務形態。

什么是補償模式?

補償模式使用一個額外的協調服務來協調各個需要保證一致性的業務服務,協調服務按順序調用各個業務微服務,如果某個業務服務調用異常(包括業務異常和技術異常)就取消之前所有已經調用成功的業務服務。

補償模式大致有TCC,和Saga兩種細分的方案

在這里插入圖片描述

TCC 事務模型

什么是TCC 事務模型

TCC(Try-Confirm-Cancel)的概念來源於 Pat Helland 發表的一篇名為“Life beyond Distributed Transactions:an Apostate’s Opinion”的論文。

TCC 分布式事務模型包括三部分:

1.主業務服務:主業務服務為整個業務活動的發起方,服務的編排者,負責發起並完成整個業務活動。

2.從業務服務:從業務服務是整個業務活動的參與方,負責提供 TCC 業務操作,實現初步操作(Try)、確認操作(Confirm)、取消操作(Cancel)三個接口,供主業務服務調用。

在這里插入圖片描述

3.業務活動管理器:業務活動管理器管理控制整個業務活動,包括記錄維護 TCC 全局事務的事務狀態和每個從業務服務的子事務狀態,並在業務活動提交時調用所有從業務服務的 Confirm 操作,在業務活動取消時調用所有從業務服務的 Cancel 操作。

TCC 提出了一種新的事務模型,基於業務層面的事務定義,鎖粒度完全由業務自己控制,目的是解決復雜業務中,跨表跨庫等大顆粒度資源鎖定的問題。

TCC 把事務運行過程分成 Try、Confirm / Cancel 兩個階段,每個階段的邏輯由業務代碼控制,避免了長事務,可以獲取更高的性能。

TCC的工作流程

TCC(Try-Confirm-Cancel)分布式事務模型相對於 XA 等傳統模型,其特征在於它不依賴資源管理器(RM)對分布式事務的支持,而是通過對業務邏輯的分解來實現分布式事務

TCC 模型認為對於業務系統中一個特定的業務邏輯,其對外提供服務時,必須接受一些不確定性,即對業務邏輯初步操作的調用僅是一個臨時性操作,調用它的主業務服務保留了后續的取消權。如果主業務服務認為全局事務應該回滾,它會要求取消之前的臨時性操作,這就對應從業務服務的取消操作。而當主業務服務認為全局事務應該提交時,它會放棄之前臨時性操作的取消權,這對應從業務服務的確認操作。每一個初步操作,最終都會被確認或取消。

因此,針對一個具體的業務服務,TCC 分布式事務模型需要業務系統提供三段業務邏輯:
初步操作 Try:完成所有業務檢查,預留必須的業務資源。

確認操作 Confirm:真正執行的業務邏輯,不作任何業務檢查,只使用 Try 階段預留的業務資源。因此,只要 Try 操作成功,Confirm 必須能成功。另外,Confirm 操作需滿足冪等性,保證一筆分布式事務有且只能成功一次。

取消操作 Cancel:釋放 Try 階段預留的業務資源。同樣的,Cancel 操作也需要滿足冪等性。

在這里插入圖片描述
TCC 分布式事務模型包括三部分:

image.png

Try 階段: 調用 Try 接口,嘗試執行業務,完成所有業務檢查,預留業務資源。

Confirm 或 Cancel 階段: 兩者是互斥的,只能進入其中一個,並且都滿足冪等性,允許失敗重試。

Confirm 操作: 對業務系統做確認提交,確認執行業務操作,不做其他業務檢查,只使用 Try 階段預留的業務資源。
Cancel 操作: 在業務執行錯誤,需要回滾的狀態下執行業務取消,釋放預留資源。

Try 階段失敗可以 Cancel,如果 Confirm 和 Cancel 階段失敗了怎么辦?

TCC 中會添加事務日志,如果 Confirm 或者 Cancel 階段出錯,則會進行重試,所以這兩個階段需要支持冪等;如果重試失敗,則需要人工介入進行恢復和處理等。

TCC事務案例

然而基於補償的事務形態也並非能實現所有的需求,如以下場景:某筆訂單完成時,同時扣掉用戶的現金,但交易未完成,也未被取消時,不能讓客戶看到錢變少了。

這時我們可以引入TCC,其流程如下:

  • 訂單服務創建訂單
  • 訂單服務發送遠程調用到現金服務,凍結客戶的現金
  • 提交訂單服務數據
  • 訂單服務發送遠程調用到現金服務,扣除客戶凍結的現金

以上是正常完成的流程,若為異常流程,則需要發送遠程調用請求到現金服務,撤銷凍結的金額。

以上流程比基於補償實現的事務的流程要復雜,同時開發的工作量也更多:

  • 訂單服務編寫創建訂單的邏輯
  • 現金服務編寫凍結現金的邏輯
  • 現金服務編寫扣除現金的邏輯
  • 現金服務編寫解凍現金的邏輯

TCC實際上是最為復雜的一種情況,其能處理所有的業務場景,但無論出於性能上的考慮,還是開發復雜度上的考慮,都應該盡量避免該類事務。

TCC事務模型的要求:

  1. 可查詢操作:服務操作具有全局唯一的標識,操作唯一的確定的時間。
  2. 冪等操作:重復調用多次產生的業務結果與調用一次產生的結果相同。一是通過業務操作實現冪等性,二是系統緩存所有請求與處理的結果,最后是檢測到重復請求之后,自動返回之前的處理結果。
  3. TCC操作:Try階段,嘗試執行業務,完成所有業務的檢查,實現一致性;預留必須的業務資源,實現准隔離性。Confirm階段:真正的去執行業務,不做任何檢查,僅適用Try階段預留的業務資源,Confirm操作還要滿足冪等性。Cancel階段:取消執行業務,釋放Try階段預留的業務資源,Cancel操作要滿足冪等性。TCC與2PC(兩階段提交)協議的區別:TCC位於業務服務層而不是資源層,TCC沒有單獨准備階段,Try操作兼備資源操作與准備的能力,TCC中Try操作可以靈活的選擇業務資源,鎖定粒度。TCC的開發成本比2PC高。實際上TCC也屬於兩階段操作,但是TCC不等同於2PC操作。
  4. 可補償操作:Do階段:真正的執行業務處理,業務處理結果外部可見。Compensate階段:抵消或者部分撤銷正向業務操作的業務結果,補償操作滿足冪等性。約束:補償操作在業務上可行,由於業務執行結果未隔離或者補償不完整帶來的風險與成本可控。實際上,TCC的Confirm和Cancel操作可以看做是補償操作。

TCC事務模型 VS DTP事務模型

比較一下TCC事務模型和DTP事務模型,如下所示:

img

這兩張圖看起來差別較大,實際上很多地方是類似的!

1、TCC模型中的 主業務服務 相當於 DTP模型中的AP,TCC模型中的從業務服務 相當於 DTP模型中的RM

  • 在DTP模型中,應用AP操作多個資源管理器RM上的資源;而在TCC模型中,是主業務服務操作多個從業務服務上的資源。例如航班預定案例中,美團App就是主業務服務,而川航和東航就是從業務服務,主業務服務需要使用從業務服務上的機票資源。不同的是DTP模型中的資源提供者是類似於Mysql這種關系型數據庫,而TCC模型中資源的提供者是其他業務服務。

    2、TCC模型中,從業務服務提供的try、confirm、cancel接口 相當於 DTP模型中RM提供的prepare、commit、rollback接口

  • XA協議中規定了DTP模型中定RM需要提供prepare、commit、rollback接口給TM調用,以實現兩階段提交。

  • 而在TCC模型中,從業務服務相當於RM,提供了類似的try、confirm、cancel接口。

    3、事務管理器

  • DTP模型和TCC模型中都有一個事務管理器。不同的是:

  • 在DTP模型中,階段1的(prepare)和階段2的(commit、rollback),都是由TM進行調用的。

  • 在TCC模型中,階段1的try接口是主業務服務調用(綠色箭頭),階段2的(confirm、cancel接口)是事務管理器TM調用(紅色箭頭)。這就是 TCC 分布式事務模型的二階段異步化功能,從業務服務的第一階段執行成功,主業務服務就可以提交完成,然后再由事務管理器框架異步的執行各從業務服務的第二階段。這里犧牲了一定的隔離性和一致性的,但是提高了長事務的可用性。

TCC與2PC對比

TCC其實本質和2PC是差不多的:
  • T就是Try,兩個C分別是Confirm和Cancel。

  • Try就是嘗試,請求鏈路中每個參與者依次執行Try邏輯,如果都成功,就再執行Confirm邏輯,如果有失敗,就執行Cancel邏輯。

TCC與XA兩階段提交有着異曲同工之妙,下圖列出了二者之間的對比

img

  1. 在階段1:
  • 在XA中,各個RM准備提交各自的事務分支,事實上就是准備提交資源的更新操作(insert、delete、update等);

  • 而在TCC中,是主業務活動請求(try)各個從業務服務預留資源。

    1. 在階段2:
  • XA根據第一階段每個RM是否都prepare成功,判斷是要提交還是回滾。如果都prepare成功,那么就commit每個事務分支,反之則rollback每個事務分支。

  • TCC中,如果在第一階段所有業務資源都預留成功,那么confirm各個從業務服務,否則取消(cancel)所有從業務服務的資源預留請求。

TCC和2PC不同的是:
  • XA是資源層面的分布式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。基於數據庫鎖實現,需要數據庫支持XA協議,由於在執行事務的全程都需要對相關數據加鎖,一般高並發性能會比較差
  • TCC是業務層面的分布式事務,最終一致性,不會一直持有資源的鎖,性能較好。但是對微服務的侵入性強,微服務的每個事務都必須實現try、confirm、cancel等3個方法,開發成本高,今后維護改造的成本也高為了達到事務的一致性要求,try、confirm、cancel接口必須實現冪等性操作由於事務管理器要記錄事務日志,必定會損耗一定的性能,並使得整個TCC事務時間拉長

TCC它會弱化每個步驟中對於資源的鎖定,以達到一個能承受高並發的目的(基於最終一致性)。

具體的說明如下:

XA是資源層面的分布式事務,強一致性,在兩階段提交的整個過程中,一直會持有資源的鎖。

XA事務中的兩階段提交內部過程是對開發者屏蔽的,開發者從代碼層面是感知不到這個過程的。而事務管理器在兩階段提交過程中,從prepare到commit/rollback過程中,資源實際上一直都是被加鎖的。如果有其他人需要更新這兩條記錄,那么就必須等待鎖釋放。

TCC是業務層面的分布式事務,最終一致性,不會一直持有資源的鎖。

TCC中的兩階段提交並沒有對開發者完全屏蔽,也就是說從代碼層面,開發者是可以感受到兩階段提交的存在。try、confirm/cancel在執行過程中,一般都會開啟各自的本地事務,來保證方法內部業務邏輯的ACID特性。其中:

1、try過程的本地事務,是保證資源預留的業務邏輯的正確性。

2、confirm/cancel執行的本地事務邏輯確認/取消預留資源,以保證最終一致性,也就是所謂的補償型事務(Compensation-Based Transactions)。由於是多個獨立的本地事務,因此不會對資源一直加鎖。

另外,這里提到confirm/cancel執行的本地事務是 補償性事務:

補償是一個獨立的支持ACID特性的本地事務,用於在邏輯上取消服務提供者上一個ACID事務造成的影響,對於一個長事務(long-running transaction),與其實現一個巨大的分布式ACID事務,不如使用基於補償性的方案,把每一次服務調用當做一個較短的本地ACID事務來處理,執行完就立即提交

TCC 的使用場景

TCC是可以解決部分場景下的分布式事務的,但是,它的一個問題在於,需要每個參與者都分別實現Try,Confirm和Cancel接口及邏輯,這對於業務的侵入性是巨大的。

TCC 方案嚴重依賴回滾和補償代碼,最終的結果是:回滾代碼邏輯復雜,業務代碼很難維護。所以,TCC 方案的使用場景較少,但是也有使用的場景。

比如說跟錢打交道的,支付、交易相關的場景,大家會用 TCC方案,嚴格保證分布式事務要么全部成功,要么全部自動回滾,嚴格保證資金的正確性,保證在資金上不會出現問題。

在這里插入圖片描述

SAGA長事務模型

SAGA可以看做一個異步的、利用隊列實現的補償事務。

Saga相關概念

1987年普林斯頓大學的Hector Garcia-Molina和Kenneth Salem發表了一篇Paper Sagas,講述的是如何處理long lived transaction(長活事務)。Saga是一個長活事務可被分解成可以交錯運行的子事務集合。其中每個子事務都是一個保持數據庫一致性的真實事務。
論文地址:sagas

Saga模型是把一個分布式事務拆分為多個本地事務,每個本地事務都有相應的執行模塊和補償模塊(對應TCC中的Confirm和Cancel),當Saga事務中任意一個本地事務出錯時,可以通過調用相關的補償方法恢復之前的事務,達到事務最終一致性。

這樣的SAGA事務模型,是犧牲了一定的隔離性和一致性的,但是提高了long-running事務的可用性。

Saga 模型由三部分組成

  • LLT(Long Live Transaction):由一個個本地事務組成的事務鏈
  • 本地事務:事務鏈由一個個子事務(本地事務)組成,LLT = T1+T2+T3+...+Ti。
  • 補償:每個本地事務 Ti 有對應的補償 Ci。

Saga的執行順序有兩種:

  • T1, T2, T3, ..., Tn
  • T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n

Saga 兩種恢復策略

  • 向后恢復(Backward Recovery):撤銷掉之前所有成功子事務。如果任意本地子事務失敗,則補償已完成的事務。如異常情況的執行順序T1,T2,T3,..Ti,Ci,...C3,C2,C1。

在這里插入圖片描述

  • 向前恢復(Forward Recovery):即重試失敗的事務,適用於必須要成功的場景,該情況下不需要Ci。執行順序:T1,T2,...,Tj(失敗),Tj(重試),...,Ti。

顯然,向前恢復沒有必要提供補償事務,如果你的業務中,子事務(最終)總會成功,或補償事務難以定義或不可能,向前恢復更符合你的需求。

理論上補償事務永不失敗,然而,在分布式世界中,服務器可能會宕機,網絡可能會失敗,甚至數據中心也可能會停電。在這種情況下我們能做些什么? 最后的手段是提供回退措施,比如人工干預。

Saga的使用條件

Saga看起來很有希望滿足我們的需求。所有長活事務都可以這樣做嗎?這里有一些限制:

  1. Saga只允許兩個層次的嵌套,頂級的Saga和簡單子事務
  2. 在外層,全原子性不能得到滿足。也就是說,sagas可能會看到其他sagas的部分結果
  3. 每個子事務應該是獨立的原子行為
  4. 在我們的業務場景下,各個業務環境(如:航班預訂、租車、酒店預訂和付款)是自然獨立的行為,而且每個事務都可以用對應服務的數據庫保證原子操作。

補償也有需考慮的事項:

  • 補償事務從語義角度撤消了事務Ti的行為,但未必能將數據庫返回到執行Ti時的狀態。(例如,如果事務觸發導彈發射, 則可能無法撤消此操作)

但這對我們的業務來說不是問題。其實難以撤消的行為也有可能被補償。例如,發送電郵的事務可以通過發送解釋問題的另一封電郵來補償。

對於ACID的保證:

Saga對於ACID的保證和TCC一樣:

  • 原子性(Atomicity):正常情況下保證。
  • 一致性(Consistency),在某個時間點,會出現A庫和B庫的數據違反一致性要求的情況,但是最終是一致的。
  • 隔離性(Isolation),在某個時間點,A事務能夠讀到B事務部分提交的結果。
  • 持久性(Durability),和本地事務一樣,只要commit則數據被持久。

Saga不提供ACID保證,因為原子性和隔離性不能得到滿足。原論文描述如下:

full atomicity is not provided. That is, sagas may view the partial results of other sagas

通過saga log,saga可以保證一致性和持久性。

SAGA模型的解決方案

SAGA模型的核心思想是,通過某種方案,將分布式事務轉化為本地事務,從而降低問題的復雜性。

比如以DB和MQ的場景為例,業務邏輯如下:

  1. 向DB中插入一條數據。

  2. 向MQ中發送一條消息。

由於上述邏輯中,對應了兩種存儲端,即DB和MQ,所以,簡單的通過本地事務是無法解決的。那么,依照SAGA模型,可以有兩種解決方案。

方案一:半消息模式。

RocketMQ新版本中,就支持了這種模式。

首先,我們理解什么是半消息。簡單來說,就是在消息上加了一個狀態。當發送者第一次將消息放入MQ后,該消息為待確認狀態。該狀態下,該消息是不能被消費者消費的。發送者必須二次和MQ進行交互,將消息從待確認狀態變更為確認狀態后,消息才能被消費者消費。待確認狀態的消息,就稱之為半消息。

半消息的完整事務邏輯如下:

  1. 向MQ發送半消息。

  2. 向DB插入數據。

  3. 向MQ發送確認消息。

我們發現,通過半消息的形式,將DB的操作夾在了兩個MQ操作的中間。假設,第2步失敗了,那么,MQ中的消息就會一直是半消息狀態,也就不會被消費者消費。

那么,半消息就一直存在於MQ中嗎?或者是說如果第3步失敗了呢?

為了解決上面的問題,MQ引入了一個掃描的機制。即MQ會每隔一段時間,對所有的半消息進行掃描,並就掃描到的存在時間過長的半消息,向發送者進行詢問,詢問如果得到確認回復,則將消息改為確認狀態,如得到失敗回復,則將消息刪除。

img半消息

如上,半消息機制的一個問題是:要求業務方提供查詢消息狀態接口,對業務方依然有較大的侵入性。

方案二:本地消息表

在DB中,新增一個消息表,用於存放消息。如下:

  1. 在DB業務表中插入數據。

  2. 在DB消息表中插入數據。

  3. 異步將消息表中的消息發送到MQ,收到ack后,刪除消息表中的消息。

img本地消息表

如上,通過上述邏輯,將一個分布式的事務,拆分成兩大步。第1和第2,構成了一個本地的事務,從而解決了分布式事務的問題。

這種解決方案,不需要業務端提供消息查詢接口,只需要稍微修改業務邏輯,侵入性是最小的。

SAGA的案例

SAGA適用於無需馬上返回業務發起方最終狀態的場景,例如:你的請求已提交,請稍后查詢或留意通知 之類。

將上述補償事務的場景用SAGA改寫,其流程如下:

  • 訂單服務創建最終狀態未知的訂單記錄,並提交事務
  • 現金服務扣除所需的金額,並提交事務
  • 訂單服務更新訂單狀態為成功,並提交事務

以上為成功的流程,若現金服務扣除金額失敗,那么,最后一步訂單服務將會更新訂單狀態為失敗。

其業務編碼工作量比補償事務多一點,包括以下內容:

  • 訂單服務創建初始訂單的邏輯
  • 訂單服務確認訂單成功的邏輯
  • 訂單服務確認訂單失敗的邏輯
  • 現金服務扣除現金的邏輯
  • 現金服務補償返回現金的邏輯

但其相對於補償事務形態有性能上的優勢,所有的本地子事務執行過程中,都無需等待其調用的子事務執行,減少了加鎖的時間,這在事務流程較多較長的業務中性能優勢更為明顯。同時,其利用隊列進行進行通訊,具有削峰填谷的作用。

因此該形式適用於不需要同步返回發起方執行最終結果、可以進行補償、對性能要求較高、不介意額外編碼的業務場景。

但當然SAGA也可以進行稍微改造,變成與TCC類似、可以進行資源預留的形態

Saga和TCC對比

Saga相比TCC的缺點是缺少預留動作,導致補償動作的實現比較麻煩:Ti就是commit,比如一個業務是發送郵件,在TCC模式下,先保存草稿(Try)再發送(Confirm),撤銷的話直接刪除草稿(Cancel)就行了。而Saga則就直接發送郵件了(Ti),如果要撤銷則得再發送一份郵件說明撤銷(Ci),實現起來有一些麻煩。

如果把上面的發郵件的例子換成:A服務在完成Ti后立即發送Event到ESB(企業服務總線,可以認為是一個消息中間件),下游服務監聽到這個Event做自己的一些工作然后再發送Event到ESB,如果A服務執行補償動作Ci,那么整個補償動作的層級就很深。

不過沒有預留動作也可以認為是優點:

  • 有些業務很簡單,套用TCC需要修改原來的業務邏輯,而Saga只需要添加一個補償動作就行了。
  • TCC最少通信次數為2n,而Saga為n(n=sub-transaction的數量)。
  • 有些第三方服務沒有Try接口,TCC模式實現起來就比較tricky了,而Saga則很簡單。
  • 沒有預留動作就意味着不必擔心資源釋放的問題,異常處理起來也更簡單。

Saga對比TCC

Saga和TCC都是補償型事務,他們的區別為:

劣勢:

  • 無法保證隔離性;

優勢:

  • 一階段提交本地事務,無鎖,高性能;
  • 事件驅動模式,參與者可異步執行,高吞吐;
  • Saga 對業務侵入較小,只需要提供一個逆向操作的Cancel即可;而TCC需要對業務進行全局性的流程改造;

總體的方案對比:

屬性 2PC TCC Saga 異步確保型事務 盡最大努力通知
事務一致性
復雜性
業務侵入性
使用局限性
性能
維護成本

seata

以下內容,來自 官網

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

seata簡介

Seata , 官網 , github , 1萬多星

  • Seata 是一款開源的分布式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分布式事務服務。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務模式
  • 在 Seata 開源之前,Seata 對應的內部版本在阿里經濟體內部一直扮演着分布式一致性中間件的角色,幫助經濟體平穩的度過歷年的雙11,對各BU業務進行了有力的支撐。商業化產品GTS 先后在阿里雲、金融雲進行售賣

相關鏈接:

什么是seata:https://seata.io/zh-cn/docs/overview/what-is-seata.html

下載 https://seata.io/zh-cn/blog/download.html
官方例子 https://seata.io/zh-cn/docs/user/quickstart.html

Seata 的三大模塊

Seata AT使用了增強型二階段提交實現。

Seata 分三大模塊 :

  • TC :事務協調者。負責我們的事務ID的生成,事務注冊、提交、回滾等。
  • TM:事務發起者。定義事務的邊界,負責告知 TC,分布式事務的開始,提交,回滾。
  • RM:資源管理者。管理每個分支事務的資源,每一個 RM 都會作為一個分支事務注冊在 TC。

在Seata的AT模式中,TM和RM都作為SDK的一部分和業務服務在一起,我們可以認為是Client。TC是一個獨立的服務,通過服務的注冊、發現將自己暴露給Client們。

Seata 中有三大模塊中, TM 和 RM 是作為 Seata 的客戶端與業務系統集成在一起,TC 作為 Seata 的服務端獨立部署。

640?wx_fmt=png

Seata 的分布式事務的執行流程

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

  • TM 開啟分布式事務(TM 向 TC 注冊全局事務記錄);
  • 按業務場景,編排數據庫、服務等事務內資源(RM 向 TC 匯報資源准備狀態 );
  • TM 結束分布式事務,事務一階段結束(TM 通知 TC 提交/回滾分布式事務);
  • TC 匯總事務信息,決定分布式事務是提交還是回滾;
  • TC 通知所有 RM 提交/回滾 資源,事務二階段結束;

在這里插入圖片描述

Seata的TC、TM、RM三個角色 , 是不是和XA模型很像. 下圖是XA模型的事務大致流程。

在這里插入圖片描述

在X/Open DTP(Distributed Transaction Process)模型里面,有三個角色:

​ AP: Application,應用程序。也就是業務層。哪些操作屬於一個事務,就是AP定義的。

​ TM: Transaction Manager,事務管理器。接收AP的事務請求,對全局事務進行管理,管理事務分支狀態,協調RM的處理,通知RM哪些操作屬於哪些全局事務以及事務分支等等。這個也是整個事務調度模型的核心部分。

​ RM:Resource Manager,資源管理器。一般是數據庫,也可以是其他的資源管理器,如消息隊列(如JMS數據源),文件系統等。

在這里插入圖片描述

4 種分布式事務解決方案

Seata 會有 4 種分布式事務解決方案,分別是 AT 模式、TCC 模式、Saga 模式和 XA 模式。

640?wx_fmt=jpeg

Seata AT模式

Seata AT模式是最早支持的模式。AT模式是指Automatic (Branch) Transaction Mode自動化分支事務。

Seata AT 模式是增強型2pc模式,或者說是增強型的XA模型。

總體來說,AT 模式,是 2pc兩階段提交協議的演變,不同的地方,Seata AT 模式不會一直鎖表。

Seata AT模式的使用前提

  • 基於支持本地 ACID 事務的關系型數據庫。

    比如,在MySQL 5.1之前的版本中,默認的搜索引擎是MyISAM,從MySQL 5.5之后的版本中,默認的搜索引擎變更為InnoDB。MyISAM存儲引擎的特點是:表級鎖、不支持事務和全文索引。 所以,基於MyISAM 的表,就不支持Seata AT模式。

  • Java 應用,通過 JDBC 訪問數據庫。

Seata AT模型圖

兩階段提交協議的演變:

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

完整的AT在Seata所制定的事務模式下的模型圖:
img

Seata AT模式的例子

我們用一個比較簡單的業務場景來描述一下Seata AT模式的工作過程。

有個充值業務,現在有兩個服務,一個負責管理用戶的余額,另外一個負責管理用戶的積分。

當用戶充值的時候,首先增加用戶賬戶上的余額,然后增加用戶的積分。

Seata AT分為兩階段,主要邏輯全部在第一階段,第二階段主要做回滾或日志清理的工作。

第一階段流程:

第一階段流程如

在這里插入圖片描述

1)余額服務中的TM,向TC申請開啟一個全局事務,TC會返回一個全局的事務ID。

2)余額服務在執行本地業務之前,RM會先向TC注冊分支事務。

3)余額服務依次生成undo log、執行本地事務、生成redo log,最后直接提交本地事務。

4)余額服務的RM向TC匯報,事務狀態是成功的。

5)余額服務發起遠程調用,把事務ID傳給積分服務。

6)積分服務在執行本地業務之前,也會先向TC注冊分支事務。

7)積分服務次生成undo log、執行本地事務、生成redo log,最后直接提交本地事務。

8)積分服務的RM向TC匯報,事務狀態是成功的。

9)積分服務返回遠程調用成功給余額服務。

10)余額服務的TM向TC申請全局事務的提交/回滾。

積分服務中也有TM,但是由於沒有用到,因此直接可以忽略。

我們如果使用 Spring框架的注解式事務,遠程調用會在本地事務提交之前發生。但是,先發起遠程調用還是先提交本地事務,這個其實沒有任何影響。

第二階段流程如:

第二階段的邏輯就比較簡單了。

Client和TC之間是有長連接的,如果是正常全局提交,則TC通知多個RM異步清理掉本地的redo和undo log即可。如果是回滾,則TC通知每個RM回滾數據即可。

這里就會引出一個問題,由於本地事務都是自己直接提交了,后面如何回滾,由於我們在操作本地業務操作的前后,做記錄了undo和redo log,因此可以通過undo log進行回滾。

由於undo和redo log和業務操作在同一個事務中,因此肯定會同時成功或同時失敗。

但是還會存在一個問題,因為每個事務從本地提交到通知回滾這段時間里,可能這條數據已經被別的事務修改,如果直接用undo log回滾,會導致數據不一致的情況。

此時,RM會用redo log進行校驗,對比數據是否一樣,從而得知數據是否有別的事務修改過。注意:undo log是被修改前的數據,可以用於回滾;redo log是被修改后的數據,用於回滾校驗。

如果數據未被其他事務修改過,則可以直接回滾;如果是臟數據,再根據不同策略處理。

Seata AT 模式在電商下單場景的使用

下面描述 Seata AT mode 的工作原理使用的電商下單場景的使用

如下圖所示:

img

在上圖中,協調者 shopping-service 先調用參與者 repo-service 扣減庫存,后調用參與者 order-service 生成訂單。這個業務流使用 Seata in XA mode 后的全局事務流程如下圖所示:

img

上圖描述的全局事務執行流程為:

1)shopping-service 向 Seata 注冊全局事務,並產生一個全局事務標識 XID

2)將 repo-service.repo_db、order-service.order_db 的本地事務執行到待提交階段,事務內容包含對 repo-service.repo_db、order-service.order_db 進行的查詢操作以及寫每個庫的 undo_log 記錄

3)repo-service.repo_db、order-service.order_db 向 Seata 注冊分支事務,並將其納入該 XID 對應的全局事務范圍

4)提交 repo-service.repo_db、order-service.order_db 的本地事務

5)repo-service.repo_db、order-service.order_db 向 Seata 匯報分支事務的提交狀態

6)Seata 匯總所有的 DB 的分支事務的提交狀態,決定全局事務是該提交還是回滾

7)Seata 通知 repo-service.repo_db、order-service.order_db 提交/回滾本地事務,若需要回滾,采取的是補償式方法

其中 1)2)3)4)5)屬於第一階段,6)7)屬於第二階段。

電商業務場景中 Seata in AT mode 工作流程詳述

在上面的電商業務場景中,購物服務調用庫存服務扣減庫存,調用訂單服務創建訂單,顯然這兩個調用過程要放在一個事務里面。即:

start global_trx

 call 庫存服務的扣減庫存接口

 call 訂單服務的創建訂單接口

commit global_trx

在庫存服務的數據庫中,存在如下的庫存表 t_repo:

img

在訂單服務的數據庫中,存在如下的訂單表 t_order:

img

現在,id 為 40002 的用戶要購買一只商品代碼為 20002 的鼠標,整個分布式事務的內容為:

1)在庫存服務的庫存表中將記錄

img

修改為

img

2)在訂單服務的訂單表中添加一條記錄

img

以上操作,在 AT 模式的第一階段的流程圖如下:

img

在這里插入圖片描述

從 AT 模式第一階段的流程來看,分支的本地事務在第一階段提交完成之后,就會釋放掉本地事務鎖定的本地記錄。這是 AT 模式和 XA 最大的不同點,在 XA 事務的兩階段提交中,被鎖定的記錄直到第二階段結束才會被釋放。所以 AT 模式減少了鎖記錄的時間,從而提高了分布式事務的處理效率。AT 模式之所以能夠實現第一階段完成就釋放被鎖定的記錄,是因為 Seata 在每個服務的數據庫中維護了一張 undo_log 表,其中記錄了對 t_order / t_repo 進行操作前后記錄的鏡像數據,即便第二階段發生異常,只需回放每個服務的 undo_log 中的相應記錄即可實現全局回滾。

undo_log 的表結構:

img

第一階段結束之后,Seata 會接收到所有分支事務的提交狀態,然后決定是提交全局事務還是回滾全局事務。

1)若所有分支事務本地提交均成功,則 Seata 決定全局提交。Seata 將分支提交的消息發送給各個分支事務,各個分支事務收到分支提交消息后,會將消息放入一個緩沖隊列,然后直接向 Seata 返回提交成功。之后,每個本地事務會慢慢處理分支提交消息,處理的方式為:刪除相應分支事務的 undo_log 記錄。之所以只需刪除分支事務的 undo_log 記錄,而不需要再做其他提交操作,是因為提交操作已經在第一階段完成了(這也是 AT 和 XA 不同的地方)。這個過程如下圖所示:

img

分支事務之所以能夠直接返回成功給 Seata,是因為真正關鍵的提交操作在第一階段已經完成了,清除 undo_log 日志只是收尾工作,即便清除失敗了,也對整個分布式事務不產生實質影響。

2)若任一分支事務本地提交失敗,則 Seata 決定全局回滾,將分支事務回滾消息發送給各個分支事務,由於在第一階段各個服務的數據庫上記錄了 undo_log 記錄,分支事務回滾操作只需根據 undo_log 記錄進行補償即可。全局事務的回滾流程如下圖所示:

img

這里對圖中的 2、3 步做進一步的說明:

1)由於上文給出了 undo_log 的表結構,所以可以通過 xid 和 branch_id 來找到當前分支事務的所有 undo_log 記錄;

2)拿到當前分支事務的 undo_log 記錄之后,首先要做數據校驗,如果 afterImage 中的記錄與當前的表記錄不一致,說明從第一階段完成到此刻期間,有別的事務修改了這些記錄,這會導致分支事務無法回滾,向 Seata 反饋回滾失敗;如果 afterImage 中的記錄與當前的表記錄一致,說明從第一階段完成到此刻期間,沒有別的事務修改這些記錄,分支事務可回滾,進而根據 beforeImage 和 afterImage 計算出補償 SQL,執行補償 SQL 進行回滾,然后刪除相應 undo_log,向 Seata 反饋回滾成功。

Seata的數據隔離性

seata的at模式主要實現邏輯是數據源代理,而數據源代理將基於如MySQL和Oracle等關系事務型數據庫實現,基於數據庫的隔離級別為read committed。換而言之,本地事務的支持是seata實現at模式的必要條件,這也將限制seata的at模式的使用場景。

寫隔離

從前面的工作流程,我們可以很容易知道,Seata的寫隔離級別是全局獨占的。

首先,我們理解一下寫隔離的流程


分支事務1-開始
| 
V 獲取 本地鎖
| 
V 獲取 全局鎖    分支事務2-開始
|               |
V 釋放 本地鎖     V 獲取 本地鎖
|               |
V 釋放 全局鎖     V 獲取 全局鎖
                |
                V 釋放 本地鎖
                |
                V 釋放 全局鎖

如上所示,一個分布式事務的鎖獲取流程是這樣的
1)先獲取到本地鎖,這樣你已經可以修改本地數據了,只是還不能本地事務提交
2)而后,能否提交就是看能否獲得全局鎖
3)獲得了全局鎖,意味着可以修改了,那么提交本地事務,釋放本地鎖
4)當分布式事務提交,釋放全局鎖。這樣就可以讓其它事務獲取全局鎖,並提交它們對本地數據的修改了。

可以看到,這里有兩個關鍵點
1)本地鎖獲取之前,不會去爭搶全局鎖
2)全局鎖獲取之前,不會提交本地鎖

這就意味着,數據的修改將被互斥開來。也就不會造成寫入臟數據。全局鎖可以讓分布式修改中的寫數據隔離。

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

以一個示例來說明:

兩個全局事務 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 語句。

Spring Cloud集成Seata AT模式

AT模式是指Automatic (Branch) Transaction Mode 自動化分支事務,使用AT模式的前提是

  • 基於支持本地 ACID 事務的關系型數據庫。

  • Java 應用,通過 JDBC 訪問數據庫。

seata-at的使用步驟

1、引入seata框架,配置好seata基本配置,建立undo_log表

2、消費者引入全局事務注解@GlobalTransactional

3、生產者引入全局事務注解@GlobalTransactional

此處沒有寫完, 尼恩的博文,都是迭代模式,后續會持續

Seata TCC 模式

簡介

TCC 與 Seata AT 事務一樣都是兩階段事務,它與 AT 事務的主要區別為:

  • TCC 對業務代碼侵入嚴重
    每個階段的數據操作都要自己進行編碼來實現,事務框架無法自動處理。

  • TCC 性能更高
    不必對數據加全局鎖,允許多個事務同時操作數據。

    a

Seata TCC 整體是 兩階段提交 的模型。一個分布式的全局事務,全局事務是由若干分支事務組成的,分支事務要滿足 兩階段提交 的模型要求,即需要每個分支事務都具備自己的:

  • 一階段 prepare 行為
  • 二階段 commit 或 rollback 行為

在這里插入圖片描述

根據兩階段行為模式的不同,我們將分支事務划分為 Automatic (Branch) Transaction ModeTCC (Branch) Transaction Mode.

AT 模式(參考鏈接 TBD)基於 支持本地 ACID 事務關系型數據庫

  • 一階段 prepare 行為:在本地事務中,一並提交業務數據更新和相應回滾日志記錄。
  • 二階段 commit 行為:馬上成功結束,自動 異步批量清理回滾日志。
  • 二階段 rollback 行為:通過回滾日志,自動 生成補償操作,完成數據回滾。

相應的,TCC 模式,不依賴於底層數據資源的事務支持:

  • 一階段 prepare 行為:調用 自定義 的 prepare 邏輯。
  • 二階段 commit 行為:調用 自定義 的 commit 邏輯。
  • 二階段 rollback 行為:調用 自定義 的 rollback 邏輯。

所謂 TCC 模式,是指支持把 自定義 的分支事務納入到全局事務的管理中。

第一階段 Try

以賬戶服務為例,當下訂單時要扣減用戶賬戶金額:

a

假如用戶購買 100 元商品,要扣減 100 元。

TCC 事務首先對這100元的扣減金額進行預留,或者說是先凍結這100元:

a

第二階段 Confirm

如果第一階段能夠順利完成,那么說明“扣減金額”業務(分支事務)最終肯定是可以成功的。當全局事務提交時, TC會控制當前分支事務進行提交,如果提交失敗,TC 會反復嘗試,直到提交成功為止。

當全局事務提交時,就可以使用凍結的金額來最終實現業務數據操作:

a

第二階段 Cancel

如果全局事務回滾,就把凍結的金額進行解凍,恢復到以前的狀態,TC 會控制當前分支事務回滾,如果回滾失敗,TC 會反復嘗試,直到回滾完成為止。

a

多個事務並發的情況

多個TCC全局事務允許並發,它們執行扣減金額時,只需要凍結各自的金額即可:

a

Seata TCC 模式 沒有寫完, 尼恩的博文,都是迭代模式,后續會持續優化

SEATA Saga 模式

Saga模式是SEATA提供的長事務解決方案,在Saga模式中,業務流程中每個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。

Saga模式示意圖

理論基礎:Hector & Kenneth 發表論⽂ Sagas (1987)

適用場景:

  • 業務流程長、業務流程多
  • 參與者包含其它公司或遺留系統服務,無法提供 TCC 模式要求的三個接口

優勢:

  • 一階段提交本地事務,無鎖,高性能
  • 事件驅動架構,參與者可異步執行,高吞吐
  • 補償服務易於實現

缺點:

  • 不保證隔離性(應對方案見后面文檔)

Saga的實現:

基於狀態機引擎的 Saga 實現:

目前SEATA提供的Saga模式是基於狀態機引擎來實現的,機制是:

  1. 通過狀態圖來定義服務調用的流程並生成 json 狀態語言定義文件
  2. 狀態圖中一個節點可以是調用一個服務,節點可以配置它的補償節點
  3. 狀態圖 json 由狀態機引擎驅動執行,當出現異常時狀態引擎反向執行已成功節點對應的補償節點將事務回滾

注意: 異常發生時是否進行補償也可由用戶自定義決定

  1. 可以實現服務編排需求,支持單項選擇、並發、子流程、參數轉換、參數映射、服務執行狀態判斷、異常捕獲等功能

示例狀態圖:

示例狀態圖

Seata Saga 模式 沒有寫完, 尼恩的博文,都是迭代模式,后續會持續優化

Seata XA 模式

使用Seata XA 模式的前提

  • 支持XA 事務的數據庫。
  • Java 應用,通過 JDBC 訪問數據庫。

Seata XA 模式的整體機制

在 Seata 定義的分布式事務框架內,利用事務資源(數據庫、消息服務等)對 XA 協議的支持,以 XA 協議的機制來管理分支事務的一種 事務模式。

注意這里的重點:利用事務資源對 XA 協議的支持,以 XA 協議的機制來管理分支事務。

img

Seata XA 模式的工作機制

1. 整體運行機制

XA 模式 運行在 Seata 定義的事務框架內:

xa-fw

2. 數據源代理

XA 模式需要 XAConnection。

獲取 XAConnection 兩種方式:

  • 方式一:要求開發者配置 XADataSource
  • 方式二:根據開發者的普通 DataSource 來創建

第一種方式,給開發者增加了認知負擔,需要為 XA 模式專門去學習和使用 XA 數據源,與 透明化 XA 編程模型的設計目標相違背。

第二種方式,對開發者比較友好,和 AT 模式使用一樣,開發者完全不必關心 XA 層面的任何問題,保持本地編程模型即可。

我們優先設計實現第二種方式:數據源代理根據普通數據源中獲取的普通 JDBC 連接創建出相應的 XAConnection。

類比 AT 模式的數據源代理機制,如下:

ds1

實際上,這種方法是在做數據庫驅動程序要做的事情。不同的廠商、不同版本的數據庫驅動實現機制是廠商私有的,我們只能保證在充分測試過的驅動程序上是正確的,開發者使用的驅動程序版本差異很可能造成機制的失效。

這點在 Oracle 上體現非常明顯。參見 Druid issue:https://github.com/alibaba/druid/issues/3707

綜合考慮,XA 模式的數據源代理設計需要同時支持第一種方式:基於 XA 數據源進行代理。

類比 AT 模式的數據源代理機制,如下:

ds2

XA start 需要 Xid 參數。

這個 Xid 需要和 Seata 全局事務的 XID 和 BranchId 關聯起來,以便由 TC 驅動 XA 分支的提交或回滾。

目前 Seata 的 BranchId 是在分支注冊過程,由 TC 統一生成的,所以 XA 模式分支注冊的時機需要在 XA start 之前。

將來一個可能的優化方向:

把分支注冊盡量延后。類似 AT 模式在本地事務提交之前才注冊分支,避免分支執行失敗情況下,沒有意義的分支注冊。

這個優化方向需要 BranchId 生成機制的變化來配合。BranchId 不通過分支注冊過程生成,而是生成后再帶着 BranchId 去注冊分支。

XA 模式的使用

從編程模型上,XA 模式與 AT 模式保持完全一致。

可以參考 Seata 官網的樣例:seata-xa

樣例場景是 Seata 經典的,涉及庫存、訂單、賬戶 3 個微服務的商品訂購業務。

在樣例中,上層編程模型與 AT 模式完全相同。只需要修改數據源代理,即可實現 XA 模式與 AT 模式之間的切換。

    @Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        // DataSourceProxy for AT mode
        // return new DataSourceProxy(druidDataSource);

        // DataSourceProxyXA for XA mode
        return new DataSourceProxyXA(druidDataSource);
    }

面試題標准答案: 如何解決分布式事務問題的?

現在Java面試,分布式系統、分布式事務幾乎是標配。而分布式系統、分布式事務本身比較復雜,大家學起來也非常頭疼。

 面試題:分布式事務了解嗎?你們是如何解決分布式事務問題的? 

Seata AT模式和Seata TCC是在生產中最常用。

  • 強一致性模型,Seata AT 強一致方案 模式用於強一致主要用於核心模塊,例如交易/訂單等。

  • 弱一致性模型。Seata TCC 弱一致方案一般用於邊緣模塊例如庫存,通過TC的協調,保證最終一致性,也可以業務解耦。

面試中如果你真的被問到,可以分場景回答:

(1)強一致性場景

對於那些特別嚴格的場景,用的是Seata AT模式來保證強一致性;

准備好例子:你找一個嚴格要求數據絕對不能錯的場景(如電商交易交易中的庫存和訂單、優惠券),可以回答使用成熟的如中間件Seata AT模式。

 阿里開源了分布式事務框架seata經歷過阿里生產環境大量考驗的框架。  seata支持Dubbo,Spring Cloud。 

在這里插入圖片描述

是Seata AT模式,保障強一致性,支持跨多個庫修改數據;

  • 訂單庫:增加訂單

  • 商品庫:扣減庫存

  • 優惠券庫:預扣優惠券

(2)弱一致性場景

對於數據一致性要求沒有那些特別嚴格、或者由不同系統執行子事務的場景,可以回答使用Seata TCC 保障弱一致性方案

准備好例子:一個不是嚴格對數據一致性要求、或者由不同系統執行子事務的場景,如電商訂單支付服務,更新訂單狀態,發送成功支付成功消息,只需要保障弱一致性即可。

在這里插入圖片描述

Seata TCC 模式,保障弱一致性,支持跨多個服務和系統修改數據,在上面的場景中,使用Seata TCC 模式事務

  • 訂單服務:修改訂單狀態

  • 通知服務:發送支付狀態

(3)最終一致性場景

基於可靠消息的最終一致性,各個子事務可以較長時間內異步,但數據絕對不能丟的場景。可以使用異步確保型事務事。

可以使用基於MQ的異步確保型事務,比如電商平台的通知支付結果:

  • 積分服務:增加積分

  • 會計服務:生成會計記錄

在這里插入圖片描述

各大模式的總體對比:

屬性 2PC TCC Saga 異步確保型事務 盡最大努力通知
事務一致性
復雜性
業務侵入性
使用局限性
性能
維護成本

參考資料

https://blog.csdn.net/wuzhiwei549/article/details/80692278
https://www.i3geek.com/archives/841
https://www.cnblogs.com/seesun2012/p/9214653.html
https://github.com/yangliu0/DistributedLock
https://www.cnblogs.com/liuyang0/p/6744076.html
https://www.cnblogs.com/liuyang0/p/6800538.html
https://mwhittaker.github.io/blog/an_illustrated_proof_of_the_cap_theorem/
https://www.infoq.cn/article/cap-twelve-years-later-how-the-rules-have-changed
https://www.cnblogs.com/bluemiaomiao/p/11216380.html

https://www.jianshu.com/p/d909dbaa9d64

https://book.douban.com/subject/26292004/

https://segmentfault.com/a/1190000004468442

https://blog.csdn.net/universsky2015/article/details/105727244/

https://www.cnblogs.com/wudimanong/p/10340948.html

https://blog.csdn.net/kusedexingfu/article/details/103484198

https://www.jianshu.com/p/bfb619d3eea2

http://seata.io/zh-cn/docs/dev/mode/at-mode.html

https://blog.csdn.net/kusedexingfu/article/details/103484198

https://blog.csdn.net/wsdc0521/article/details/108223310

https://blog.csdn.net/lidatgb/article/details/38468005

https://www.cnblogs.com/cuiqq/p/12175826.html

https://blog.csdn.net/qq_22343483/article/details/99638554

https://blog.csdn.net/SOFAStack/article/details/99670033

https://seata.io/zh-cn/docs/dev/mode/at-mode.html

http://t.zoukankan.com/lay2017-p-12528071.html

https://segmentfault.com/a/1190000037757622


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM