分佈式事務——2PC、3PC 和 TCC

對於單機下的本地事務,很顯然咱們有已被實踐證實的成熟 ACID 模型來保證數據的嚴格一致性。但對於一個高訪問量、高併發的分佈式系統來講,若是咱們指望實現一套嚴格知足 ACID 特性的分佈式事務,極可能出現的狀況就是在系統的可用性和嚴格一致性之間出現衝突——由於當咱們要求分佈式系統具備嚴格一致性時,極可能就要犧牲掉系統的可用性。但毋庸置疑的一點是,可用性又是一個全部用戶不容許咱們討價還價的屬性,好比像淘寶這樣的網站,咱們要求它 7x24 小時不間斷地對外服務。所以,咱們須要在可用性和一致性之間作一些取捨,圍繞這種取捨,出現了兩個經典的分佈式理論——CAP 和 BASE,這二者也是全部分佈式事務協議的基石。數據庫

1、CAP 定理網絡

CAP 首次在 ACM PODC 會議上做爲猜測被提出,兩年後被證實爲定理,今後深深影響了分佈式計算的發展。CAP 理論告訴咱們,一個分佈式系統不可能同時知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三個基本需求,最多隻能同時知足其中的兩項。架構

  • 一致性:數據在多個副本之間保持一致。當有一個節點的數據發生更新後,其它節點應該也能同步地更新數據。併發

  • 可用性:對於用戶的每個操做請求,系統總能在有限的時間內返回結果。分佈式

  • 分區容錯性:分佈式系統中的不一樣節點可能分佈在不一樣的子網絡中,這些子網絡被稱爲網絡分區。因爲一些特殊緣由致使子網絡之間出現網絡不連通的狀況,系統仍須要可以保證對外提供一致性和可用性的服務。ide

CAP 定理告訴了咱們同時知足這三項是不可能的,那麼放棄其中的一項會是什麼樣的呢?高併發

放棄項性能

放棄P : 若是但願可以避免出現分區容錯性問題,一種較爲簡單的作法是將全部數據放在一個節點上。這樣確定不會受網絡分區影響。但此時分佈式系統也失去了意義。所以在實際的架構設計中,P是必定要知足的。網站

放棄A: 放棄可用性就是在系統遇到網絡分區或其餘故障時,受影響的服務能夠暫時不對外提供,等到系統恢復後再對外提供服務。spa

放棄C: 放棄一致性不表明徹底放棄數據一致性,這樣的話系統就沒有意義了。而是放棄數據的強一致性,保留最終一致性。這樣的系統沒法保證數據保持實時的一致性,但可以承諾數據最終會達到一個一致的狀態。

實際的實現中,咱們每每會把精力花在如何根據業務特色在 C(一致性)和 A(可用性)之間尋求平衡。

2、BASE 理論

BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性)三個短語的簡寫。BASE 是對 CAP 中一致性和可用性權衡的結果,其來源於對大規模互聯網系統分佈式實踐的總結。其核心思想是:即便沒法作到強一致性,但每一個應用均可以根據自身的業務特色,採用適當的方式來使系統達到最終一致性。

  • 基本可用:基本可用是指在分佈式系統出現不可預知的故障時,容許損失部分性能。好比:正常狀況下 0.5 秒就能返回結果的服務,但在故障狀況(網絡分區或其餘故障)下,須要 1~2 秒;正常狀況下,電商網站的首頁展現的是每一個用戶個性化的推薦內容,但在節日大促的狀況下,展現的是統一的推薦內容。

  • 軟狀態:軟狀態是指運行系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的總體可用性,即容許系統在不一樣節點的數據副本之間進行數據同步的過程存在延時。好比秒殺系統中,用戶餘額的扣減和商家餘額的增長能夠存在延時,當用戶餘額減了以後便可返回支付成功,商家餘額的增長能夠等系統壓力小的時候再作。

  • 最終一致性:最終一致性強調的是系統中全部的數據副本,在通過一段時間的同步後,最終能達到一個一致的狀態。這也是分佈式系統的一個基本要求。

嚴格遵照 ACID 的分佈式事務咱們稱爲剛性事務,而遵循 BASE 理論的事務咱們稱爲柔性事務。在分佈式環境下,剛性事務會讓系統的可用性變得難以忍受,所以實際生產中使用的分佈式事務都是柔性事務,其中使用最多的就是 2PC、3PC 和 TCC。

3、2PC 協議

2PC 是二階段提交(Two-phase Commit)的縮寫,顧名思義,這個協議分兩階段完成。第一個階段是準備階段,第二個階段是提交階段,準備階段和提交階段都是由事務管理器(協調者)發起的,協調的對象是資源管理器(參與者)。二階段提交協議的概念來自 X/Open 組織提出的分佈式事務的規範 XA 協議,協議主要定義了(全局)事務管理器和(局部)資源管理器之間的接口。XA 接口是雙向的系統接口,在事務管理器以及一個或多個資源管理器之間造成通訊橋樑。Java 平臺上的事務規範 JTA(Java Transaction API)提供了對 XA 事務的支持,它要求全部須要被分佈式事務管理的資源(由不一樣廠商實現)都必須實現規定接口(XAResource 中的 prepare、commit 和 rollback 等)。

