文章很長,並且持續更新,建議收藏起來,慢慢讀! Java 高併發 發燒友社羣:瘋狂創客圈(總入口) 奉上如下珍貴的學習資源:html
入大廠 、作架構、大力提高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圖解(秒懂 + 史上最全 + 高薪必備) |
10: 分佈式事務( 圖解 + 史上最全 + 吐血推薦 ) |
Java 面試題 30個專題 , 史上最全 , 面試必刷 | 阿里、京東、美團... 隨意挑、橫着走!!! |
---|---|
1: JVM面試題(史上最強、持續更新、吐血推薦) | 2:Java基礎面試題(史上最全、持續更新、吐血推薦 |
3:架構設計面試題 (史上最全、持續更新、吐血推薦) | 4:設計模式面試題 (史上最全、持續更新、吐血推薦) |
1七、分佈式事務面試題 (史上最全、持續更新、吐血推薦) | 一致性協議 (史上最全) |
2九、多線程面試題(史上最全) | 30、HR面經,過五關斬六將後,當心陰溝翻船! |
9.網絡協議面試題(史上最全、持續更新、吐血推薦) | 更多專題, 請參見【 瘋狂創客圈 高併發 總目錄 】 |
SpringCloud 精彩博文 | |
---|---|
nacos 實戰(史上最全) | sentinel (史上最全+入門教程) |
SpringCloud gateway (史上最全) | 更多專題, 請參見【 瘋狂創客圈 高併發 總目錄 】 |
如今Java面試,分佈式系統、分佈式事務幾乎是標配。而分佈式系統、分佈式事務自己比較複雜,你們學起來也很是頭疼。java
面試題:分佈式事務瞭解嗎?大家是如何解決分佈式事務問題的? (標準答案:見末尾)
友情提示:node
看完此文,在分佈式事務這塊,基本能夠作到吊打面試官了。mysql
首先奉上一張全網最爲牛逼的圖,給你們作個總覽:git
分佈式鎖解決的是分佈式資源搶佔的問題;分佈式事務和本地事務是解決流程化提交問題。github
事務(Transaction)是操做數據庫中某個數據項的一個程序執行單元(unit)。面試
事務應該具備4個屬性:原子性、一致性、隔離性、持久性。這四個屬性一般稱爲ACID特性。spring
事務必須是一個原子的操做序列單元,事務中包含的各項操做在一次執行過程當中,要麼所有執行成功,要麼所有不執行,任何一項失敗,整個事務回滾,只有所有都執行成功,整個事務纔算成功。sql
事務的執行不能破壞數據庫數據的完整性和一致性,事務在執行以前和以後,數據庫都必須處於一致性狀態。數據庫
在併發環境中,併發的事務是相互隔離的,一個事務的執行不能被其餘事務干擾。即不一樣的事務併發操縱相同的數據時,每一個事務都有各自完整的數據空間,即一個事務內部的操做及使用的數據對其餘併發事務是隔離的,併發執行的各個事務之間不能相互干擾。
(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)串行化
最嚴格的事務,要求全部事務被串行執行,不能併發執行。
若是不對事務進行併發控制,咱們看看數據庫併發操做是會有那些異常情形
持久性(durability):持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中對應數據的狀態變動就應該是永久性的。
即便發生系統崩潰或機器宕機,只要數據庫可以從新啓動,那麼必定可以將其恢復到事務成功結束時的狀態。
比方說:一我的買東西的時候須要記錄在帳本上,即便老闆忘記了那也有據可查。
大多數場景下,咱們的應用都只須要操做單一的數據庫,這種狀況下的事務稱之爲本地事務(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 的內容,將全部數據恢復到崩潰以前的狀態。
在多個事務併發操做時,數據庫中會出現下面三種問題:髒讀,幻讀,不可重複讀。
事務A讀到了事務B還未提交的數據:
事務A讀取的數據,事務B對該數據進行修改還未提交數據以前,事務A再次讀取數據會讀到事務B已經修改後的數據,若是此時事務B進行回滾或再次修改該數據而後提交,事務A讀到的數據就是髒數據,這個狀況被稱爲髒讀(Dirty Read)。
事務A進行範圍查詢時,事務B中新增了知足該範圍條件的記錄,當事務A再次按該條件進行範圍查詢,會查到在事務B中提交的新的知足條件的記錄(幻行 Phantom Row)。
事務A在讀取某些數據後,再次讀取該數據,發現讀出的該數據已經在事務B中發生了變動或刪除。
幻讀和不可重複度的區別:
- 幻讀:在同一事務中,相同條件下,兩次查詢出來的 記錄數 不同;
- 不可重複讀:在同一事務中,相同條件下,兩次查詢出來的 數據 不同;
爲了解決數據庫中事務併發所產生的問題,在標準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):最高的隔離級別,事務串行的執行,前一個事務執行完,後面的事務會執行。讀取每條數據都會加鎖,會致使大量的超時和鎖爭用問題;
問:如何保證 REPEATABLE READ 級別絕對不產生幻讀?
答:在SQL中加入 for update (排他鎖) 或 lock in share mode (共享鎖)語句實現。就是鎖住了可能形成幻讀的數據,阻止數據的寫入操做。
當本地事務要擴展到分佈式時,它的複雜性進一步增長了。
存儲端的多樣性。
首先就是存儲端的多樣性。本地事務的狀況下,全部數據都會落到同一個DB中,可是,在分佈式的狀況下,就會出現數據可能要落到多個DB,或者還會落到Redis,落到MQ等中。
存儲端多樣性, 以下圖所示:
事務鏈路的延展性
本地事務的狀況下,一般全部事務相關的業務操做,會被咱們封裝到一個Service方法中。而在分佈式的狀況下,請求鏈路被延展,拉長,一個操做會被拆分紅多個服務,它們呈現線狀或網狀,依靠網絡通訊構建成一個總體。在這種狀況下,事務無疑變得更復雜。
事務鏈路延展性, 以下圖所示:
基於上述兩個複雜性,指望有一個統一的分佈式事務方案,可以像本地事務同樣,以幾乎無侵入的方式,知足各類存儲介質,各類複雜鏈路,是不現實的。
至少,在當前,尚未一個十分紅熟的解決方案。因此,通常狀況下,在分佈式下,事務會被拆分解決,並根據不一樣的狀況,採用不一樣的解決方案。
對於分佈式系統而言,須要保證分佈式系統中的數據一致性,保證數據在子系統中始終保持一致,避免業務出現問題。分佈式系統中對數要麼一塊兒成功,要麼一塊兒失敗,必須是一個總體性的事務。
分佈式事務指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不一樣的分佈式系統的不一樣節點之上。
簡單的說,在分佈式系統上一次大的操做由不一樣的小操做組成,這些小的操做分佈在不一樣的服務節點上,且屬於不一樣的應用,分佈式事務須要保證這些小操做要麼所有成功,要麼所有失敗。
舉個例子:在電商網站中,用戶對商品進行下單,須要在訂單表中建立一條訂單數據,同時須要在庫存表中修改當前商品的剩餘庫存數量,兩步操做一個添加,一個修改,咱們必定要保證這兩步操做必定同時操做成功或失敗,不然業務就會出現問題。
任何事務機制在實現時,都應該考慮事務的ACID特性,包括:本地事務、分佈式事務。對於分佈式事務而言,即便不能都很好的知足,也要考慮支持到什麼程度。
典型的分佈式事務場景:
跨庫事務指的是,一個應用某個功能須要操做多個庫,不一樣的庫中存儲不一樣的業務數據。筆者見過一個相對比較複雜的業務,一個業務中同時操做了9個庫。
下圖演示了一個服務同時操做2個庫的狀況:
一般一個庫數據量比較大或者預期將來的數據量比較大,都會進行水平拆分,也就是分庫分表。
以下圖,將數據庫B拆分紅了2個庫:
對於分庫分表的狀況,通常開發人員都會使用一些數據庫中間件來下降sql操做的複雜性。
如,對於sql:insert into user(id,name) values (1,"tianshouzhi"),(2,"wangxiaoxiao")。這條sql是操做單庫的語法,單庫狀況下,能夠保證事務的一致性。
可是因爲如今進行了分庫分表,開發人員但願將1號記錄插入分庫1,2號記錄插入分庫2。因此數據庫中間件要將其改寫爲2條sql,分別插入兩個不一樣的分庫,此時要保證兩個庫要不都成功,要不都失敗,所以基本上全部的數據庫中間件都面臨着分佈式事務的問題。
微服務架構是目前一個比較一個比較火的概念。例如上面筆者提到的一個案例,某個應用同時操做了9個庫,這樣的應用業務邏輯必然很是複雜,對於開發人員是極大的挑戰,應該拆分紅不一樣的獨立服務,以簡化業務邏輯。拆分後,獨立服務之間經過RPC框架來進行遠程調用,實現彼此的通訊。下圖演示了一個3個服務之間彼此調用的架構:
Service A完成某個功能須要直接操做數據庫,同時須要調用Service B和Service C,而Service B又同時操做了2個數據庫,Service C也操做了一個庫。須要保證這些跨服務的對多個數據庫的操做要不都成功,要不都失敗,實際上這多是最典型的分佈式事務場景。
分佈式事務實現方案必需要考慮性能的問題,若是爲了嚴格保證ACID特性,致使性能嚴重降低,那麼對於一些要求快速響應的業務,是沒法接受的。
數據庫事務ACID 四大特性,沒法知足分佈式事務的實際需求,這個時候又有一些新的大牛提出一些新的理論。
CAP定理是由加州大學伯克利分校Eric Brewer教授提出來的,他指出WEB服務沒法同時知足一下3個屬性:
具體地講在分佈式系統中,一個Web應用至多隻能同時支持上面的兩個屬性。所以,設計人員必須在一致性與可用性之間作出選擇。
2000年7月Eric Brewer教授僅僅提出來的是一個猜測,2年後,麻省理工學院的Seth Gilbert和Nancy Lynch從理論上證實了CAP理論,而且而一個分佈式系統最多隻能知足CAP中的2項。以後,CAP理論正式成爲分佈式計算領域的公認定理。
因此,CAP定理在迄今爲止的分佈式系統中都是適用的!
CAP的一致性、可用性、分區容錯性 具體以下:
數據一致性指「all nodes see the same data at the same time」,即更新操做成功並返回客戶端完成後,全部節點在同一時間的數據徹底一致,不能存在中間狀態。
分佈式環境中,一致性是指多個副本之間可否保持一致的特性。在一致性的需求下,當一個系統在數據一致的狀態下執行更新操做後,應該保證系統的數據仍然處理一致的狀態。
例如對於電商系統用戶下單操做,庫存減小、用戶資金帳戶扣減、積分增長等操做必須在用戶下單操做完成後必須是一致的。不能出現相似於庫存已經減小,而用戶資金帳戶還沒有扣減,積分也未增長的狀況。若是出現了這種狀況,那麼就認爲是不一致的。
數據一致性分爲強一致性、弱一致性、最終一致性。
面試題:什麼是數據一致性? 如今知道怎麼回答了吧!
系統提供的服務必須一直處於可用的狀態,對於用戶的每個操做請求老是可以在有限的時間內返回結果。
兩個度量的維度:
(1)有限時間內
對於用戶的一個操做請求,系統必須可以在指定的時間(響應時間)內返回對應的處理結果,若是超過了這個時間範圍,那麼系統就被認爲是不可用的。即這個響應時間必須在一個合理的值內,不讓用戶感到失望。
試想,若是一個下單操做,爲了保證分佈式事務的一致性,須要10分鐘才能處理完,那麼用戶顯然是沒法忍受的。
(2)返回正常結果
要求系統在完成對用戶請求的處理後,返回一個正常的響應結果。正常的響應結果一般可以明確地反映出對請求的處理結果,即成功或失敗,而不是一個讓用戶感到困惑的返回結果。好比返回一個系統錯誤如OutOfMemory,則認爲系統是不可用的。
「返回結果」是可用性的另外一個很是重要的指標,它要求系統在完成對用戶請求的處理後,返回一個正常的響應結果,不論這個結果是成功仍是失敗。
即分佈式系統在遇到任何網絡分區故障時,仍然須要可以保證對外提供知足一致性和可用性的服務,除非是整個網絡環境都發生了故障。
網絡分區,是指分佈式系統中,不一樣的節點分佈在不一樣的子網絡(機房/異地網絡)中,因爲一些特殊的緣由致使這些子網絡之間出現網絡不連通的狀態,但各個子網絡的內部網絡是正常的,從而致使整個系統的網絡環境被切分紅了若干孤立的區域。組成一個分佈式系統的每一個節點的加入與退出均可以看作是一個特殊的網絡分區。
放棄分區容錯性的話,則放棄了分佈式,放棄了系統的可擴展性
放棄可用性的話,則在遇到網絡分區或其餘故障時,受影響的服務須要等待必定的時間,再此期間沒法對外提供政策的服務,即不可用
放棄一致性的話(這裏指強一致),則系統沒法保證數據保持實時的一致性,在數據達到最終一致性時,有個時間窗口,在時間窗口內,數據是不一致的。
對於分佈式系統來講,P是不能放棄的,所以架構師一般是在可用性和一致性之間權衡。
目前不少大型網站及應用都是分佈式部署的,分佈式場景中的數據一致性問題一直是一個比較重要的話題。
基於 CAP理論,不少系統在設計之初就要對這三者作出取捨:
任何一個分佈式系統都沒法同時知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多隻能同時知足兩項。在互聯網領域的絕大多數的場景中,都須要犧牲強一致性來換取系統的高可用性,系統每每只須要保證最終一致性。
問:爲何分佈式系統中沒法同時保證一致性和可用性?
答:首先一個前提,對於分佈式系統而言,分區容錯性是一個最基本的要求,所以基本上咱們在設計分佈式系統的時候只能從一致性(C)和可用性(A)之間進行取捨。
若是保證了一致性(C):對於節點N1和N2,當往N1裏寫數據時,N2上的操做必須被暫停,只有當N1同步數據到N2時才能對N2進行讀寫請求,在N2被暫停操做期間客戶端提交的請求會收到失敗或超時。顯然,這與可用性是相悖的。
若是保證了可用性(A):那就不能暫停N2的讀寫操做,但同時N1在寫數據的話,這就違背了一致性的要求。
經過 CAP 理論,咱們知道沒法同時知足一致性、可用性和分區容錯性這三個特性,那要捨棄哪一個呢?
對於多數大型互聯網應用的場景,主機衆多、部署分散,並且如今的集羣規模愈來愈大,因此節點故障、網絡故障是常態,並且要保證服務可用性達到 N 個 9,即保證 P 和 A,捨棄C(退而求其次保證最終一致性)。雖然某些地方會影響客戶體驗,但沒達到形成用戶流程的嚴重程度。
對於涉及到錢財這樣不能有一絲讓步的場景,C 必須保證。網絡發生故障寧肯中止服務,這是保證 CA,捨棄 P。貌似這幾年國內銀行業發生了不下 10 起事故,但影響面不大,報道也很少,廣大羣衆知道的少。還有一種是保證 CP,捨棄 A。例如網絡故障是隻讀不寫。
總之:
ACID裏的一致性指的是事務執行先後,數據庫完整性,而CAP的一致性,指的是分佈式節點的數據的一致性。背景不一樣,無從可比
CAP是分佈式系統設計理論,BASE是CAP理論中AP方案的延伸,對於C咱們採用的方式和策略就是保證最終一致性;
eBay的架構師Dan Pritchett源於對大規模分佈式系統的實踐總結,在ACM上發表文章提出BASE理論,BASE理論是對CAP理論的延伸,核心思想是即便沒法作到強一致性(StrongConsistency,CAP的一致性就是強一致性),但應用能夠採用適合的方式達到最終一致性(Eventual Consitency)。
BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的縮寫。BASE基於CAP定理演化而來,核心思想是即時沒法作到強一致性,但每一個應用均可以根據自身業務特色,採用適當的方式來使系統達到最終一致性。
基本可用是指分佈式系統在出現不可預知的故障的時候,容許損失部分可用性,但不等於系統不可用。
(1)響應時間上的損失
當出現故障時,響應時間增長
(2)功能上的損失
當流量高峯期時,屏蔽一些功能的使用以保證系統穩定性(服務降級)
指容許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體可用性。
與硬狀態相對,便是指容許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體可用性,即容許系統在不一樣節點的數據副本之間進行數據同步的過程存在延時。
強調系統中全部的數據副本,在通過一段時間的同步後,最終可以達到一個一致的狀態。其本質是須要系統保證最終數據可以達到一致,而不須要實時保證系統數據的強一致性。
最終一致性可分爲以下幾種:
BASE理論是提出經過犧牲一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。
BASE理論面向的是大型高可用可擴展的分佈式系統,和傳統的事物ACID特性是相反的。
它徹底不一樣於ACID的強一致性模型,而是經過犧牲強一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。
但同時,在實際的分佈式場景中,不一樣業務單元和組件對數據一致性的要求是不一樣的,所以在具體的分佈式系統架構設計過程當中,ACID特性和BASE理論每每又會結合在一塊兒。
BASE理論是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網系統分佈式實踐的總結, 是基於CAP定理逐步演化而來的。BASE理論的核心思想是:即便沒法作到強一致性,但每一個應用均可以根據自身業務特色,採用適當的方式來使系統達到最終一致性。
BASE理論其實就是對CAP理論的延伸和補充,主要是對AP的補充。犧牲數據的強一致性,來保證數據的可用性,雖然存在中間裝填,但數據最終一致。
ACID 是傳統數據庫經常使用的設計理念,追求強一致性模型。BASE 支持的是大型分佈式系統,提出經過犧牲強一致性得到高可用性。
ACID 和 BASE 表明了兩種截然相反的設計哲學,在分佈式系統設計的場景中,系統組件對一致性要求是不一樣的,所以 ACID 和 BASE 又會結合使用。
分佈式場景下,多個服務同時對服務一個流程,好比電商下單場景,須要支付服務進行支付、庫存服務扣減庫存、訂單服務進行訂單生成、物流服務更新物流信息等。若是某一個服務執行失敗,或者網絡不通引發的請求丟失,那麼整個系統可能出現數據不一致的緣由。
上述場景就是分佈式一致性問題,追根到底,分佈式一致性的根本緣由在於數據的分佈式操做,引發的本地事務沒法保障數據的原子性引發。
分佈式一致性問題的解決思路有兩種,一種是分佈式事務,一種是儘可能經過業務流程避免分佈式事務。分佈式事務是直接解決問題,而業務規避其實經過解決出問題的地方(解決提問題的人)。其實在真實業務場景中,若是業務規避不是很麻煩的前提,最優雅的解決方案就是業務規避。
分佈式事務實現方案從類型上去分剛性事務、柔型事務:
剛性事務知足CAP的CP理論
柔性事務知足BASE理論(基本可用,最終一致)
剛性事務:一般無業務改造,強一致性,原生支持回滾/隔離性,低併發,適合短事務。
原則:剛性事務知足足CAP的CP理論
剛性事務指的是,要使分佈式事務,達到像本地式事務同樣,具有數據強一致性,從CAP來看,就是說,要達到CP狀態。
剛性事務:XA 協議(2PC、JTA、JTS)、3PC,但因爲同步阻塞,處理效率低,不適合大型網站分佈式場景。
柔性事務指的是,不要求強一致性,而是要求最終一致性,容許有中間狀態,也就是Base理論,換句話說,就是AP狀態。
與剛性事務相比,柔性事務的特色爲:有業務改造,最終一致性,實現補償接口,實現資源鎖定接口,高併發,適合長事務。
柔性事務分爲:
柔型事務:TCC/FMT、Saga(狀態機模式、Aop模式)、本地事務消息、消息事務(半消息)
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 規範 是 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各個階段的處理流程
兩階段提交(2PC)協議是XA規範定義的 數據一致性協議。
三階段提交(3PC)協議對 2PC協議的一種擴展。
Seata 是一款開源的分佈式事務解決方案,致力於在微服務架構下提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式
在 Seata 開源以前,Seata 對應的內部版本在阿里經濟體內部一直扮演着分佈式一致性中間件的角色,幫助經濟體平穩的度過歷年的雙11,對各BU業務進行了有力的支撐。商業化產品GTS 前後在阿里雲、金融雲進行售賣
做爲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應用。
事務是編程中必不可少的一項內容,基於此,爲了規範事務開發,Java增長了關於事務的規範,即JTA和JTS
JTA定義了一套接口,其中約定了幾種主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,並定義了這些角色之間須要遵照的規範,如Transaction的委託給TransactionManager等。
JTS也是一組規範,上面提到JTA中須要角色之間的交互,那應該如何交互?JTS就是約定了交互細節的規範。
整體上來講JTA更多的是從框架的角度來約定程序角色的接口,而JTS則是從具體實現的角度來約定程序角色之間的接口,二者各司其職。
Atomikos公司旗下有兩款著名的分佈事務產品:
這兩個產品的關係以下圖所示:
能夠看到,在開源版本中支持JTA/XA、JDBC、JMS的事務。
atomikos也支持與spring事務整合。
spring事務管理器的頂級抽象是PlatformTransactionManager接口,其提供了個重要的實現類:
顯然,在這裏,咱們須要配置的是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); } }
Seata AT 模式是加強型2pc模式。
AT 模式: 兩階段提交協議的演變,沒有一直鎖表
TX-LCN , 官方文檔 , github , 3千多星 , 5.0之後因爲框架兼容了LCN(2pc)、TCC、TXC 三種事務模式,爲了區分LCN模式,特此將LCN分佈式事務更名爲TX-LCN分佈式事務框架。
TX-LCN定位於一款事務協調性框架,框架其自己並不生產事務,而是本地事務的協調者,從而達到事務一致性的效果。
TX-LCN 主要有兩個模塊,Tx-Client(TC) ,Tx-Manager™.
2PC即Two-Phase Commit,二階段提交。
普遍應用在數據庫領域,爲了使得基於分佈式架構的全部節點能夠在進行事務處理時可以保持原子性和一致性。絕大部分關係型數據庫,都是基於2PC完成分佈式的事務處理。
顧名思義,2PC分爲兩個階段處理,階段一:提交事務請求、階段二:執行事務提交;
若是階段一超時或者出現異常,2PC的階段二:中斷事務
協調者在階段二決定是否最終執行事務提交操做。這一階段包含兩種情形:
執行事務提交
全部參與者reply Yes,那麼執行事務提交。
事情總會出現意外,當存在某一參與者向協調者發送No響應,或者等待超時。協調者只要沒法收到全部參與者的Yes響應,就會中斷事務。
顧名思義,兩階段提交在處理分佈式事務時分爲兩個階段:voting(投票階段,有的地方會叫作prepare階段)和commit階段。
2pc中存在兩個角色,事務協調者(seata、atomikos、lcn)和事務參與者,事務參與者一般是指應用的數據庫。
2PC 方案中,有一個事務管理器的角色,負責協調多個數據庫(資源管理器)的事務,事務管理器先問問各個數據庫你準備好了嗎?若是每一個數據庫都回復 ok,那麼就正式提交事務,在各個數據庫上執行操做;若是任何其中一個數據庫回答不 ok,那麼就回滾事務。
2PC 方案比較適合單體應用裏,跨多個庫的分佈式事務,並且由於嚴重依賴於數據庫層面來搞定複雜的事務,效率很低,絕對不適合高併發的場景。
2PC 方案實際不多用,通常來講某個系統內部若是出現跨多個庫的這麼一個操做,是不合規的。我能夠給你們介紹一下, 如今微服務,一個大的系統分紅幾百個服務,幾十個服務。通常來講,咱們的規定和規範,是要求每一個服務只能操做本身對應的一個數據庫。
若是你要操道別的服務對應的庫,不容許直連別的服務的庫,違反微服務架構的規範,你隨便交叉胡亂訪問,幾百個服務的話,全體亂套,這樣的一套服務是無法管理的,無法治理的,可能會出現數據被別人改錯,本身的庫被別人寫掛等狀況。
若是你要操道別人的服務的庫,你必須是經過調用別的服務的接口來實現,絕對不容許交叉訪問別人的數據庫。
優勢主要體如今實現原理簡單;
缺點比較多:
實際上分佈式事務是一件很是複雜的事情,兩階段提交只是經過增長了事務協調者(Coordinator)的角色來經過2個階段的處理流程來解決分佈式系統中一個事務須要跨多個服務節點的數據一致性問題。可是從異常狀況上考慮,這個流程也並非那麼的無懈可擊。
假設若是在第二個階段中Coordinator在接收到Partcipant的"Vote_Request"後掛掉了或者網絡出現了異常,那麼此時Partcipant節點就會一直處於本地事務掛起的狀態,從而長時間地佔用資源。固然這種狀況只會出如今極端狀況下,然而做爲一套健壯的軟件系統而言,異常Case的處理纔是真正考驗方案正確性的地方。
從流程上咱們能夠看得出,其最大缺點就在於它的執行過程當中間,節點都處於阻塞狀態。各個操做數據庫的節點此時都佔用着數據庫資源,只有當全部節點準備完畢,事務協調者纔會通知進行全局提交,參與者進行本地事務提交後纔會釋放資源。這樣的過程會比較漫長,對性能影響比較大。
事務協調者是整個XA模型的核心,一旦事務協調者節點掛掉,會致使參與者收不到提交或回滾的通知,從而致使參與者節點始終處於事務沒法完成的中間狀態。
在第二個階段,若是發生局部網絡問題,一部分事務參與者收到了提交消息,另外一部分事務參與者沒收到提交消息,那麼就會致使節點間數據的不一致問題。
針對2PC的缺點,研究者提出了3PC,即Three-Phase Commit。
做爲2PC的改進版,3PC將原有的兩階段過程,從新劃分爲CanCommit、PreCommit和do Commit三個階段。
在本階段,協調者會根據上一階段的反饋狀況來決定是否能夠執行事務的PreCommit操做。有如下兩種可能:
執行事務預提交
中斷事務
加入任意一個參與者向協調者發送No響應,或者等待超時,協調者在沒有獲得全部參與者響應時,便可以中斷事務:
在這個階段,會真正的進行事務提交,一樣存在兩種可能。
執行提交
中斷事務
在該階段,假設正常狀態的協調者接收到任一個參與者發送的No響應,或在超時時間內,仍舊沒收到反饋消息,就會中斷事務:
發送中斷請求。協調者向全部的參與者發送abort請求;
事務回滾。參與者收到abort請求後,會利用階段二中的Undo消息執行事務回滾,並在完成回滾後釋放佔用資源;
反饋事務回滾結果。參與者在完成回滾後向協調者發送Ack消息;
中端事務。協調者接收到全部參與者反饋的Ack消息後,完成事務中斷。
3PC有效下降了2PC帶來的參與者阻塞範圍,而且可以在出現單點故障後繼續達成一致;
但3PC帶來了新的問題,在參與者收到preCommit消息後,若是網絡出現分區,協調者和參與者沒法進行後續的通訊,這種狀況下,參與者在等待超時後,依舊會執行事務提交,這樣會致使數據的不一致。
三階段提交協議在協調者和參與者中都引入 超時機制,而且把兩階段提交協議的第一個階段拆分紅了兩步:詢問,而後再鎖資源,最後真正提交。三階段提交的三個階段分別爲:can_commit,pre_commit,do_commit。
在doCommit階段,若是參與者沒法及時接收到來自協調者的doCommit或者abort請求時,會在等待超時以後,繼續進行事務的提交。(其實這個應該是基於機率來決定的,當進入第三階段時,說明參與者在第二階段已經收到了PreCommit請求,那麼協調者產生PreCommit請求的前提條件是他在第二階段開始以前,收到全部參與者的CanCommit響應都是Yes。(一旦參與者收到了PreCommit,意味他知道你們其實都贊成修改了)因此,一句話歸納就是,當進入第三階段時, 因爲網絡超時等緣由,雖然參與者沒有收到commit或者abort響應,可是他有理由相信:成功提交的概率很大。 )
相對於2PC,3PC主要解決的單點故障問題,並減小阻塞, 由於一旦參與者沒法及時收到來自協調者的信息以後,他會默認執行commit。而不會一直持有事務資源並處於阻塞狀態。
可是這種機制也會致使數據一致性問題,由於,因爲網絡緣由,協調者發送的abort響應沒有及時被參與者接收到,那麼參與者在等待超時以後執行了commit操做。這樣就和其餘接到abort命令並執行回滾的參與者之間存在數據不一致的狀況。
相比較2PC而言,3PC對於協調者(Coordinator)和參與者(Partcipant)都設置了超時時間,而2PC只有協調者才擁有超時機制。這解決了一個什麼問題呢?
這個優化點,主要是避免了參與者在長時間沒法與協調者節點通信(協調者掛掉了)的狀況下,沒法釋放資源的問題,由於參與者自身擁有超時機制會在超時後,自動進行本地commit從而進行釋放資源。而這種機制也側面下降了整個事務的阻塞時間和範圍。
另外,經過CanCommit、PreCommit、DoCommit三個階段的設計,相較於2PC而言,多設置了一個緩衝階段保證了在最後提交階段以前各參與節點的狀態是一致的。
以上就是3PC相對於2PC的一個提升(相對緩解了2PC中的前兩個問題),可是3PC依然沒有徹底解決數據不一致的問題。假如在 DoCommit 過程,參與者A沒法接收協調者的通訊,那麼參與者A會自動提交,可是提交失敗了,其餘參與者成功了,此時數據就會不一致。
可是XA規範在1994年就出現了,至今沒有大規模流行起來,必然有他必定的缺陷:
數據鎖定:數據在事務未結束前,爲了保障一致性,根據數據隔離級別進行鎖定。
協議阻塞:本地事務在全局事務 沒 commit 或 callback前都是阻塞等待的。
性能損耗高:主要體如今事務協調增長的RT成本,併發事務數據使用鎖進行競爭阻塞。
XA協議比較簡單,並且一旦商業數據庫實現了XA協議,使用分佈式事務的成本也比較低。可是,XA也有致命的缺點,那就是性能不理想,特別是在交易下單鏈路,每每併發量很高,XA沒法知足高併發場景。XA目前在商業數據庫支持的比較理想,在mysql數據庫中支持的不太理想,mysql的XA實現,沒有記錄prepare階段日誌,主備切換回致使主庫與備庫數據不一致。許多nosql也沒有支持XA,這讓XA的應用場景變得很是狹隘。
其實也並不是不用,例如在IBM大型機上基於CICS不少跨資源是基於XA協議實現的分佈式事務,事實上XA也算分佈式事務處理的規範了,但在爲何互聯網中不多使用,究其緣由有如下幾個:
準確講XA是一個規範、協議,它只是定義了一系列的接口,只是目前大多數實現XA的都是數據庫或者MQ,因此提起XA每每多指基於資源層的底層分佈式事務解決方案。其實如今也有些數據分片框架或者中間件也支持XA協議,畢竟它的兼容性、廣泛性更好。
在電商領域等互聯網場景下,剛性事務在數據庫性能和處理能力上都暴露出了瓶頸。
柔性事務有兩個特性:基本可用和柔性狀態。
- 基本可用是指分佈式系統出現故障的時候容許損失一部分的可用性。
- 柔性狀態是指容許系統存在中間狀態,這個中間狀態不會影響系統總體的可用性,好比數據庫讀寫分離的主從同步延遲等。柔性事務的一致性指的是最終一致性。
柔性事務主要分爲補償型和通知型,
補償型事務又分:TCC、Saga;
通知型事務分:MQ事務消息、最大努力通知型。
補償型事務都是同步的,通知型事務都是異步的。
通知型事務的主流實現是經過MQ(消息隊列)來通知其餘事務參與者本身事務的執行狀態,引入MQ組件,有效的將事務參與者進行解耦,各參與者均可以異步執行,因此通知型事務又被稱爲異步事務。
通知型事務主要適用於那些須要異步更新數據,而且對數據的實時性要求較低的場景,主要包含:
異步確保型事務和最大努力通知事務兩種。
異步確保型事務:主要適用於內部系統的數據最終一致性保障,由於內部相對比較可控,如訂單和購物車、收貨與清算、支付與結算等等場景;
最大努力通知:主要用於外部系統,由於外部的網絡環境更加複雜和不可信,因此只能盡最大努力去通知實現數據最終一致性,好比充值平臺與運營商、支付對接等等跨網絡系統級別對接;
指將一系列同步的事務操做修改成基於消息隊列異步執行的操做,來避免分佈式事務中同步阻塞帶來的數據操做性能的降低。
基於MQ的事務消息方案主要依靠MQ的半消息機制來實現投遞消息和參與者自身本地事務的一致性保障。半消息機制實現原理其實借鑑的2PC的思路,是二階段提交的廣義拓展。
半消息:在原有隊列消息執行後的邏輯,若是後面的本地邏輯出錯,則不發送該消息,若是經過則告知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端的代碼貼出來你就明白了:
半消息發送時,會傳入一個回調類TransactionListener,使用時必須實現其中的兩個方法,executeLocalTransaction 方法會在broker返回半消息存儲成功後執行,咱們會在其中執行本地事務;checkLocalTransaction方法會在broker向producer發起反查時執行,咱們會在其中查詢庫表狀態。兩個方法的返回值都是消息狀態,就是告訴broker應該提交或者回滾半消息
有時候咱們目前的MQ組件並不支持事務消息,或者咱們想盡可能少的侵入業務方。這時咱們須要另一種方案「基於DB本地消息表「。
本地消息表最初由eBay 提出來解決分佈式事務的問題。是目前業界使用的比較多的方案之一,它的核心思想就是將分佈式事務拆分成本地事務進行處理。
本地消息表流程
發送消息方:
消息消費方:
生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。若是有靠譜的自動對帳補帳邏輯,這種方案仍是很是實用的。
本地消息表優缺點:
優勢:
缺點:
兩者的共性:
一、 事務消息都依賴MQ進行事務通知,因此都是異步的。
二、 事務消息在投遞方都是存在重複投遞的可能,須要有配套的機制去下降重複投遞率,實現更友好的消息投遞去重。
三、 事務消息的消費方,由於投遞重複的沒法避免,所以須要進行消費去重設計或者服務冪等設計。
兩者的區別:
MQ事務消息:
DB本地消息表:
最大努力通知方案的目標,就是發起通知方經過必定的機制,最大努力將業務處理結果通知到接收方。
最大努力通知型的最終一致性:
本質是經過引入按期校驗機制實現最終一致性,對業務的侵入性較低,適合於對最終一致性敏感度比較低、業務鏈路較短的場景。
最大努力通知事務主要用於外部系統,由於外部的網絡環境更加複雜和不可信,因此只能盡最大努力去通知實現數據最終一致性,好比充值平臺與運營商、支付對接、商戶通知等等跨平臺、跨企業的系統間業務交互場景;
而異步確保型事務主要適用於內部系統的數據最終一致性保障,由於內部相對比較可控,好比訂單和購物車、收貨與清算、支付與結算等等場景。
普通消息是沒法解決本地事務執行和消息發送的一致性問題的。由於消息發送是一個網絡通訊的過程,發送消息的過程就有可能出現發送失敗、或者超時的狀況。超時有可能發送成功了,有可能發送失敗了,消息的發送方是沒法肯定的,因此此時消息發送方不管是提交事務仍是回滾事務,都有可能不一致性出現。
因此,通知型事務的難度在於: 投遞消息和參與者本地事務的一致性保障。
由於核心要點一致,都是爲了保證消息的一致性投遞,因此,最大努力通知事務在投遞流程上跟異步確保型是同樣的,所以也有兩個分支:
基於MQ自身的事務消息方案
基於DB的本地事務消息表方案
要實現最大努力通知,能夠採用 MQ 的 ACK 機制。
最大努力通知事務在投遞以前,跟異步確保型流程都差很少,關鍵在於投遞後的處理。
由於異步確保型在於內部的事務處理,因此MQ和系統是直連而且無需嚴格的權限、安全等方面的思路設計。最大努力通知事務在於第三方系統的對接,因此最大努力通知事務有幾個特性:
業務主動方在完成業務處理後,向業務被動方(第三方系統)發送通知消息,容許存在消息丟失。
業務主動方提供遞增多擋位時間間隔(5min、10min、30min、1h、24h),用於失敗重試調用業務被動方的接口;在通知N次以後就再也不通知,報警+記日誌+人工介入。
業務被動方提供冪等的服務接口,防止通知重複消費。
業務主動方須要有按期校驗機制,對業務數據進行兜底;防止業務被動方沒法履行責任時進行業務回滾,確保數據最終一致性。
特色
要實現最大努力通知,能夠採用 按期檢查本地消息表的機制 。
發送消息方:
最大努力通知事務在於第三方系統的對接,因此最大努力通知事務有幾個特性:
業務主動方在完成業務處理後,向業務被動方(第三方系統)發送通知消息,容許存在消息丟失。
業務主動方提供遞增多擋位時間間隔(5min、10min、30min、1h、24h),用於失敗重試調用業務被動方的接口;在通知N次以後就再也不通知,報警+記日誌+人工介入。
業務被動方提供冪等的服務接口,防止通知重複消費。
業務主動方須要有按期校驗機制,對業務數據進行兜底;防止業務被動方沒法履行責任時進行業務回滾,確保數據最終一致性。
最大努力通知事務在我認知中,實際上是基於異步確保型事務發展而來適用於外部對接的一種業務實現。他們主要有的是業務差異,以下:
• 從參與者來講:最大努力通知事務適用於跨平臺、跨企業的系統間業務交互;異步確保型事務更適用於同網絡體系的內部服務交付。
• 從消息層面說:最大努力通知事務須要主動推送並提供多檔次時間的重試機制來保證數據的通知;而異步確保型事務只須要消息消費者主動去消費。
• 從數據層面說:最大努力通知事務還需額外的按期校驗機制對數據進行兜底,保證數據的最終一致性;而異步確保型事務只需保證消息的可靠投遞便可,自身無需對數據進行兜底處理。
通知型事務,是沒法解決本地事務執行和消息發送的一致性問題的。
由於消息發送是一個網絡通訊的過程,發送消息的過程就有可能出現發送失敗、或者超時的狀況。超時有可能發送成功了,有可能發送失敗了,消息的發送方是沒法肯定的,因此此時消息發送方不管是提交事務仍是回滾事務,都有可能不一致性出現。
消息中間件在分佈式系統中的核心做用就是異步通信、應用解耦和併發緩衝(也叫做流量削峯)。在分佈式環境下,須要經過網絡進行通信,就引入了數據傳輸的不肯定性,也就是CAP理論中的分區容錯性。
消息發送一致性是指產生消息的業務動做與消息發送動做一致,也就是說若是業務操做成功,那麼由這個業務操做所產生的消息必定要發送出去,不然就丟失。
常規的MQ隊列處理流程沒法實現消息的一致性。因此,須要藉助半消息、本地消息表,保障一致性。
對於未確認的消息,採用按規則從新投遞的方式進行處理。
對於以上流程,消息重複發送會致使業務處理接口出現重複調用的問題。消息消費過程當中消息重複發送的主要緣由就是消費者成功接收處理完消息後,消息中間件沒有及時更新投遞狀態致使的。若是容許消息重複發送,那麼消費方應該實現業務接口的冪等性設計。
可是基於消息實現的事務並不能解決全部的業務場景,例如如下場景:某筆訂單完成時,同時扣掉用戶的現金。
這裏事務發起方是管理訂單庫的服務,但對整個事務是否提交併不能只由訂單服務決定,由於還要確保用戶有足夠的錢,才能完成這筆交易,而這個信息在管理現金的服務裏。這裏咱們能夠引入基於補償實現的事務,其流程以下:
以上這個是正常成功的流程,異常流程須要回滾的話,將額外發送遠程調用到現金服務以加上以前扣掉的金額。
以上流程比基於消息隊列實現的事務的流程要複雜,同時開發的工做量也更多:
能夠看到,該事務流程相對於基於消息實現的分佈式事務更爲複雜,須要額外開發相關的業務回滾方法,也失去了服務間流量削峯填谷的功能。但其僅僅只比基於消息的事務複雜多一點,若不能使用基於消息隊列的最終一致性事務,那麼能夠優先考慮使用基於補償的事務形態。
補償模式使用一個額外的協調服務來協調各個須要保證一致性的業務服務,協調服務按順序調用各個業務微服務,若是某個業務服務調用異常(包括業務異常和技術異常)就取消以前全部已經調用成功的業務服務。
補償模式大體有TCC,和Saga兩種細分的方案
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(Try-Confirm-Cancel)分佈式事務模型相對於 XA 等傳統模型,其特徵在於它不依賴資源管理器(RM)對分佈式事務的支持,而是經過對業務邏輯的分解來實現分佈式事務。
TCC 模型認爲對於業務系統中一個特定的業務邏輯,其對外提供服務時,必須接受一些不肯定性,即對業務邏輯初步操做的調用僅是一個臨時性操做,調用它的主業務服務保留了後續的取消權。若是主業務服務認爲全局事務應該回滾,它會要求取消以前的臨時性操做,這就對應從業務服務的取消操做。而當主業務服務認爲全局事務應該提交時,它會放棄以前臨時性操做的取消權,這對應從業務服務的確認操做。每個初步操做,最終都會被確認或取消。
所以,針對一個具體的業務服務,TCC 分佈式事務模型須要業務系統提供三段業務邏輯:
初步操做 Try:完成全部業務檢查,預留必須的業務資源。
確認操做 Confirm:真正執行的業務邏輯,不做任何業務檢查,只使用 Try 階段預留的業務資源。所以,只要 Try 操做成功,Confirm 必須能成功。另外,Confirm 操做需知足冪等性,保證一筆分佈式事務有且只能成功一次。
取消操做 Cancel:釋放 Try 階段預留的業務資源。一樣的,Cancel 操做也須要知足冪等性。
TCC 分佈式事務模型包括三部分:
Try 階段: 調用 Try 接口,嘗試執行業務,完成全部業務檢查,預留業務資源。
Confirm 或 Cancel 階段: 二者是互斥的,只能進入其中一個,而且都知足冪等性,容許失敗重試。
Confirm 操做: 對業務系統作確認提交,確認執行業務操做,不作其餘業務檢查,只使用 Try 階段預留的業務資源。
Cancel 操做: 在業務執行錯誤,須要回滾的狀態下執行業務取消,釋放預留資源。
Try 階段失敗能夠 Cancel,若是 Confirm 和 Cancel 階段失敗了怎麼辦?
TCC 中會添加事務日誌,若是 Confirm 或者 Cancel 階段出錯,則會進行重試,因此這兩個階段須要支持冪等;若是重試失敗,則須要人工介入進行恢復和處理等。
然而基於補償的事務形態也並不是能實現全部的需求,如如下場景:某筆訂單完成時,同時扣掉用戶的現金,但交易未完成,也未被取消時,不能讓客戶看到錢變少了。
這時咱們能夠引入TCC,其流程以下:
以上是正常完成的流程,若爲異常流程,則須要發送遠程調用請求到現金服務,撤銷凍結的金額。
以上流程比基於補償實現的事務的流程要複雜,同時開發的工做量也更多:
TCC其實是最爲複雜的一種狀況,其能處理全部的業務場景,但不管出於性能上的考慮,仍是開發複雜度上的考慮,都應該儘可能避免該類事務。
比較一下TCC事務模型和DTP事務模型,以下所示:
這兩張圖看起來差異較大,實際上不少地方是相似的!
一、TCC模型中的 主業務服務 至關於 DTP模型中的AP,TCC模型中的從業務服務 至關於 DTP模型中的RM
在DTP模型中,應用AP操做多個資源管理器RM上的資源;而在TCC模型中,是主業務服務操做多個從業務服務上的資源。例如航班預約案例中,美團App就是主業務服務,而川航和東航就是從業務服務,主業務服務須要使用從業務服務上的機票資源。不一樣的是DTP模型中的資源提供者是相似於Mysql這種關係型數據庫,而TCC模型中資源的提供者是其餘業務服務。
二、TCC模型中,從業務服務提供的try、confirm、cancel接口 至關於 DTP模型中RM提供的prepare、commit、rollback接口
XA協議中規定了DTP模型中定RM須要提供prepare、commit、rollback接口給TM調用,以實現兩階段提交。
而在TCC模型中,從業務服務至關於RM,提供了相似的try、confirm、cancel接口。
三、事務管理器
DTP模型和TCC模型中都有一個事務管理器。不一樣的是:
在DTP模型中,階段1的(prepare)和階段2的(commit、rollback),都是由TM進行調用的。
在TCC模型中,階段1的try接口是主業務服務調用(綠色箭頭),階段2的(confirm、cancel接口)是事務管理器TM調用(紅色箭頭)。這就是 TCC 分佈式事務模型的二階段異步化功能,從業務服務的第一階段執行成功,主業務服務就能夠提交完成,而後再由事務管理器框架異步的執行各從業務服務的第二階段。這裏犧牲了必定的隔離性和一致性的,可是提升了長事務的可用性。
T就是Try,兩個C分別是Confirm和Cancel。
Try就是嘗試,請求鏈路中每一個參與者依次執行Try邏輯,若是都成功,就再執行Confirm邏輯,若是有失敗,就執行Cancel邏輯。
TCC與XA兩階段提交有着殊途同歸之妙,下圖列出了兩者之間的對比
在XA中,各個RM準備提交各自的事務分支,事實上就是準備提交資源的更新操做(insert、delete、update等);
而在TCC中,是主業務活動請求(try)各個從業務服務預留資源。
XA根據第一階段每一個RM是否都prepare成功,判斷是要提交仍是回滾。若是都prepare成功,那麼就commit每一個事務分支,反之則rollback每一個事務分支。
TCC中,若是在第一階段全部業務資源都預留成功,那麼confirm各個從業務服務,不然取消(cancel)全部從業務服務的資源預留請求。
TCC它會弱化每一個步驟中對於資源的鎖定,以達到一個能承受高併發的目的(基於最終一致性)。
具體的說明以下:
XA是資源層面的分佈式事務,強一致性,在兩階段提交的整個過程當中,一直會持有資源的鎖。
XA事務中的兩階段提交內部過程是對開發者屏蔽的,開發者從代碼層面是感知不到這個過程的。而事務管理器在兩階段提交過程當中,從prepare到commit/rollback過程當中,資源實際上一直都是被加鎖的。若是有其餘人須要更新這兩條記錄,那麼就必須等待鎖釋放。
TCC是業務層面的分佈式事務,最終一致性,不會一直持有資源的鎖。
TCC中的兩階段提交併無對開發者徹底屏蔽,也就是說從代碼層面,開發者是能夠感覺到兩階段提交的存在。try、confirm/cancel在執行過程當中,通常都會開啓各自的本地事務,來保證方法內部業務邏輯的ACID特性。其中:
一、try過程的本地事務,是保證資源預留的業務邏輯的正確性。
二、confirm/cancel執行的本地事務邏輯確認/取消預留資源,以保證最終一致性,也就是所謂的補償型事務(Compensation-Based Transactions)。因爲是多個獨立的本地事務,所以不會對資源一直加鎖。
另外,這裏提到confirm/cancel執行的本地事務是 補償性事務:
補償是一個獨立的支持ACID特性的本地事務,用於在邏輯上取消服務提供者上一個ACID事務形成的影響,對於一個長事務(long-running transaction),與其實現一個巨大的分佈式ACID事務,不如使用基於補償性的方案,把每一次服務調用當作一個較短的本地ACID事務來處理,執行完就當即提交
TCC是能夠解決部分場景下的分佈式事務的,可是,它的一個問題在於,須要每一個參與者都分別實現Try,Confirm和Cancel接口及邏輯,這對於業務的侵入性是巨大的。
TCC 方案嚴重依賴回滾和補償代碼,最終的結果是:回滾代碼邏輯複雜,業務代碼很難維護。因此,TCC 方案的使用場景較少,可是也有使用的場景。
好比說跟錢打交道的,支付、交易相關的場景,你們會用 TCC方案,嚴格保證分佈式事務要麼所有成功,要麼所有自動回滾,嚴格保證資金的正確性,保證在資金上不會出現問題。
SAGA能夠看作一個異步的、利用隊列實現的補償事務。
1987年普林斯頓大學的Hector Garcia-Molina和Kenneth Salem發表了一篇Paper Sagas,講述的是如何處理long lived transaction(長活事務)。Saga是一個長活事務可被分解成能夠交錯運行的子事務集合。其中每一個子事務都是一個保持數據庫一致性的真實事務。
論文地址:sagas
Saga模型是把一個分佈式事務拆分爲多個本地事務,每一個本地事務都有相應的執行模塊和補償模塊(對應TCC中的Confirm和Cancel),當Saga事務中任意一個本地事務出錯時,能夠經過調用相關的補償方法恢復以前的事務,達到事務最終一致性。
這樣的SAGA事務模型,是犧牲了必定的隔離性和一致性的,可是提升了long-running事務的可用性。
顯然,向前恢復沒有必要提供補償事務,若是你的業務中,子事務(最終)總會成功,或補償事務難以定義或不可能,向前恢復更符合你的需求。
理論上補償事務永不失敗,然而,在分佈式世界中,服務器可能會宕機,網絡可能會失敗,甚至數據中心也可能會停電。在這種狀況下咱們能作些什麼? 最後的手段是提供回退措施,好比人工干預。
Saga看起來頗有但願知足咱們的需求。全部長活事務均可以這樣作嗎?這裏有一些限制:
補償也有需考慮的事項:
但這對咱們的業務來講不是問題。其實難以撤消的行爲也有可能被補償。例如,發送電郵的事務能夠經過發送解釋問題的另外一封電郵來補償。
Saga對於ACID的保證和TCC同樣:
Saga不提供ACID保證,由於原子性和隔離性不能獲得知足。原論文描述以下:
full atomicity is not provided. That is, sagas may view the partial results of other sagas
經過saga log,saga能夠保證一致性和持久性。
SAGA模型的核心思想是,經過某種方案,將分佈式事務轉化爲本地事務,從而下降問題的複雜性。
好比以DB和MQ的場景爲例,業務邏輯以下:
向DB中插入一條數據。
向MQ中發送一條消息。
因爲上述邏輯中,對應了兩種存儲端,即DB和MQ,因此,簡單的經過本地事務是沒法解決的。那麼,依照SAGA模型,能夠有兩種解決方案。
RocketMQ新版本中,就支持了這種模式。
首先,咱們理解什麼是半消息。簡單來講,就是在消息上加了一個狀態。當發送者第一次將消息放入MQ後,該消息爲待確認狀態。該狀態下,該消息是不能被消費者消費的。發送者必須二次和MQ進行交互,將消息從待確認狀態變動爲確認狀態後,消息才能被消費者消費。待確認狀態的消息,就稱之爲半消息。
半消息的完整事務邏輯以下:
向MQ發送半消息。
向DB插入數據。
向MQ發送確認消息。
咱們發現,經過半消息的形式,將DB的操做夾在了兩個MQ操做的中間。假設,第2步失敗了,那麼,MQ中的消息就會一直是半消息狀態,也就不會被消費者消費。
那麼,半消息就一直存在於MQ中嗎?或者是說若是第3步失敗了呢?
爲了解決上面的問題,MQ引入了一個掃描的機制。即MQ會每隔一段時間,對全部的半消息進行掃描,並就掃描到的存在時間過長的半消息,向發送者進行詢問,詢問若是獲得確認回覆,則將消息改成確認狀態,如獲得失敗回覆,則將消息刪除。
半消息
如上,半消息機制的一個問題是:要求業務方提供查詢消息狀態接口,對業務方依然有較大的侵入性。
在DB中,新增一個消息表,用於存放消息。以下:
在DB業務表中插入數據。
在DB消息表中插入數據。
異步將消息表中的消息發送到MQ,收到ack後,刪除消息表中的消息。
本地消息表
如上,經過上述邏輯,將一個分佈式的事務,拆分紅兩大步。第1和第2,構成了一個本地的事務,從而解決了分佈式事務的問題。
這種解決方案,不須要業務端提供消息查詢接口,只須要稍微修改業務邏輯,侵入性是最小的。
SAGA適用於無需立刻返回業務發起方最終狀態的場景,例如:你的請求已提交,請稍後查詢或留意通知 之類。
將上述補償事務的場景用SAGA改寫,其流程以下:
以上爲成功的流程,若現金服務扣除金額失敗,那麼,最後一步訂單服務將會更新訂單狀態爲失敗。
其業務編碼工做量比補償事務多一點,包括如下內容:
但其相對於補償事務形態有性能上的優點,全部的本地子事務執行過程當中,都無需等待其調用的子事務執行,減小了加鎖的時間,這在事務流程較多較長的業務中性能優點更爲明顯。同時,其利用隊列進行進行通信,具備削峯填谷的做用。
所以該形式適用於不須要同步返回發起方執行最終結果、能夠進行補償、對性能要求較高、不介意額外編碼的業務場景。
但固然SAGA也能夠進行稍微改造,變成與TCC相似、能夠進行資源預留的形態
Saga相比TCC的缺點是缺乏預留動做,致使補償動做的實現比較麻煩:Ti就是commit,好比一個業務是發送郵件,在TCC模式下,先保存草稿(Try)再發送(Confirm),撤銷的話直接刪除草稿(Cancel)就好了。而Saga則就直接發送郵件了(Ti),若是要撤銷則得再發送一份郵件說明撤銷(Ci),實現起來有一些麻煩。
若是把上面的發郵件的例子換成:A服務在完成Ti後當即發送Event到ESB(企業服務總線,能夠認爲是一個消息中間件),下游服務監聽到這個Event作本身的一些工做而後再發送Event到ESB,若是A服務執行補償動做Ci,那麼整個補償動做的層級就很深。
不過沒有預留動做也能夠認爲是優勢:
Saga對比TCC
Saga和TCC都是補償型事務,他們的區別爲:
劣勢:
優點:
屬性 | 2PC | TCC | Saga | 異步確保型事務 | 盡最大努力通知 |
---|---|---|---|---|---|
事務一致性 | 強 | 弱 | 弱 | 弱 | 弱 |
複雜性 | 中 | 高 | 中 | 低 | 低 |
業務侵入性 | 小 | 大 | 小 | 中 | 中 |
使用侷限性 | 大 | 大 | 中 | 小 | 中 |
性能 | 低 | 中 | 高 | 高 | 高 |
維護成本 | 低 | 高 | 中 | 低 | 中 |
如下內容,來自 官網
Seata 是一款開源的分佈式事務解決方案,致力於提供高性能和簡單易用的分佈式事務服務。Seata 將爲用戶提供了 AT、TCC、SAGA 和 XA 事務模式,爲用戶打造一站式的分佈式解決方案。
相關連接:
什麼是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 AT使用了加強型二階段提交實現。
Seata 分三大模塊 :
在Seata的AT模式中,TM和RM都做爲SDK的一部分和業務服務在一塊兒,咱們能夠認爲是Client。TC是一個獨立的服務,經過服務的註冊、發現將本身暴露給Client們。
Seata 中有三大模塊中, TM 和 RM 是做爲 Seata 的客戶端與業務系統集成在一塊兒,TC 做爲 Seata 的服務端獨立部署。
在 Seata 中,分佈式事務的執行流程:
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數據源),文件系統等。
Seata 會有 4 種分佈式事務解決方案,分別是 AT 模式、TCC 模式、Saga 模式和 XA 模式。
Seata AT模式是最先支持的模式。AT模式是指Automatic (Branch) Transaction Mode自動化分支事務。
Seata AT 模式是加強型2pc模式,或者說是加強型的XA模型。
整體來講,AT 模式,是 2pc兩階段提交協議的演變,不一樣的地方,Seata AT 模式不會一直鎖表。
基於支持本地 ACID 事務的關係型數據庫。
好比,在MySQL 5.1以前的版本中,默認的搜索引擎是MyISAM,從MySQL 5.5以後的版本中,默認的搜索引擎變動爲InnoDB。MyISAM存儲引擎的特色是:表級鎖、不支持事務和全文索引。 因此,基於MyISAM 的表,就不支持Seata AT模式。
Java 應用,經過 JDBC 訪問數據庫。
兩階段提交協議的演變:
完整的AT在Seata所制定的事務模式下的模型圖:
咱們用一個比較簡單的業務場景來描述一下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 mode 的工做原理使用的電商下單場景的使用
以下圖所示:
在上圖中,協調者 shopping-service 先調用參與者 repo-service 扣減庫存,後調用參與者 order-service 生成訂單。這個業務流使用 Seata in XA mode 後的全局事務流程以下圖所示:
上圖描述的全局事務執行流程爲:
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)屬於第二階段。
在上面的電商業務場景中,購物服務調用庫存服務扣減庫存,調用訂單服務建立訂單,顯然這兩個調用過程要放在一個事務裏面。即:
start global_trx call 庫存服務的扣減庫存接口 call 訂單服務的建立訂單接口 commit global_trx
在庫存服務的數據庫中,存在以下的庫存表 t_repo:
在訂單服務的數據庫中,存在以下的訂單表 t_order:
如今,id 爲 40002 的用戶要購買一隻商品代碼爲 20002 的鼠標,整個分佈式事務的內容爲:
1)在庫存服務的庫存表中將記錄
修改成
2)在訂單服務的訂單表中添加一條記錄
以上操做,在 AT 模式的第一階段的流程圖以下:
從 AT 模式第一階段的流程來看,分支的本地事務在第一階段提交完成以後,就會釋放掉本地事務鎖定的本地記錄。這是 AT 模式和 XA 最大的不一樣點,在 XA 事務的兩階段提交中,被鎖定的記錄直到第二階段結束纔會被釋放。因此 AT 模式減小了鎖記錄的時間,從而提升了分佈式事務的處理效率。AT 模式之因此可以實現第一階段完成就釋放被鎖定的記錄,是由於 Seata 在每一個服務的數據庫中維護了一張 undo_log 表,其中記錄了對 t_order / t_repo 進行操做先後記錄的鏡像數據,即使第二階段發生異常,只需回放每一個服務的 undo_log 中的相應記錄便可實現全局回滾。
undo_log 的表結構:
第一階段結束以後,Seata 會接收到全部分支事務的提交狀態,而後決定是提交全局事務仍是回滾全局事務。
1)若全部分支事務本地提交均成功,則 Seata 決定全局提交。Seata 將分支提交的消息發送給各個分支事務,各個分支事務收到分支提交消息後,會將消息放入一個緩衝隊列,而後直接向 Seata 返回提交成功。以後,每一個本地事務會慢慢處理分支提交消息,處理的方式爲:刪除相應分支事務的 undo_log 記錄。之因此只需刪除分支事務的 undo_log 記錄,而不須要再作其餘提交操做,是由於提交操做已經在第一階段完成了(這也是 AT 和 XA 不一樣的地方)。這個過程以下圖所示:
分支事務之因此可以直接返回成功給 Seata,是由於真正關鍵的提交操做在第一階段已經完成了,清除 undo_log 日誌只是收尾工做,即使清除失敗了,也對整個分佈式事務不產生實質影響。
2)若任一分支事務本地提交失敗,則 Seata 決定全局回滾,將分支事務回滾消息發送給各個分支事務,因爲在第一階段各個服務的數據庫上記錄了 undo_log 記錄,分支事務回滾操做只需根據 undo_log 記錄進行補償便可。全局事務的回滾流程以下圖所示:
這裏對圖中的 二、3 步作進一步的說明:
1)因爲上文給出了 undo_log 的表結構,因此能夠經過 xid 和 branch_id 來找到當前分支事務的全部 undo_log 記錄;
2)拿到當前分支事務的 undo_log 記錄以後,首先要作數據校驗,若是 afterImage 中的記錄與當前的表記錄不一致,說明從第一階段完成到此刻期間,有別的事務修改了這些記錄,這會致使分支事務沒法回滾,向 Seata 反饋回滾失敗;若是 afterImage 中的記錄與當前的表記錄一致,說明從第一階段完成到此刻期間,沒有別的事務修改這些記錄,分支事務可回滾,進而根據 beforeImage 和 afterImage 計算出補償 SQL,執行補償 SQL 進行回滾,而後刪除相應 undo_log,向 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 語句。
AT模式是指Automatic (Branch) Transaction Mode 自動化分支事務,使用AT模式的前提是
基於支持本地 ACID 事務的關係型數據庫。
Java 應用,經過 JDBC 訪問數據庫。
seata-at的使用步驟
一、引入seata框架,配置好seata基本配置,創建undo_log表
二、消費者引入全局事務註解@GlobalTransactional
三、生產者引入全局事務註解@GlobalTransactional
此處沒有寫完, 尼恩的博文,都是迭代模式,後續會持續
TCC 與 Seata AT 事務同樣都是兩階段事務,它與 AT 事務的主要區別爲:
TCC 對業務代碼侵入嚴重
每一個階段的數據操做都要本身進行編碼來實現,事務框架沒法自動處理。
TCC 性能更高
沒必要對數據加全局鎖,容許多個事務同時操做數據。
Seata TCC 總體是 兩階段提交 的模型。一個分佈式的全局事務,全局事務是由若干分支事務組成的,分支事務要知足 兩階段提交 的模型要求,即須要每一個分支事務都具有本身的:
根據兩階段行爲模式的不一樣,咱們將分支事務劃分爲 Automatic (Branch) Transaction Mode 和 TCC (Branch) Transaction Mode.
AT 模式(參考連接 TBD)基於 支持本地 ACID 事務 的 關係型數據庫:
相應的,TCC 模式,不依賴於底層數據資源的事務支持:
所謂 TCC 模式,是指支持把 自定義 的分支事務歸入到全局事務的管理中。
以帳戶服務爲例,當下訂單時要扣減用戶帳戶金額:
假如用戶購買 100 元商品,要扣減 100 元。
TCC 事務首先對這100元的扣減金額進行預留,或者說是先凍結這100元:
若是第一階段可以順利完成,那麼說明「扣減金額」業務(分支事務)最終確定是能夠成功的。當全局事務提交時, TC會控制當前分支事務進行提交,若是提交失敗,TC 會反覆嘗試,直到提交成功爲止。
當全局事務提交時,就能夠使用凍結的金額來最終實現業務數據操做:
若是全局事務回滾,就把凍結的金額進行解凍,恢復到之前的狀態,TC 會控制當前分支事務回滾,若是回滾失敗,TC 會反覆嘗試,直到回滾完成爲止。
多個TCC全局事務容許併發,它們執行扣減金額時,只須要凍結各自的金額便可:
Seata TCC 模式 沒有寫完, 尼恩的博文,都是迭代模式,後續會持續優化
Saga模式是SEATA提供的長事務解決方案,在Saga模式中,業務流程中每一個參與者都提交本地事務,當出現某一個參與者失敗則補償前面已經成功的參與者,一階段正向服務和二階段補償服務都由業務開發實現。
理論基礎:Hector & Kenneth 發表論⽂ Sagas (1987)
目前SEATA提供的Saga模式是基於狀態機引擎來實現的,機制是:
注意: 異常發生時是否進行補償也可由用戶自定義決定
示例狀態圖:
Seata Saga 模式 沒有寫完, 尼恩的博文,都是迭代模式,後續會持續優化
在 Seata 定義的分佈式事務框架內,利用事務資源(數據庫、消息服務等)對 XA 協議的支持,以 XA 協議的機制來管理分支事務的一種 事務模式。
注意這裏的重點:利用事務資源對 XA 協議的支持,以 XA 協議的機制來管理分支事務。
XA 模式 運行在 Seata 定義的事務框架內:
XA 模式須要 XAConnection。
獲取 XAConnection 兩種方式:
第一種方式,給開發者增長了認知負擔,須要爲 XA 模式專門去學習和使用 XA 數據源,與 透明化 XA 編程模型的設計目標相違背。
第二種方式,對開發者比較友好,和 AT 模式使用同樣,開發者徹底沒必要關心 XA 層面的任何問題,保持本地編程模型便可。
咱們優先設計實現第二種方式:數據源代理根據普通數據源中獲取的普通 JDBC 鏈接建立出相應的 XAConnection。
類比 AT 模式的數據源代理機制,以下:
實際上,這種方法是在作數據庫驅動程序要作的事情。不一樣的廠商、不一樣版本的數據庫驅動實現機制是廠商私有的,咱們只能保證在充分測試過的驅動程序上是正確的,開發者使用的驅動程序版本差別極可能形成機制的失效。
這點在 Oracle 上體現很是明顯。參見 Druid issue:https://github.com/alibaba/druid/issues/3707
綜合考慮,XA 模式的數據源代理設計須要同時支持第一種方式:基於 XA 數據源進行代理。
類比 AT 模式的數據源代理機制,以下:
XA start 須要 Xid 參數。
這個 Xid 須要和 Seata 全局事務的 XID 和 BranchId 關聯起來,以便由 TC 驅動 XA 分支的提交或回滾。
目前 Seata 的 BranchId 是在分支註冊過程,由 TC 統一輩子成的,因此 XA 模式分支註冊的時機須要在 XA start 以前。
未來一個可能的優化方向:
把分支註冊儘可能延後。相似 AT 模式在本地事務提交以前才註冊分支,避免分支執行失敗狀況下,沒有意義的分支註冊。
這個優化方向須要 BranchId 生成機制的變化來配合。BranchId 不經過分支註冊過程生成,而是生成後再帶着 BranchId 去註冊分支。
從編程模型上,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的協調,保證最終一致性,也能夠業務解耦。
面試中若是你真的被問到,能夠分場景回答:
對於那些特別嚴格的場景,用的是Seata AT模式來保證強一致性;
準備好例子:你找一個嚴格要求數據絕對不能錯的場景(如電商交易交易中的庫存和訂單、優惠券),能夠回答使用成熟的如中間件Seata AT模式。
阿里開源了分佈式事務框架seata經歷過阿里生產環境大量考驗的框架。 seata支持Dubbo,Spring Cloud。
是Seata AT模式,保障強一致性,支持跨多個庫修改數據;
訂單庫:增長訂單
商品庫:扣減庫存
優惠券庫:預扣優惠券
對於數據一致性要求沒有那些特別嚴格、或者由不一樣系統執行子事務的場景,能夠回答使用Seata TCC 保障弱一致性方案
準備好例子:一個不是嚴格對數據一致性要求、或者由不一樣系統執行子事務的場景,如電商訂單支付服務,更新訂單狀態,發送成功支付成功消息,只須要保障弱一致性便可。
Seata TCC 模式,保障弱一致性,支持跨多個服務和系統修改數據,在上面的場景中,使用Seata TCC 模式事務
訂單服務:修改訂單狀態
通知服務:發送支付狀態
基於可靠消息的最終一致性,各個子事務能夠較長時間內異步,但數據絕對不能丟的場景。能夠使用異步確保型事務事。
能夠使用基於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/
http://www.javashuo.com/article/p-wgrnzeon-kt.html
https://blog.csdn.net/universsky2015/article/details/105727244/
http://www.javashuo.com/article/p-pxenkbis-wr.html
http://www.javashuo.com/article/p-azvfyfcf-og.html
https://www.jianshu.com/p/bfb619d3eea2
http://seata.io/zh-cn/docs/dev/mode/at-mode.html
http://www.javashuo.com/article/p-azvfyfcf-og.html
https://blog.csdn.net/wsdc0521/article/details/108223310
https://blog.csdn.net/lidatgb/article/details/38468005
http://www.javashuo.com/article/p-qzqsapxq-wr.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