分佈式計算原理之分佈式協調與同步(1)——分佈式事務

什麼是分佈式事務

事務,其實是包含一系列操作的、一個有邊界的工作序列,有明確的開始和結束標誌,且要麼被完全執行,要麼完全失敗,即 all or nothing。通常情況下,我們所說的事務指的都是本地事務,也就是在單機上的事務。

而分佈式事務,就是在分佈式系統中運行的事務,由多個本地事務組合而成。在分佈式場景下,對事務的處理操作可能來自不同的機器,甚至是來自不同的操作系統。文章開頭提到的電商處理訂單問題,就是典型的分佈式事務。

要深入理解分佈式事務,首先需要了解它的特徵。分佈式事務是多個事務的組合,那麼事務的特徵 ACID,也是分佈式事務的基本特徵,其中 ACID 具體含義如下:

  • 原子性(Atomicity),即事務最終的狀態只有兩種,全部執行成功和全部不執行。若處理事務的任何一項操作不成功,就會導致整個事務失敗。一旦操作失敗,所有操作都會被取消(即回滾),使得事務彷彿沒有被執行過一樣。
  • 一致性(Consistency),是指事務操作前和操作後,數據的完整性保持一致或滿足完整性約束。比如,用戶 A 和用戶 B 在銀行分別有 800 元和 600 元,總共 1400 元,用戶 A 給用戶 B 轉賬 200 元,分爲兩個步驟,從 A 的賬戶扣除 200 元和對 B 的賬戶增加 200 元 ; 一致性就是要求上述步驟操作後,最後的結果是用戶 A 還有 600 元,用戶 B 有 800 元,總共 1400 元,而不會出現用戶 A 扣除了 200 元,但用戶 B 未增加的情況 (該情況,用戶 A 和 B 均爲 600 元,總共 1200 元)。
  • 隔離性(Isolation),是指當系統內有多個事務併發執行時,多個事務不會相互干擾,即一個事務內部的操作及使用的數據,對其他併發事務是隔離的。
  • 持久性(Durability),也被稱爲永久性,是指一個事務完成了,那麼它對數據庫所做的更新就被永久保存下來了。即使發生系統崩潰或宕機等故障,只要數據庫能夠重新被訪問,那麼一定能夠將其恢復到事務完成時的狀態。

分佈式事務基本能夠滿足 ACID,其中的 C 是強一致性,也就是所有操作均執行成功,才提交最終結果,以保證數據一致性或完整性。但隨着分佈式系統規模不斷擴大,複雜度急劇上升,達成強一致性所需時間週期較長,限定了複雜業務的處理。爲了適應複雜業務,出現了 BASE 理論,該理論的一個關鍵點就是採用最終一致性代替強一致性,最終一致性也稱爲弱一致性。

如何實現分佈式事務

實際上,分佈式事務主要是解決在分佈式環境下,組合事務的一致性問題。實現分佈式事務有以下 3 種基本方法:

  • 基於 XA 協議的二階段提交協議方法;
  • 三階段提交協議方法;
  • 基於消息的最終一致性方法。

基於 XA 協議的二階段提交方法

XA 是一個分佈式事務協議,規定了事務管理器和資源管理器接口。因此,XA 協議可以分爲兩部分,即事務管理器和本地資源管理器。

XA 實現分佈式事務的原理,類似於集中式算法:事務管理器作爲協調者,負責各個本地資源的提交和回滾;而資源管理器就是分佈式事務的參與者,通常由數據庫實現,比如 Oracle、DB2 等商業數據庫都實現了 XA 接口。

基於 XA 協議的二階段提交方法中,二階段提交協議(The two-phase commit protocol,2PC),用於保證分佈式系統中事務提交時的數據一致性,是 XA 在全局事務中用於協調多個資源的機制。

那麼,兩階段提交協議如何保證分佈在不同節點上的分佈式事務的一致性呢?爲了保證它們的一致性,需要引入一個協調者來管理所有的節點,並確保這些節點正確提交操作結果,若提交失敗則放棄事務。

兩階段提交協議的執行過程,分爲投票(voting)和提交(commit)兩個階段。

