細品分佈式事務

背景

我們平時的單機事物的使用,一步操作,要麼全部執行完成,要麼全部不執行,也就是ALL or Nothing。但是如果我們使用了分佈式,一件事情分爲多個分別在多個在不同的機器(進程)上執行。那對於這種的事物我們應該如何控制呢?

事物

ACID屬性

  • Atomicity :原子性,原子是人類目前發現最小單元,他是不能再分的,也就是事物也具有這個屬性。
  • Consistency:一致性,事物在執行前後,具有完整性約束。也就是保證該事物的各個步驟下來是是正確的
  • Isolation:隔離性,也許你立馬想到了mysql的4種隔離級別,讀提交,讀未提交,可重複讀取,串行化。這裏的隔離的意思是:多個事務同時使用相同的數據時,不會相互干擾,每個事務都有一個完整的數據空間,對其他併發事務是隔離的。也就是說,消費者購買商品這個事務,是不影響其他消費者購買的。
  • Durability:持久化,當事物執行完成後,會持久化下來永久保存。
    瞭解了事物的4大特性,那我們接下來了解一下分佈式事物

分佈式事物

簡介

  • 既然說到分佈式,那肯定是多臺機器的上的。那就是多個機器(進程)事物組成一個分佈式事物。其中的任何一個事物失敗可以做到整個事物的回滾。並且符合事物的4大特性。
  • 但是由於分佈式系統在快速應用,導致其ACID中的C強一致性不能完全做到,就是是技術能力跟不上應用需求,於是大佬們也就提出了base理論。也就是我們所說的最終一致性。
  • 那base理論我們再品一下,BASE理論是Basically Available(基本可用),Soft State(軟狀態)和Eventually Consistent(最終一致性)三個短語的縮寫。其中的軟狀態我個人覺得比較難理解:允許系統中的數據存在中間狀態,並認爲該狀態不影響系統的整體可用性,即允許系統在多個不同節點的數據副本存在數據延時。
    (BASE是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網分佈式系統實踐的總結,是基於CAP定律逐步演化而來。其核心思想是即使無法做到強一致性,但每個應用都可以根據自身業務特點,才用適當的方式來使系統打到最終一致性。來源:掘金

分佈式事物的實現方式

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

  • wiki:對於XA協議的理解:在計算技術上,XA規範是開放羣組關於分佈式事務處理 (DTP)的規範。規範描述了全局的事務管理器與局部的資源管理器之間的接口。XA規範的目的是允許多個資源(如數據庫,應用服務器,消息隊列,等等)在同一事務中訪問,這樣可以使ACID屬性跨越應用程序而保持有效。XA使用兩階段提交來保證所有資源同時提交或回滾任何特定的事務。
  • 如果讓我們自己實現一個分佈式事物:首先我們得達到一處異常全部回滾的目標,那麼各個本地事物記得得知其他事物的運行狀態,那就得搞一個協調着,也就是來當其中任何一個有問題了,就可以及時告知其中的一個,然後可以回滾自己的操作。我們來看看大佬們是如何設計的

執行過程

  • 兩階段提交協議的執行過程,分爲投票(Voting)和提交(Commit)兩個階段。
1.投票

首先,我們看一下第一階段投票:在這一階段,協調者(Coordinator,即事務管理器)會向事務的參與者(Cohort,即本地資源管理器)發起執行操作的 CanCommit 請求,並等待參與者的響應。參與者接收到請求後,會執行請求中的事務操作,將操作信息記錄到事務日誌中但不提交(即不會修改數據庫中的數據),待參與者執行成功,則向協調者發送“Yes”消息,表示同意操作;若不成功,則發送“No”消息,表示終止操作。當所有的參與者都返回了操作結果(Yes 或 No 消息)後,系統進入了第二階段提交階段(也可以稱爲,執行階段)。

在這裏插入圖片描述

2. 提交階段
  • 在提交階段,協調者會根據所有參與者返回的信息向參與者發送 DoCommit(提交)或 DoAbort(取消)指令。
  • 具體規則如下:若協調者從參與者那裏收到的都是“Yes”消息,則向參與者發送“DoCommit”消息。參與者收到“DoCommit”消息後,完成剩餘的操作(比如修改數據庫中的數據)並釋放資源(整個事務過程中佔用的資源),然後向協調者返回“HaveCommitted”消息;
  • 若協調者從參與者收到的消息中包含“No”消息,則向所有參與者發送“DoAbort”消息。此時投票階段發送“Yes”消息的參與者,則會根據之前執行操作時的事務日誌對操作進行回滾,就好像沒有執行過請求操作一樣,然後所有參與者會向協調者發送“HaveCommitted”消息;協調者接收到來自所有參與者的“HaveCommitted”消息後,就意味着整個事務結束了。
    在這裏插入圖片描述
3.缺點
  • 同步阻塞問題:二階段提交算法在執行過程中,所有參與節點都是事務阻塞型的。也就是說,當本地資源管理器佔有臨界資源時,其他資源管理器如果要訪問同一臨界資源,會處於阻塞狀態。因此,基於 XA 的二階段提交協議不支持高併發場景。
  • 單點故障問題:該算法類似於集中式算法,一旦事務管理器發生故障,整個系統都處於停滯狀態。尤其是在提交階段,一旦事務管理器發生故障,資源管理器會由於等待管理器的消息,而一直鎖定事務資源,導致整個系統被阻塞。
  • 數據不一致問題:在提交階段,當協調者向所有參與者發送“DoCommit”請求時,如果發生了局部網絡異常,或者在發送提交請求的過程中協調者發生了故障,就會導致只有一部分參與者接收到了提交請求並執行提交操作,但其他未接到提交請求的那部分參與者則無法執行事務提交。於是整個分佈式系統便出現了數據不一致的問題。

三階段提交

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

  • 三階段提交協議(Three-phase Commit Protocol,3PC),是對二階段提交(2PC)的改進。爲了更好地處理兩階段提交的同步阻塞和數據不一致問題,三階段提交引入了超時機制和準備階段。
  • 與 2PC 只是在協調者引入超時機制不同,3PC 同時在協調者和參與者中引入了超時機制。如果協調者或參與者在規定的時間內沒有接收到來自其他節點的響應,就會根據當前的狀態選擇提交或者終止整個事務,從而減少了整個集羣的阻塞時間,在一定程度上減少或減弱了 2PC 中出現的同步阻塞問題。
  • 在第一階段和第二階段中間引入了一個準備階段,或者說把 2PC 的投票階段一分爲二,也就是在提交階段之前,加入了一個預提交階段。在預提交階段儘可能排除一些不一致的情況,保證在最後提交之前各參與節點的狀態是一致的。三階段提交協議就有 CanCommit、PreCommit、DoCommit 三個階段,下面我們來看一下這個三個階段。
第一階段
  • 第一,CanCommit階段。協調者向參與者發送請求操作(CanCommit 請求),詢問參與者是否可以執行事務提交操作,然後等待參與者的響應;參與者收到 CanCommit 請求之後,回覆 Yes,表示可以順利執行事務;否則回覆 No。
  • 3PC 的 CanCommit 階段與 2PC 的 Voting 階段相比:
    • 類似之處在於:協調者均需要向參與者發送請求操作(CanCommit 請求),詢問參與者是否可以執行事務提交操作,然後等待參與者的響應。參與者收到 CanCommit 請求之後,回覆 Yes,表示可以順利執行事務;否則回覆 No。
    • 不同之處在於,在 2PC 中,在投票階段,若參與者可以執行事務,會將操作信息記錄到事務日誌中但不提交,並返回結果給協調者。但在 3PC 中,在 CanCommit 階段,參與者僅會判斷是否可以順利執行事務,並返回結果。而操作信息記錄到事務日誌但不提交的操作由第二階段預提交階段執行。

CanCommit 階段不同節點之間的事務請求成功和失敗的流程,如下所示。
在這裏插入圖片描述

當協調者接收到所有參與者回覆的消息後,進入預提交階段(PreCommit 階段)。

第二,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 消息之後,執行事務的中斷,並結束事務。
  • 執行階段不同節點上事務執行成功和失敗 (事務中斷) 的流程,如下所示。
    在這裏插入圖片描述

3PC 協議在協調者和參與者均引入了超時機制。即當參與者在預提交階段向協調者發送 Ack 消息後,如果長時間沒有得到協調者的響應,在默認情況下,參與者會自動將超時的事務進行提交,從而減少整個集羣的阻塞時間,在一定程度上減少或減弱了 2PC 中出現的同步阻塞問題。但三階段提交仍然存在數據不一致的情況,比如在 PreCommit 階段,部分參與者已經接受到 ACK 消息進入執行階段,但部分參與者與協調者網絡不通,導致接收不到 ACK 消息,此時接收到 ACK 消息的參與者會執行任務,未接收到 ACK 消息且網絡不通的參與者無法執行任務,最終導致數據不一致。

基於消息的最終一致性方法

2PC 和 3PC 核心思想均是以集中式的方式實現分佈式事務,這兩種方法都存在兩個共同的缺點,一是,同步執行,性能差;二是,數據不一致問題。爲了解決這兩個問題,通過分佈式消息來確保事務最終一致性的方案便出現了。

在 eBay 的分佈式系統架構中,架構師解決一致性問題的核心思想就是:將需要分佈式處理的事務通過消息或者日誌的方式異步執行,消息或日誌可以存到本地文件、數據庫或消息隊列中,再通過業務規則進行失敗重試。這個案例,就是使用基於分佈式消息的最終一致性方案解決了分佈式事務的問題。

基於分佈式消息的最終一致性方案的事務處理,引入了一個消息中間件(在本案例中,我們採用 Message Queue,MQ,消息隊列),用於在多個應用之間進行消息傳遞。實際使用中,阿里就是採用 RocketMQ 機制來支持消息事務。

基於消息中間件協商多個節點分佈式事務執行操作的示意圖,如下所示。
在這裏插入圖片描述

仍然以網上購物爲例。假設用戶 A 在某電商平臺下了一個訂單,需要支付 50 元,發現自己的賬戶餘額共 150 元,就使用餘額支付,支付成功之後,訂單狀態修改爲支付成功,然後通知倉庫發貨。在該事件中,涉及到了訂單系統、支付系統、倉庫系統,這三個系統是相互獨立的應用,通過遠程服務進行調用。
在這裏插入圖片描述

根據基於分佈式消息的最終一致性方案,用戶 A 通過終端手機首先在訂單系統上操作,通過消息隊列完成整個購物流程。然後整個購物的流程如下所示。

在這裏插入圖片描述

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

在上述過程中,可能會產生如下異常情況,其對應的解決方案爲:

  1. 訂單消息未成功存儲到 MQ 中,則訂單系統不執行任何操作,數據保持一致;
  2. MQ 成功將消息發送給支付系統(或倉庫系統),但是支付系統(或倉庫系統)操作成功的 ACK 消息回傳失敗(由於通信方面的原因),導致訂單系統與支付系統(或倉庫系統)數據不一致,此時 MQ 會確認各系統的操作結果,刪除相關消息,支付系統(或倉庫系統)操作回滾,使得各系統數據保持一致;
  3. MQ 成功將消息發送給支付系統(或倉庫系統),但是支付系統(或倉庫系統)操作成功的 ACK 消息回傳成功,訂單系統操作後的最終結果(成功或失敗)未能成功發送給 MQ,此時各系統數據可能不一致,MQ 也需確認各系統的操作結果,若數據一致,則更新消息;若不一致,則回滾操作、刪除消息。

基於分佈式消息的最終一致性方案採用消息傳遞機制,並使用異步通信的方式,避免了通信阻塞,從而增加系統的吞吐量。同時,這種方案還可以屏蔽不同系統的協議規範,使其可以直接交互。

在不需要請求立即返回結果的場景下, 這些特性就帶來了明顯的通信優勢,並且通過引入消息中間件,實現了消息生成方(如上述的訂單系統)本地事務和消息發送的原子性,採用最終一致性的方式,只需保證數據最終一致即可,一定程度上解決了二階段和三階段方法要保證強一致性而在某些情況導致的數據不一致問題。

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

三種實現方式對比現在,爲了方便你理解並記憶這三種方法,我總結了一張表格,從算法一致性、執行方式、性能等角度進行了對比:

在這裏插入圖片描述

總結

  • 從事務的 ACID 特性出發,介紹了分佈式事務的概念、特徵,以及如何實現分佈式事務。在關於如何實現分佈式的部分,我以網購爲例,與你介紹了常見的三種實現方式,即基於 XA 協議的二階段提交方法,三階段方法以及基於分佈式消息的最終一致性方法。

  • 二階段和三階段方法是維護強一致性的算法,它們針對剛性事務,實現的是事務的 ACID 特性。而基於分佈式消息的最終一致性方案更適用於大規模分佈式系統,它維護的是事務的最終一致性,遵循的是 BASE 理論,因此適用於柔性事務。

  • 在分佈式系統的設計與實現中,分佈式事務是不可或缺的一部分。可以說,沒有實現分佈式事務的分佈式系統,不是一個完整的分佈式系統。分佈式事務的實現過程看似複雜,但將方法分解剖析後,你就會發現分佈式事務的實現是有章可循的。
    在這裏插入圖片描述
    聲明:本文大部分摘自:https://time.geekbang.org/column/intro/233

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