您好,登錄后才能下訂單哦!
對于單機下的本地事務,很顯然我們有已被實踐證明的成熟 ACID 模型來保證數據的嚴格一致性。但對于一個高訪問量、高并發的分布式系統來說,如果我們期望實現一套嚴格滿足 ACID 特性的分布式事務,很可能出現的情況就是在系統的可用性和嚴格一致性之間出現沖突——因為當我們要求分布式系統具有嚴格一致性時,很可能就要犧牲掉系統的可用性。但毋庸置疑的一點是,可用性又是一個所有用戶不允許我們討價還價的屬性,比如像淘寶這樣的網站,我們要求它 7x24 小時不間斷地對外服務。因此,我們需要在可用性和一致性之間做一些取舍,圍繞這種取舍,出現了兩個經典的分布式理論——CAP 和 BASE,這兩者也是所有分布式事務協議的基石。
一、CAP 定理
CAP 首次在 ACM PODC 會議上作為猜想被提出,兩年后被證明為定理,從此深深影響了分布式計算的發展。CAP 理論告訴我們,一個分布式系統不可能同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三個基本需求,最多只能同時滿足其中的兩項。
一致性:數據在多個副本之間保持一致。當有一個節點的數據發生更新后,其它節點應該也能同步地更新數據。
可用性:對于用戶的每一個操作請求,系統總能在有限的時間內返回結果。
分區容錯性:分布式系統中的不同節點可能分布在不同的子網絡中,這些子網絡被稱為網絡分區。由于一些特殊原因導致子網絡之間出現網絡不連通的情況,系統仍需要能夠保證對外提供一致性和可用性的服務。
CAP 定理告訴了我們同時滿足這三項是不可能的,那么放棄其中的一項會是什么樣的呢?
放棄項
放棄P : 如果希望能夠避免出現分區容錯性問題,一種較為簡單的做法是將所有數據放在一個節點上。這樣肯定不會受網絡分區影響。但此時分布式系統也失去了意義。因此在實際的架構設計中,P是一定要滿足的。
放棄A: 放棄可用性就是在系統遇到網絡分區或其他故障時,受影響的服務可以暫時不對外提供,等到系統恢復后再對外提供服務。
放棄C: 放棄一致性不代表完全放棄數據一致性,這樣的話系統就沒有意義了。而是放棄數據的強一致性,保留最終一致性。這樣的系統無法保證數據保持實時的一致性,但能夠承諾數據最終會達到一個一致的狀態。
實際的實現中,我們往往會把精力花在如何根據業務特點在 C(一致性)和 A(可用性)之間尋求平衡。
二、BASE 理論
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性)三個短語的簡寫。BASE 是對 CAP 中一致性和可用性權衡的結果,其來源于對大規模互聯網系統分布式實踐的總結。其核心思想是:即使無法做到強一致性,但每個應用都可以根據自身的業務特點,采用適當的方式來使系統達到最終一致性。
基本可用:基本可用是指在分布式系統出現不可預知的故障時,允許損失部分性能。比如:正常情況下 0.5 秒就能返回結果的服務,但在故障情況(網絡分區或其他故障)下,需要 1~2 秒;正常情況下,電商網站的首頁展示的是每個用戶個性化的推薦內容,但在節日大促的情況下,展示的是統一的推薦內容。
軟狀態:軟狀態是指運行系統中的數據存在中間狀態,并認為該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程存在延時。比如秒殺系統中,用戶余額的扣減和商家余額的增加可以存在延時,當用戶余額減了之后即可返回支付成功,商家余額的增加可以等系統壓力小的時候再做。
最終一致性:最終一致性強調的是系統中所有的數據副本,在經過一段時間的同步后,最終能達到一個一致的狀態。這也是分布式系統的一個基本要求。
嚴格遵守 ACID 的分布式事務我們稱為剛性事務,而遵循 BASE 理論的事務我們稱為柔性事務。在分布式環境下,剛性事務會讓系統的可用性變得難以忍受,因此實際生產中使用的分布式事務都是柔性事務,其中使用最多的就是 2PC、3PC 和 TCC。
三、2PC 協議
2PC 是二階段提交(Two-phase Commit)的縮寫,顧名思義,這個協議分兩階段完成。第一個階段是準備階段,第二個階段是提交階段,準備階段和提交階段都是由事務管理器(協調者)發起的,協調的對象是資源管理器(參與者)。二階段提交協議的概念來自 X/Open 組織提出的分布式事務的規范 XA 協議,協議主要定義了(全局)事務管理器和(局部)資源管理器之間的接口。XA 接口是雙向的系統接口,在事務管理器以及一個或多個資源管理器之間形成通信橋梁。Java 平臺上的事務規范 JTA(Java Transaction API)提供了對 XA 事務的支持,它要求所有需要被分布式事務管理的資源(由不同廠商實現)都必須實現規定接口(XAResource 中的 prepare、commit 和 rollback 等)。
兩階段如下:
準備階段:協調者向參與者發起指令,參與者評估自己的狀態,如果參與者評估指令可以完成,參與者會寫 redo 和 undo 日志,然后鎖定資源,執行操作,但是并不提交。
提交階段:如果每個參與者明確返回準備成功,也就是預留資源和執行操作成功,協調者向參與者發起提交指令,參與者提交資源變更的事務,釋放鎖定的資源;如果任何一個參與者明確返回準備失敗,也就是預留資源或者執行操作失敗,協調者向參與者發起中止指令,參與者取消已經變更的事務,執行 undo 日志,釋放鎖定的資源。
兩階段提交協議成功場景示意圖如下:
我們看到兩階段提交協議在準備階段鎖定資源,是一個重量級的操作,并能保證強一致性,但是實現起來復雜、成本較高,不夠靈活,更重要的是它有如下致命的問題:
阻塞:從上面的描述來看,對于任何一次指令必須收到明確的響應,才會繼續做下一步,否則處于阻塞狀態,占用的資源被一直鎖定,不會被釋放。
單點故障:如果協調者宕機,參與者沒有了協調者指揮,會一直阻塞,盡管可以通過選舉新的協調者替代原有協調者,但是如果之前協調者在發送一個提交指令后宕機,而提交指令僅僅被一個參與者接受,并且參與者接收后也宕機,新上任的協調者無法處理這種情況。
腦裂:協調者發送提交指令,有的參與者接收到執行了事務,有的參與者沒有接收到事務,就沒有執行事務,多個參與者之間是不一致的。
上面所有的這些問題,都是需要人工干預處理,沒有自動化的解決方案,因此兩階段提交協議在正常情況下能保證系統的強一致性,但是在出現異常情況下,當前處理的操作處于錯誤狀態,需要管理員人工干預解決,因此可用性不夠好,這也符合 CAP 定理的一致性和可用性不能兼得的原理。
四、3PC 協議
三階段提交協議(3PC 協議)是兩階段提交協議的改進版本。它通過超時機制解決了阻塞的問題,并且把兩個階段增加為三個階段:
詢問階段:協調者詢問參與者是否可以完成指令,協調者只需要回答是還是不是,而不需要做真正的操作,這個階段參與者在等待超時后會自動中止。
準備階段:如果在詢問階段所有的參與者都返回可以執行操作,協調者向參與者發送預執行請求,然后參與者寫 redo 和 undo 日志,鎖定資源,執行操作,但是不提交操作;如果在詢問階段任何參與者返回不能執行操作的結果,則協調者向參與者發送中止請求,這里的邏輯與兩階段提交協議的的準備階段是相似的,這個階段參與者在等待超時后會自動提交。
提交階段:如果每個參與者在準備階段返回準備成功,也就是預留資源和執行操作成功,協調者向參與者發起提交指令,參與者提交資源變更的事務,釋放鎖定的資源;如果任何一個參與者返回準備失敗,也就是預留資源或者執行操作失敗,協調者向參與者發起中止指令,參與者取消已經變更的事務,執行 undo 日志,釋放鎖定的資源,這里的邏輯與兩階段提交協議的提交階段一致。
三階段提交協議成功場景示意圖如下:
這里與兩階段提交協議有兩個主要的不同:
增加了一個詢問階段,詢問階段可以確保盡可能早的發現無法執行操作而需要中止的行為,但是它并不能發現所有的這種行為,只會減少這種情況的發生。
增加了等待超時的處理邏輯,如果在詢問階段等待超時,則自動中止;如果在準備階段之后等待超時,則自動提交。這也是根據概率統計上的正確性最大。
三階段提交協議相比二階段提交協議,避免了資源被無限鎖定的情況。但也增加了系統的復雜度,增加了參與者和協調者之間的通信次數。
五、TCC 協議
無論是 2PC 還是 3PC,都存在一個大粒度資源鎖定的問題。為了解釋這個問題,我們先來想象這樣一種場景,用戶在電商網站購買商品1000元,使用余額支付800元,使用紅包支付200元。我們看一下在 2PC 中的流程:
prepare 階段:
下單系統插入一條訂單記錄,不提交
余額系統減 800 元,給記錄加鎖,寫 redo 和 undo 日志,不提交
紅包系統減 200 元,給記錄加鎖,寫 redo 和 undo 日志,不提交
commit 階段:
下單系統提交訂單記錄
余額系統提交,釋放鎖
紅包系統提交,釋放鎖
為什么說這是一種大粒度的資源鎖定呢?是因為在 prepare 階段,當數據庫給用戶余額減 800 元之后,為了維持隔離性,會給該條記錄加鎖,在事務提交前,其它事務無法再訪問該條記錄。但實際上,我們只需要預留其中的 800 元,不需要鎖定整個用戶余額。這是 2PC 和 3PC 的局限,因為這兩者是資源層的協議,無法提供更靈活的資源鎖定操作。為了解決這個問題,TCC 應運而生。TCC 本質上也是一個二階段提交協議,但和 JTA 中的二階段協議不同的是,它是一個服務層的協議,因此開發者可以根據業務自由控制資源鎖定的粒度。我們等會兒可以看到 TCC 在上面這個場景中的優勢,但在那之前,我們先來看一下 TCC 協議的運行過程。
TCC 將事務的提交過程分為 try-confirm-cancel(實際上 TCC 就是 try、confirm、cancel 的簡稱) 三個階段:
try:完成業務檢查、預留業務資源
confirm:使用預留的資源執行業務操作(需要保證冪等性)
cancel:取消執行業務操作,釋放預留的資源(需要保證冪等性)
和 JTA 二階段事務的參與方都要實現 prepare、commit、rollback 一樣,TCC 的事務參與方也必須實現 try、confirm、cancel 三個接口。流程如下:
事務發起方向事務協調器發起事務請求,事務協調器調用所有事務參與者的 try 方法完成資源的預留,這時候并沒有真正執行業務,而是為后面具體要執行的業務預留資源,這里完成了一階段。
如果事務協調器發現有參與者的 try 方法預留資源時候發現資源不夠,則調用參與方的 cancel 方法回滾預留的資源,需要注意 cancel 方法需要實現業務冪等,因為有可能調用失敗(比如網絡原因參與者接受到了請求,但是由于網絡原因事務協調器沒有接受到回執)會重試。
如果事務協調器發現所有參與者的 try 方法返回都 OK,則事務協調器調用所有參與者的 confirm 方法,不做資源檢查,直接進行具體的業務操作。
如果協調器發現所有參與者的 confirm 方法都 OK 了,則分布式事務結束。
如果協調器發現有些參與者的 confirm 方法失敗了,或者由于網絡原因沒有收到回執,則協調器會進行重試。這里如果重試一定次數后還是失敗,會怎么樣?常見的是做事務補償。
TCC 執行場景示意圖如下:
現在我們再回到開始的那個支付場景中,看看 TCC 在該場景中的流程:
Try操作
tryX 下單系統創建待支付訂單
tryY 凍結賬戶紅包 200 元
tryZ 凍結資金賬戶 800 元
Confirm操作
confirmX 訂單更新為支付成功
confirmY 扣減賬戶紅包 200 元
confirmZ 扣減資金賬戶 800 元
Cancel操作
cancelX 訂單處理異常,資金紅包退回,訂單支付失敗
cancelY 凍結紅包失敗,賬戶余額退回,訂單支付失敗
cancelZ 凍結余額失敗,賬戶紅包退回,訂單支付失敗
可以看到,我們使用了凍結代替了原先的賬號鎖定(實際操作中,凍結操作可以用數據庫減操作+日志實現),這樣在凍結操作之后,事務提交之前,其它事務也能使用賬戶余額,提高了并發性。
總結一下,相比于二階段提交協議,TCC 主要有以下區別:
2PC 位于資源層而 TCC 位于服務層。
2PC 的接口由第三方廠商實現,TCC 的接口由開發人員實現。
TCC 可以更靈活地控制資源鎖定的粒度。
TCC 對應用的侵入性強。業務邏輯的每個分支都需要實現 try、confirm、cancel 三個操作,應用侵入性較強,改造成本高。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。