投票爲第一階段,協調者(Coordinator,即事務管理器)會向事務的參與者(Cohort,即本地資源管理器)發起執行操作的 CanCommit 請求,並等待參與者的響應。參與者接收到請求後,會執行請求中的事務操作,記錄日誌信息但不提交,待參與者執行成功,則向協調者發送“Yes”消息,表示同意操作;若不成功,則發送“No”消息,表示終止操作。

當所有的參與者都返回了操作結果(Yes 或 No 消息)後,系統進入了提交階段。在提交階段,協調者會根據所有參與者返回的信息向參與者發送 DoCommit 或 DoAbort 指令:

  • 若協調者收到的都是“Yes”消息,則向參與者發送“DoCommit”消息,參與者會完成剩餘的操作並釋放資源,然後向協調者返回“HaveCommitted”消息;
  • 如果協調者收到的消息中包含“No”消息,則向所有參與者發送“DoAbort”消息,此時發送“Yes”的參與者則會根據之前執行操作時的回滾日誌對操作進行回滾,然後所有參與者會向協調者發送“HaveCommitted”消息;
  • 協調者接收到“HaveCommitted”消息,就意味着整個事務結束了

接下來,以用戶 A 要在網上下單購買 100 件 T 恤爲例,重點與你介紹下單操作和減庫存操作這兩個操作,幫助你加深對二階段提交協議的理解。

第一階段:訂單系統中將與用戶 A 有關的訂單數據庫鎖住,準備好增加一條關於用戶 A 購買 100 件 T 恤的信息,並將同意消息“Yes”回覆給協調者。而庫存系統由於 T 恤庫存不足,出貨失敗,因此向協調者回復了一個終止消息“No”。

在這裏插入圖片描述
第二階段:由於庫存系統操作不成功,因此,協調者就會向訂單系統和庫存系統發送“DoAbort”消息。訂單系統接收到“DoAbort”消息後,將系統內的數據退回到沒有用戶 A 購買 100 件 T 恤的版本,並釋放鎖住的數據庫資源。訂單系統和庫存系統完成操作後,向協調者發送“HaveCommitted”消息,表示完成了事務的撤銷操作。

至此,用戶 A 購買 100 件 T 恤這一事務已經結束,用戶 A 購買失敗。

在這裏插入圖片描述

由上述流程可以看出,二階段提交的算法思路可以概括爲:協調者下發請求事務操作,參與者將操作結果通知協調者,協調者根據所有參與者的反饋結果決定各參與者是要提交操作還是撤銷操作。

雖然基於 XA 的二階段提交算法基本滿足了事務的 ACID 特性,但存在以下缺點

  • 同步阻塞問題:二階段提交算法在執行過程中,所有參與節點都是事務阻塞型的。也就是說,當本地資源管理器佔有臨界資源時,其他資源管理器如果要訪問同一臨界資源,會處於阻塞狀態。
  • 單點故障問題:基於 XA 的二階段提交算法類似於集中式算法,一旦事務管理器發生故障,整個系統都處於停滯狀態。尤其是在提交階段,一旦事務管理器發生故障,資源管理器會由於等待管理器的消息,而一直鎖定事務資源,導致整個系統被阻塞。
  • 數據不一致問題:在提交階段,當協調者向參與者發送 DoCommit 請求之後,如果發生了局部網絡異常,或者在發送提交請求的過程中協調者發生了故障,就會導致只有一部分參與者接收到了提交請求並執行提交操作,但其他未接到提交請求的那部分參與者則無法執行事務提交。於是整個分佈式系統便出現了數據不一致的問題。

三階段提交協議方法

三階段提交協議(Three-phase commit protocol,3PC),是對二階段提交(2PC)的改進。爲了解決兩階段提交的同步阻塞和數據不一致問題,三階段提交引入了超時機制和準備階段。

  • 同時在協調者和參與者中引入超時機制。如果協調者或參與者在規定的時間內沒有接收到來自其他節點的響應,就會根據當前的狀態選擇提交或者終止整個事務。
  • 在第一階段和第二階段中間引入了一個準備階段,也就是在提交階段之前,加入了一個預提交階段。在預提交階段排除一些不一致的情況,保證在最後提交之前各參與節點的狀態是一致的。