兩階段以下:

  • 準備階段:協調者向參與者發起指令,參與者評估本身的狀態,若是參與者評估指令能夠完成,參與者會寫 redo 和 undo 日誌,而後鎖定資源,執行操做,可是並不提交。

  • 提交階段:若是每一個參與者明確返回準備成功,也就是預留資源和執行操做成功,協調者向參與者發起提交指令,參與者提交資源變動的事務,釋放鎖定的資源;若是任何一個參與者明確返回準備失敗,也就是預留資源或者執行操做失敗,協調者向參與者發起停止指令,參與者取消已經變動的事務,執行 undo 日誌,釋放鎖定的資源。

兩階段提交協議成功場景示意圖以下:


分佈式事務——2PC、3PC 和 TCC



咱們看到兩階段提交協議在準備階段鎖定資源,是一個重量級的操做,並能保證強一致性,可是實現起來複雜、成本較高,不夠靈活,更重要的是它有以下致命的問題:

  • 阻塞:從上面的描述來看,對於任何一次指令必須收到明確的響應,纔會繼續作下一步,不然處於阻塞狀態,佔用的資源被一直鎖定,不會被釋放。

  • 單點故障:若是協調者宕機,參與者沒有了協調者指揮,會一直阻塞,儘管能夠經過選舉新的協調者替代原有協調者,可是若是以前協調者在發送一個提交指令後宕機,而提交指令僅僅被一個參與者接受,而且參與者接收後也宕機,新上任的協調者沒法處理這種狀況。

  • 腦裂:協調者發送提交指令,有的參與者接收到執行了事務,有的參與者沒有接收到事務,就沒有執行事務,多個參與者之間是不一致的。

上面全部的這些問題,都是須要人工干預處理,沒有自動化的解決方案,所以兩階段提交協議在正常狀況下能保證系統的強一致性,可是在出現異常狀況下,當前處理的操做處於錯誤狀態,須要管理員人工干預解決,所以可用性不夠好,這也符合 CAP 定理的一致性和可用性不能兼得的原理。

4、3PC 協議

三階段提交協議(3PC 協議)是兩階段提交協議的改進版本。它經過超時機制解決了阻塞的問題,而且把兩個階段增長爲三個階段:

  • 詢問階段:協調者詢問參與者是否能夠完成指令,協調者只須要回答是仍是不是,而不須要作真正的操做,這個階段參與者在等待超時後會自動停止。

  • 準備階段:若是在詢問階段全部的參與者都返回能夠執行操做,協調者向參與者發送預執行請求,而後參與者寫 redo 和 undo 日誌,鎖定資源,執行操做,可是不提交操做;若是在詢問階段任何參與者返回不能執行操做的結果,則協調者向參與者發送停止請求,這裏的邏輯與兩階段提交協議的的準備階段是類似的,這個階段參與者在等待超時後會自動提交。

  • 提交階段:若是每一個參與者在準備階段返回準備成功,也就是預留資源和執行操做成功,協調者向參與者發起提交指令,參與者提交資源變動的事務,釋放鎖定的資源;若是任何一個參與者返回準備失敗,也就是預留資源或者執行操做失敗,協調者向參與者發起停止指令,參與者取消已經變動的事務,執行 undo 日誌,釋放鎖定的資源,這裏的邏輯與兩階段提交協議的提交階段一致。

三階段提交協議成功場景示意圖以下:


分佈式事務——2PC、3PC 和 TCC



這裏與兩階段提交協議有兩個主要的不一樣:

  • 增長了一個詢問階段,詢問階段能夠確保儘量早的發現沒法執行操做而須要停止的行爲,可是它並不能發現全部的這種行爲,只會減小這種狀況的發生。

  • 增長了等待超時的處理邏輯,若是在詢問階段等待超時,則自動停止;若是在準備階段以後等待超時,則自動提交。這也是根據機率統計上的正確性最大。

三階段提交協議相比二階段提交協議,避免了資源被無限鎖定的狀況。但也增長了系統的複雜度,增長了參與者和協調者之間的通訊次數。

5、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 三個接口。流程以下:

  1. 事務發起方向事務協調器發起事務請求,事務協調器調用全部事務參與者的 try 方法完成資源的預留,這時候並無真正執行業務,而是爲後面具體要執行的業務預留資源,這裏完成了一階段。

  2. 若是事務協調器發現有參與者的 try 方法預留資源時候發現資源不夠,則調用參與方的 cancel 方法回滾預留的資源,須要注意 cancel 方法須要實現業務冪等,由於有可能調用失敗(好比網絡緣由參與者接受到了請求,可是因爲網絡緣由事務協調器沒有接受到回執)會重試。

  3. 若是事務協調器發現全部參與者的 try 方法返回都 OK,則事務協調器調用全部參與者的 confirm 方法,不作資源檢查,直接進行具體的業務操做。

  4. 若是協調器發現全部參與者的 confirm 方法都 OK 了,則分佈式事務結束。

  5. 若是協調器發現有些參與者的 confirm 方法失敗了,或者因爲網絡緣由沒有收到回執,則協調器會進行重試。這裏若是重試必定次數後仍是失敗,會怎麼樣?常見的是作事務補償。

TCC 執行場景示意圖以下:

分佈式事務——2PC、3PC 和 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 三個操做,應用侵入性較強,改形成本高。

相關文章
相關標籤/搜索