也就是說,除了引入超時機制之外,3PC 把 2PC 的提交階段一分爲二,這樣三階段提交協議就有 CanCommit、PreCommit、DoCommit 三個階段。

第一:CanCommit 階段:

CanCommit 階段與 2PC 的投票階段類似:協調者向參與者發送請求操作(CanCommit 請求),詢問參與者是否可以執行事務提交操作,然後等待參與者的響應;參與者收到 CanCommit 請求之後,回覆 Yes,表示可以順利執行事務;否則回覆 No。

CanCommit 階段不同節點之間的事務請求成功和失敗的流程,如下所示

在這裏插入圖片描述

第二,PreCommit 階段:
協調者根據參與者的回覆情況,來決定是否可以進行 PreCommit 操作。

  • 如果所有參與者回覆的都是“Yes”,那麼協調者就會執行事務的預執行:
    • 發送預提交請求。協調者向參與者發送 PreCommit 請求,進入預提交階段。
    • 事務預提交。參與者接收到 PreCommit 請求後執行事務操作,並將 Undo 和 Redo 信息記錄到事務日誌中。
    • 響應反饋。如果參與者成功執行了事務操作,則返回 ACK 響應,同時開始等待最終指令。
  • 假如任何一個參與者向協調者發送了“No”消息,或者等待超時之後,協調者都沒有收到參與者的響應,就執行中斷事務的操作:發送中斷請求。協調者向所有參與者發送“Abort”消息。中斷事務。參與者收到“Abort”消息之後,或超時後仍未收到協調者的消息,執行事務的中斷操作。

預執行階段,不同節點上事務執行成功和失敗的流程,如下所示:
在這裏插入圖片描述

第三,DoCommit 階段:
DoCmmit 階段進行真正的事務提交,根據 PreCommit 階段協調者發送的消息,進入執行提交階段或事務中斷階段。

  • 執行提交階段:

    • 發送提交請求。協調者接收到所有參與者發送的 Ack 響應,從預提交狀態進入到提交狀態,並向所有參與者發送 DoCommit 消息。
    • 事務提交。參與者接收到 DoCommit 消息之後,正式提交事務。完成事務提交之後,釋放所有鎖住的資源。
    • 響應反饋。參與者提交完事務之後,向協調者發送 Ack 響應。
    • 完成事務。協調者接收到所有參與者的 Ack 響應之後,完成事務。
  • 事務中斷階段:

    • 發送中斷請求。協調者向所有參與者發送 Abort 請求。
    • 事務回滾。參與者接收到 Abort 消息之後,利用其在 PreCommit 階段記錄的 Undo 信息執行事務的回滾操作,並釋放所有鎖住的資源。
    • 反饋結果。參與者完成事務回滾之後,向協調者發送 Ack 消息。
    • 中斷事務。協調者接收到參與者反饋的 Ack 消息之後,執行事務的中斷,並結束事務。

執行階段不同節點上事務執行成功和失敗 (事務中斷) 的流程,如下所示:

在這裏插入圖片描述
在 DoCommit 階段,當參與者向協調者發送 Ack 消息後,如果長時間沒有得到協調者的響應,在默認情況下,參與者會自動將超時的事務進行提交,不會像兩階段提交那樣被阻塞住。

基於分佈式消息的最終一致性方案

2PC 和 3PC 這兩種方法,有兩個共同的缺點:

  • 都需要鎖定資源,降低系統性能;
  • 沒有解決數據不一致的問題。

因此,便有了通過分佈式消息來確保事務最終一致性的方案——基於分佈式消息的最終一致性方案解決了分佈式事務的問題。

基於分佈式消息的最終一致性方案的事務處理,引入了一個消息中間件(Message Queue,MQ),用於在多個應用之間進行消息傳遞。基於消息中間件協商多個節點分佈式事務執行操作的示意圖,如下所示:

在這裏插入圖片描述
仍然以網上購物爲例。假設用戶 A 在某電商平臺下了一個訂單,需要支付 50 元,發現自己的賬戶餘額共 150 元,就使用餘額支付,支付成功之後,訂單狀態修改爲支付成功,然後通知倉庫發貨。

在該事件中,涉及到了訂單系統、支付系統、倉庫系統,這三個系統是相互獨立的應用,通過遠程服務進行調用。
在這裏插入圖片描述
根據基於分佈式消息的最終一致性方案,用戶 A 通過終端手機首先在訂單系統上操作,然後整個購物的流程如下所示。

在這裏插入圖片描述

  1. 訂單系統把訂單消息發給消息中間件,消息狀態標記爲“待確認”。
  2. 消息中間件收到消息後,進行消息持久化操作,即在消息存儲系統中新增一條狀態爲“待發送”的消息。
  3. 消息中間件返回消息持久化結果(成功 / 失敗),訂單系統根據返回結果判斷如何進行業務操作。失敗,放棄訂單,結束(必要時向上層返回失敗結果);成功,則創建訂單.
  4. 訂單操作完成後,把操作結果(成功 / 失敗)發送給消息中間件。
  5. 消息中間件收到業務操作結果後,根據結果進行處理:失敗,刪除消息存儲中的消息,結束;成功,則更新消息存儲中的消息狀態爲“待發送(可發送)”,並執行消息投遞。
  6. 如果消息狀態爲“可發送”,則 MQ 會將消息發送給支付系統,表示已經創建好訂單,需要對訂單進行支付。支付系統也按照上述方式進行訂單支付操作。
  7. 訂單系統支付完成後,會將支付消息返回給消息中間件,中間件將消息傳送給訂單系統。訂單系統再調用庫存系統,進行出貨操作。

可以看出,分佈式事務中,當且僅當所有的事務均成功時整個流程才成功。所以,分佈式事務的一致性是實現分佈式事務的關鍵問題,目前來看還沒有一種很簡單、完美的方案可以應對所有場景

三種實現方式對比

現在,爲從算法一致性、執行方式、性能等角度對以上三種分佈式事務進行了對比:
在這裏插入圖片描述

拓展

在討論事務的時候,經常會提到剛性事務與柔性事務,但卻很難區分這兩種事務。下面將說明兩者之間的區別

  • 剛性事務,遵循 ACID 原則,具有強一致性。比如,數據庫事務。
  • 柔性事務,其實就是根據不同的業務場景使用不同的方法實現最終一致性,也就是說可以根據業務的特性做部分取捨,容忍一定時間內的數據不一致,最終結果保持一致性即可,即弱一致性。

總結來講,與剛性事務不同,柔性事務允許一定時間內,不同節點的數據不一致,但要求最終一致。而柔性事務的最終一致性,遵循的是 BASE 理論。

BASE 理論包括基本可用(Basically Available)、柔性狀態(Soft State)和最終一致性(Eventual Consistency)。

  • 基本可用:分佈式系統出現故障的時候,允許損失一部分功能的可用性。比如,某些電商 618 大促的時候,會對一些非核心鏈路的功能進行降級處理。
  • 柔性狀態:在柔性事務中,允許系統存在中間狀態,且這個中間狀態不會影響系統整體可用性。比如,數據庫讀寫分離,寫庫同步到讀庫(主庫同步到從庫)會有一個延時,其實就是一種柔性狀態。
  • 最終一致性:事務在操作過程中可能會由於同步延遲等問題導致不一致,但最終狀態下,數據都是一致的。

可見,BASE 理論爲了支持大型分佈式系統,通過犧牲強一致性,保證最終一致性,來獲得高可用性,是對 ACID 原則的弱化。具體到今天的三種分佈式事務實現方式,二階段提交、三階段提交方法,遵循的是 ACID 原則,而消息最終一致性方案遵循的就是 BASE 理論。

總結

你介紹了常見的三種分佈式事務實現方式,即基於 XA 協議的二階段提交方法,三階段方法以及基於分佈式消息的最終一致性方法。二階段和三階段方法是維護強一致性的算法,它們針對剛性事務,實現的是事務的 ACID 特性。而基於分佈式消息的最終一致性方案更適用於大規模分佈式系統,它維護的是事務的最終一致性,遵循的是 BASE 理論,因此適用於柔性事務。

以下是思維導圖:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章