一文解讀分佈式事務 (轉)

這篇文章將介紹什麼是分佈式事務,分佈式事務解決什麼問題,對分佈式事務實現的難點,解決思路,不一樣場景下方案的選擇,經過圖解的方式進行梳理、總結和比較。node

相信耐心看完這篇文章,談到分佈式事務,再也不只是有「2PC」、「3PC」、「MQ的消息事務」、「最終一致性」、「TCC」等這些知識碎片,而是可以將知識連成一片,造成知識體系。算法

什麼是事務數據庫

介紹分佈式事務以前,先介紹什麼是事務。編程

事務的具體定義

事務提供一種機制將一個活動涉及的全部操做歸入到一個不可分割的執行單元,組成事務的全部操做只有在全部操做均能正常執行的狀況下方能提交,只要其中任一操做執行失敗,都將致使整個事務的回滾。緩存

簡單地說,事務提供一種「 要麼什麼都不作,要麼作全套(All or Nothing)」機制。服務器

數據庫事務的 ACID 屬性

事務是基於數據進行操做,須要保證事務的數據一般存儲在數據庫中,因此介紹到事務,就不得不介紹數據庫事務的 ACID 特性。網絡

ACID 指數據庫事務正確執行的四個基本特性的縮寫,包含:架構

原子性(Atomicity)

整個事務中的全部操做,要麼所有完成,要麼所有不完成,不可能停滯在中間某個環節。併發

事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。分佈式

例如:銀行轉帳,從 A 帳戶轉 100 元至 B 帳戶,分爲兩個步驟:

  • 從 A 帳戶取 100 元。
  • 存入 100 元至 B 帳戶。

這兩步要麼一塊兒完成,要麼一塊兒不完成,若是隻完成第一步,第二步失敗,錢會莫名其妙少了 100 元。

一致性(Consistency)

在事務開始以前和事務結束之後,數據庫數據的一致性約束沒有被破壞。

例如:現有完整性約束 A+B=100,若是一個事務改變了 A,那麼必須得改變 B,使得事務結束後依然知足 A+B=100,不然事務失敗。

隔離性(Isolation)

數據庫容許多個併發事務同時對數據進行讀寫和修改的能力,若是一個事務要訪問的數據正在被另一個事務修改,只要另一個事務未提交,它所訪問的數據就不受未提交事務的影響。

隔離性能夠防止多個事務併發執行時因爲交叉執行而致使數據的不一致。

例如:現有有個交易是從 A 帳戶轉 100 元至 B 帳戶,在這個交易事務還未完成的狀況下,若是此時 B 查詢本身的帳戶,是看不到新增長的 100 元的。

持久性(Durability)

事務處理結束後,對數據的修改就是永久的,即使系統故障也不會丟失。

簡單而言,ACID 是從不一樣維度描述事務的特性:

  • 原子性:事務操做的總體性。
  • 一致性:事務操做下數據的正確性。
  • 隔離性:事務併發操做下數據的正確性。
  • 持久性:事務對數據修改的可靠性。

一個支持事務(Transaction)的數據庫,須要具備這 4 種特性,不然在事務過程中沒法保證數據的正確性,處理結果很可能達不到請求方的要求。

何時使用數據庫事務

在介紹完事務基本概念以後,何時該使用數據庫事務?

簡單而言,就是業務上有一組數據操做,須要若是其中有任何一個操做執行失敗,整組操做所有不執行並恢復到未執行狀態,要麼所有成功,要麼所有失敗。

在使用數據庫事務時須要注意,儘量短的保持事務,修改多個不一樣表的數據的冗長事務會嚴重妨礙系統中的全部其餘用戶,這頗有可能致使一些性能問題。

什麼是分佈式事務

介紹完事務相關基本概念以後,下面介紹分佈式事務。

分佈式產生背景與概念

隨着互聯網快速發展,微服務,SOA 等服務架構模式正在被大規模的使用,如今分佈式系統通常由多個獨立的子系統組成,多個子系統經過網絡通訊互相協做配合完成各個功能。

有不少用例會跨多個子系統才能完成,比較典型的是電子商務網站的下單支付流程,至少會涉及交易系統和支付系統。

並且這個過程當中會涉及到事務的概念,即保證交易系統和支付系統的數據一致性,此處咱們稱這種跨系統的事務爲分佈式事務。

具體一點而言,分佈式事務是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不一樣的分佈式系統的不一樣節點之上。

舉個互聯網經常使用的交易業務爲例:

上圖中包含了庫存和訂單兩個獨立的微服務,每一個微服務維護了本身的數據庫。

在交易系統的業務邏輯中,一個商品在下單以前須要先調用庫存服務,進行扣除庫存,再調用訂單服務,建立訂單記錄。

能夠看到,若是多個數據庫之間的數據更新沒有保證事務,將會致使出現子系統數據不一致,業務出現問題。

分佈式事務的難點

事務的原子性

事務操做跨不一樣節點,當多個節點某一節點操做失敗時,須要保證多節點操做的要麼什麼都不作,要麼作全套(All or Nothing)的原子性。

事務的一致性

當發生網絡傳輸故障或者節點故障,節點間數據複製通道中斷,在進行事務操做時須要保證數據一致性,保證事務的任何操做都不會使得數據違反數據庫定義的約束、觸發器等規則。

事務的隔離性

事務隔離性的本質就是如何正確處理多個併發事務的讀寫衝突和寫寫衝突,由於在分佈式事務控制中,可能會出現提交不一樣步的現象,這個時候就有可能出現「部分已經提交」的事務。

此時併發應用訪問數據若是沒有加以控制,有可能出現「髒讀」問題。

分佈式系統的一致性

前面介紹到的分佈式事務的難點涉及的問題,最終影響是致使數據出現不一致,下面對分佈式系統的一致性問題進行理論分析,後面將基於這些理論進行分佈式方案的介紹。

可用性和一致性的衝突:CAP 理論

CAP 定理又被稱做布魯爾定理,是加州大學的計算機科學家布魯爾在 2000 年提出的一個猜測。

2002 年,麻省理工學院的賽斯·吉爾伯特和南希·林奇發表了布魯爾猜測的證實,使之成爲分佈式計算領域公認的一個定理。

布魯爾在提出 CAP 猜測時並無具體定義 Consistency、Availability、Partition Tolerance 這 3 個詞的含義,不一樣資料的具體定義也有差異。

爲了更好地解釋,下面選擇Robert Greiner的文章《CAP Theorem》做爲參考基礎:

  • http://robertgreiner.com/about/
  • http://robertgreiner.com/2014/08/cap-theorem-revisited/

CAP 理論的定義

在一個分佈式系統(指互相鏈接並共享數據的節點的集合)中,當涉及讀寫操做時,只能保證一致性(Consistence)、可用性(Availability)、分區容錯性(Partition Tolerance)三者中的兩個,另一個必須被犧牲。

Consistency、Availability、Partition Tolerance 具體解釋以下:

C - Consistency 一致性:A read is guaranteed to return the most recent write for a given client.

對某個指定的客戶端來講,讀操做保證可以返回最新的寫操做結果。

這裏並非強調同一時刻擁有相同的數據,對於系統執行事務來講,在事務執行過程當中,系統其實處於一個不一致的狀態,不一樣的節點的數據並不徹底一致。

一致性強調客戶端讀操做可以獲取最新的寫操做結果,是由於事務在執行過程當中,客戶端是沒法讀取到未提交的數據的。

只有等到事務提交後,客戶端才能讀取到事務寫入的數據,而若是事務失敗則會進行回滾,客戶端也不會讀取到事務中間寫入的數據。

A - Availability 可用性:A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout).

非故障的節點在合理的時間內返回合理的響應(不是錯誤和超時的響應)。

這裏強調的是合理的響應,不能超時,不能出錯。注意並無說「正確」的結果,例如,應該返回 100 但實際上返回了 90,確定是不正確的結果,但能夠是一個合理的結果。

P - Partition Tolerance 分區容忍性:The system will continue to function when network partitions occur.

當出現網絡分區後,系統可以繼續「履行職責」。

這裏網絡分區是指:一個分佈式系統裏面,節點組成的網絡原本應該是連通的。

然而可能由於一些故障(節點間網絡鏈接斷開、節點宕機),使得有些節點之間不連通了,整個網絡就分紅了幾塊區域,數據就散佈在了這些不連通的區域中。

一致性、可用性、分區容忍性的選擇

雖然 CAP 理論定義是三個要素中只能取兩個,但放到分佈式環境下來思考,咱們會發現必須選擇 P(分區容忍)要素,由於網絡自己沒法作到 100% 可靠,有可能出故障,因此分區是一個必然的現象。

若是咱們選擇了 CA(一致性 + 可用性) 而放棄了 P(分區容忍性),那麼當發生分區現象時,爲了保證 C(一致性),系統須要禁止寫入。

當有寫入請求時,系統返回 error(例如,當前系統不容許寫入),這又和 A(可用性) 衝突了,由於 A(可用性)要求返回 no error 和 no timeout。

所以,分佈式系統理論上不可能選擇 CA (一致性 + 可用性)架構,只能選擇 CP(一致性 + 分區容忍性) 或者 AP (可用性 + 分區容忍性)架構,在一致性和可用性作折中選擇。

①CP - Consistency + Partition Tolerance (一致性 + 分區容忍性)

如上圖所示,由於 Node1 節點和 Node2 節點鏈接中斷致使分區現象,Node1 節點的數據已經更新到 y,可是 Node1 和 Node2 之間的複製通道中斷,數據 y 沒法同步到 Node2,Node2 節點上的數據仍是舊數據 x。

這時客戶端 C 訪問 Node2 時,Node2 須要返回 error,提示客戶端 「系統如今發生了錯誤」,這種處理方式違背了可用性(Availability)的要求,所以 CAP 三者只能知足 CP。

②AP - Availability + Partition Tolerance (可用性 + 分區容忍性)

一樣是 Node2 節點上的數據仍是舊數據 x,這時客戶端 C 訪問 Node2 時,Node2 將當前本身擁有的數據 x 返回給客戶端了。

而實際上當前最新的數據已是 y 了,這就不知足一致性(Consistency)的要求了,所以 CAP 三者只能知足 AP。

注意:這裏 Node2 節點返回 x,雖然不是一個「正確」的結果,可是一個「合理」的結果,由於 x 是舊的數據,並非一個錯亂的值,只是否是最新的數據。

值得補充的是,CAP 理論告訴咱們分佈式系統只能選擇 AP 或者 CP,但實際上並非說整個系統只能選擇 AP 或者 CP。

在 CAP 理論落地實踐時,咱們須要將系統內的數據按照不一樣的應用場景和要求進行分類,每類數據選擇不一樣的策略(CP 仍是 AP),而不是直接限定整個系統全部數據都是同一策略。

另外,只能選擇 CP 或者 AP 是指系統發生分區現象時沒法同時保證 C(一致性)和 A(可用性),但不是意味着什麼都不作,當分區故障解決後,系統仍是要保持保證 CA。

CAP 理論的延伸:BASE 理論

BASE 是指基本可用(Basically Available)、軟狀態( Soft State)、最終一致性( Eventual Consistency)。

它的核心思想是即便沒法作到強一致性(CAP 的一致性就是強一致性),但應用能夠採用適合的方式達到最終一致性。

BA - Basically Available 基本可用

分佈式系統在出現故障時,容許損失部分可用性,即保證核心可用。

這裏的關鍵詞是「部分」和「核心」,實際實踐上,哪些是核心須要根據具體業務來權衡。

例如登陸功能相對註冊功能更加核心,註冊不了最多影響流失一部分用戶,若是用戶已經註冊但沒法登陸,那就意味着用戶沒法使用系統,形成的影響範圍更大。

S - Soft State 軟狀態

容許系統存在中間狀態,而該中間狀態不會影響系統總體可用性。這裏的中間狀態就是 CAP 理論中的數據不一致。

E - Eventual Consistency 最終一致性

系統中的全部數據副本通過必定時間後,最終可以達到一致的狀態。

這裏的關鍵詞是「必定時間」 和 「最終」,「必定時間」和數據的特性是強關聯的,不一樣業務不一樣數據可以容忍的不一致時間是不一樣的。

例如支付類業務是要求秒級別內達到一致,由於用戶時時關注;用戶發的最新微博,能夠容忍 30 分鐘內達到一致的狀態,由於用戶短期看不到明星發的微博是無感知的。

而「最終」的含義就是無論多長時間,最終仍是要達到一致性的狀態。

BASE 理論本質上是對 CAP 的延伸和補充,更具體地說,是對 CAP 中 AP 方案的一個補充:CAP 理論是忽略延時的,而實際應用中延時是沒法避免的。

這一點就意味着完美的 CP 場景是不存在的,即便是幾毫秒的數據複製延遲,在這幾毫秒時間間隔內,系統是不符合 CP 要求的。

所以 CAP 中的 CP 方案,實際上也是實現了最終一致性,只是「必定時間」是指幾毫秒而已。

AP 方案中犧牲一致性只是指發生分區故障期間,而不是永遠放棄一致性。

這一點其實就是 BASE 理論延伸的地方,分區期間犧牲一致性,但分區故障恢復後,系統應該達到最終一致性。

數據一致性模型

前面介紹的 BASE 模型提過「強一致性」和「最終一致性」,下面對這些一致性模型展開介紹。

分佈式系統經過複製數據來提升系統的可靠性和容錯性,而且將數據的不一樣的副本存放在不一樣的機器上,因爲維護數據副本的一致性代價很高,所以許多系統採用弱一致性來提升性能。

下面介紹常見的一致性模型:

  • 強一致性:要求不管更新操做是在哪一個數據副本上執行,以後全部的讀操做都要能得到最新的數據。 對於單副本數據來講,讀寫操做是在同一數據上執行的,容易保證強一致性。對多副本數據來講,則須要使用分佈式事務協議。
  • 弱一致性:在這種一致性下,用戶讀到某一操做對系統特定數據的更新須要一段時間,咱們將這段時間稱爲"不一致性窗口"。
  • 最終一致性:是弱一致性的一種特例,在這種一致性下系統保證用戶最終可以讀取到某操做對系統特定數據的更新(讀取操做以前沒有該數據的其餘更新操做)。 "不一致性窗口"的大小依賴於交互延遲、系統的負載,以及數據的副本數等。

系統選擇哪一種一致性模型取決於應用對一致性的需求,所選取的一致性模型還會影響到系統如何處理用戶的請求以及對副本維護技術的選擇等。

後面將基於上面介紹的一致性模型分別介紹分佈式事務的解決方案。

柔性事務

柔性事務的概念

在電商等互聯網場景下,傳統的事務在數據庫性能和處理能力上都暴露出了瓶頸。在分佈式領域基於 CAP 理論以及 BASE 理論,有人就提出了柔性事務的概念。

基於 BASE 理論的設計思想,柔性事務下,在不影響系統總體可用性的狀況下(Basically Available 基本可用),容許系統存在數據不一致的中間狀態(Soft State 軟狀態),在通過數據同步的延時以後,最終數據可以達到一致。

並非徹底放棄了 ACID,而是經過放寬一致性要求,藉助本地事務來實現最終分佈式事務一致性的同時也保證系統的吞吐。

實現柔性事務的一些特性

下面介紹的是實現柔性事務的一些常見特性,這些特性在具體的方案中不必定都要知足,由於不一樣的方案要求不同。

可見性(對外可查詢) :在分佈式事務執行過程當中,若是某一個步驟執行出錯,就須要明確的知道其餘幾個操做的處理狀況,這就須要其餘的服務都可以提供查詢接口,保證能夠經過查詢來判斷操做的處理狀況。

爲了保證操做的可查詢,須要對於每個服務的每一次調用都有一個全局惟一的標識,能夠是業務單據號(如訂單號)、也能夠是系統分配的操做流水號(如支付記錄流水號)。除此以外,操做的時間信息也要有完整的記錄。

操做冪等性:冪等性,實際上是一個數學概念。冪等函數,或冪等方法,是指可使用相同參數重複執行,並能得到相同結果的函數。

冪等操做的特色是其任意屢次執行所產生的影響均與一次執行的影響相同。也就是說,同一個方法,使用一樣的參數,調用屢次產生的業務結果與調用一次產生的業務結果相同。

之因此須要操做冪等性,是由於爲了保證數據的最終一致性,不少事務協議都會有不少重試的操做,若是一個方法不保證冪等,那麼將沒法被重試。

冪等操做的實現方式有多種,如在系統中緩存全部的請求與處理結果、檢測到重複操做後,直接返回上一次的處理結果等。

常見分佈式事務解決方案

介紹完分佈式系統的一致性相關理論,下面基於不一樣的一致性模型介紹分佈式事務的常看法決方案,後面會再介紹各個方案的使用場景。

分佈式事務的實現有許多種,其中較經典是由 Tuxedo 提出的 XA 分佈式事務協議,XA 協議包含二階段提交(2PC)和三階段提交(3PC)兩種實現。

2PC(二階段提交)方案:強一致性

方案簡介

二階段提交協議(Two-phase Commit,即 2PC)是經常使用的分佈式事務解決方案,即將事務的提交過程分爲兩個階段來進行處理:準備階段和提交階段。事務的發起者稱協調者,事務的執行者稱參與者。

在分佈式系統裏,每一個節點均可以知曉本身操做的成功或者失敗,卻沒法知道其餘節點操做的成功或失敗。

當一個事務跨多個節點時,爲了保持事務的原子性與一致性,而引入一個協調者來統一掌控全部參與者的操做結果,並指示它們是否要把操做結果進行真正的提交或者回滾(rollback)。

二階段提交的算法思路能夠歸納爲:參與者將操做成敗通知協調者,再由協調者根據全部參與者的反饋情報決定各參與者是否要提交操做仍是停止操做。

核心思想就是對每個事務都採用先嚐試後提交的處理方式,處理後全部的讀操做都要能得到最新的數據,所以也能夠將二階段提交看做是一個強一致性算法。

處理流程

簡單一點理解,能夠把協調者節點比喻爲帶頭大哥,參與者理解比喻爲跟班小弟,帶頭大哥統一協調跟班小弟的任務執行。

階段 1:準備階段

準備階段有以下三個步驟:

  • 協調者向全部參與者發送事務內容,詢問是否能夠提交事務,並等待全部參與者答覆。
  • 各參與者執行事務操做,將 undo 和 redo 信息記入事務日誌中(但不提交事務)。
  • 如參與者執行成功,給協調者反饋 yes,便可以提交;如執行失敗,給協調者反饋 no,即不可提交。

階段 2:提交階段

若是協調者收到了參與者的失敗消息或者超時,直接給每一個參與者發送回滾(rollback)消息;不然,發送提交(commit)消息。

參與者根據協調者的指令執行提交或者回滾操做,釋放全部事務處理過程當中使用的鎖資源。(注意:必須在最後階段釋放鎖資源) 接下來分兩種狀況分別討論提交階段的過程。

狀況 1,當全部參與者均反饋 yes,提交事務,如上圖:

  • 協調者向全部參與者發出正式提交事務的請求(即 commit 請求)。
  • 參與者執行 commit 請求,並釋放整個事務期間佔用的資源。
  • 各參與者向協調者反饋 ack(應答)完成的消息。
  • 協調者收到全部參與者反饋的 ack 消息後,即完成事務提交。

狀況 2,當任何階段 1 一個參與者反饋 no,中斷事務,如上圖:

  • 協調者向全部參與者發出回滾請求(即 rollback 請求)。
  • 參與者使用階段 1 中的 undo 信息執行回滾操做,並釋放整個事務期間佔用的資源。
  • 各參與者向協調者反饋 ack 完成的消息。
  • 協調者收到全部參與者反饋的 ack 消息後,即完成事務中斷。

方案總結

2PC 方案實現起來簡單,實際項目中使用比較少,主要由於如下問題:

  • 性能問題:全部參與者在事務提交階段處於同步阻塞狀態,佔用系統資源,容易致使性能瓶頸。
  • 可靠性問題:若是協調者存在單點故障問題,若是協調者出現故障,參與者將一直處於鎖定狀態。
  • 數據一致性問題:在階段 2 中,若是發生局部網絡問題,一部分事務參與者收到了提交消息,另外一部分事務參與者沒收到提交消息,那麼就致使了節點之間數據的不一致。

3PC(三階段提交)方案

方案簡介

三階段提交協議,是二階段提交協議的改進版本,與二階段提交不一樣的是,引入超時機制。同時在協調者和參與者中都引入超時機制。

三階段提交將二階段的準備階段拆分爲 2 個階段,插入了一個 preCommit 階段,使得原先在二階段提交中,參與者在準備以後,因爲協調者發生崩潰或錯誤,而致使參與者處於沒法知曉是否提交或者停止的「不肯定狀態」所產生的可能至關長的延時的問題得以解決。

處理流程

階段 1:canCommit

協調者向參與者發送 commit 請求,參與者若是能夠提交就返回 yes 響應(參與者不執行事務操做),不然返回 no 響應:

  • 協調者向全部參與者發出包含事務內容的 canCommit 請求,詢問是否能夠提交事務,並等待全部參與者答覆。
  • 參與者收到 canCommit 請求後,若是認爲能夠執行事務操做,則反饋 yes 並進入預備狀態,不然反饋 no。

階段 2:preCommit

協調者根據階段 1 canCommit 參與者的反應狀況來決定是否能夠進行基於事務的 preCommit 操做。根據響應狀況,有如下兩種可能。

狀況 1:階段 1 全部參與者均反饋 yes,參與者預執行事務,如上圖:

  • 協調者向全部參與者發出 preCommit 請求,進入準備階段。
  • 參與者收到 preCommit 請求後,執行事務操做,將 undo 和 redo 信息記入事務日誌中(但不提交事務)。
  • 各參與者向協調者反饋 ack 響應或 no 響應,並等待最終指令。

狀況 2:階段 1 任何一個參與者反饋 no,或者等待超時後協調者尚沒法收到全部參與者的反饋,即中斷事務,如上圖:

  • 協調者向全部參與者發出 abort 請求。
  • 不管收到協調者發出的 abort 請求,或者在等待協調者請求過程當中出現超時,參與者均會中斷事務。

階段 3:do Commit

該階段進行真正的事務提交,也能夠分爲如下兩種狀況。

狀況 1:階段 2 全部參與者均反饋 ack 響應,執行真正的事務提交,如上圖:

  • 若是協調者處於工做狀態,則向全部參與者發出 do Commit 請求。
  • 參與者收到 do Commit 請求後,會正式執行事務提交,並釋放整個事務期間佔用的資源。
  • 各參與者向協調者反饋 ack 完成的消息。
  • 協調者收到全部參與者反饋的 ack 消息後,即完成事務提交。

狀況 2:階段 2 任何一個參與者反饋 no,或者等待超時後協調者尚沒法收到全部參與者的反饋,即中斷事務,如上圖:

  • 若是協調者處於工做狀態,向全部參與者發出 abort 請求。
  • 參與者使用階段 1 中的 undo 信息執行回滾操做,並釋放整個事務期間佔用的資源。
  • 各參與者向協調者反饋 ack 完成的消息。
  • 協調者收到全部參與者反饋的 ack 消息後,即完成事務中斷。

注意:進入階段 3 後,不管協調者出現問題,或者協調者與參與者網絡出現問題,都會致使參與者沒法接收到協調者發出的 do Commit 請求或 abort 請求。此時,參與者都會在等待超時以後,繼續執行事務提交。

方案總結

優勢:相比二階段提交,三階段提交下降了阻塞範圍,在等待超時後協調者或參與者會中斷事務。避免了協調者單點問題,階段 3 中協調者出現問題時,參與者會繼續提交事務。

缺點:數據不一致問題依然存在,當在參與者收到 preCommit 請求後等待 do commite 指令時,此時若是協調者請求中斷事務,而協調者沒法與參與者正常通訊,會致使參與者繼續提交事務,形成數據不一致。

TCC 事務:最終一致性

方案簡介

TCC(Try-Confirm-Cancel)的概念,最先是由 Pat Helland 於 2007 年發表的一篇名爲《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

TCC 是服務化的二階段編程模型,其 Try、Confirm、Cancel 3 個方法均由業務編碼實現:

  • Try 操做做爲一階段,負責資源的檢查和預留。
  • Confirm 操做做爲二階段提交操做,執行真正的業務。
  • Cancel 是預留資源的取消。

TCC 事務的 Try、Confirm、Cancel 能夠理解爲 SQL 事務中的 Lock、Commit、Rollback。

處理流程

爲了方便理解,下面以電商下單爲例進行方案解析,這裏把整個過程簡單分爲扣減庫存,訂單建立 2 個步驟,庫存服務和訂單服務分別在不一樣的服務器節點上。

①Try 階段

從執行階段來看,與傳統事務機制中業務邏輯相同。但從業務角度來看,卻不同。

TCC 機制中的 Try 僅是一個初步操做,它和後續的確認一塊兒才能真正構成一個完整的業務邏輯,這個階段主要完成:

  • 完成全部業務檢查( 一致性 ) 。
  • 預留必須業務資源( 準隔離性 ) 。
  • Try 嘗試執行業務。

TCC 事務機制以初步操做(Try)爲中心的,確認操做(Confirm)和取消操做(Cancel)都是圍繞初步操做(Try)而展開。

所以,Try 階段中的操做,其保障性是最好的,即便失敗,仍然有取消操做(Cancel)能夠將其執行結果撤銷。

假設商品庫存爲 100,購買數量爲 2,這裏檢查和更新庫存的同時,凍結用戶購買數量的庫存,同時建立訂單,訂單狀態爲待確認。

②Confirm / Cancel 階段

根據 Try 階段服務是否所有正常執行,繼續執行確認操做(Confirm)或取消操做(Cancel)。

Confirm 和 Cancel 操做知足冪等性,若是 Confirm 或 Cancel 操做執行失敗,將會不斷重試直到執行完成。

Confirm:當 Try 階段服務所有正常執行, 執行確認業務邏輯操做

這裏使用的資源必定是 Try 階段預留的業務資源。在 TCC 事務機制中認爲,若是在 Try 階段能正常的預留資源,那 Confirm 必定能完整正確的提交。

Confirm 階段也能夠當作是對 Try 階段的一個補充,Try+Confirm 一塊兒組成了一個完整的業務邏輯。

Cancel:當 Try 階段存在服務執行失敗, 進入 Cancel 階段

Cancel 取消執行,釋放 Try 階段預留的業務資源,上面的例子中,Cancel 操做會把凍結的庫存釋放,並更新訂單狀態爲取消。

方案總結

TCC 事務機制相對於傳統事務機制(X/Open XA),TCC 事務機制相比於上面介紹的 XA 事務機制,有如下優勢:

  • 性能提高:具體業務來實現控制資源鎖的粒度變小,不會鎖定整個資源。
  • 數據最終一致性:基於 Confirm 和 Cancel 的冪等性,保證事務最終完成確認或者取消,保證數據的一致性。
  • 可靠性:解決了 XA 協議的協調者單點故障問題,由主業務方發起並控制整個業務活動,業務活動管理器也變成多點,引入集羣。

缺點: TCC 的 Try、Confirm 和 Cancel 操做功能要按具體業務來實現,業務耦合度較高,提升了開發成本。

本地消息表:最終一致性

方案簡介

本地消息表的方案最初是由 eBay 提出,核心思路是將分佈式事務拆分紅本地事務進行處理。

方案經過在事務主動發起方額外新建事務消息表,事務發起方處理業務和記錄事務消息在本地事務中完成,輪詢事務消息表的數據發送事務消息,事務被動方基於消息中間件消費事務消息表中的事務。

這樣設計能夠避免」業務處理成功 + 事務消息發送失敗",或"業務處理失敗 + 事務消息發送成功"的棘手狀況出現,保證 2 個系統事務的數據一致性。

處理流程

下面把分佈式事務最早開始處理的事務方稱爲事務主動方,在事務主動方以後處理的業務內的其餘事務稱爲事務被動方。

爲了方便理解,下面繼續以電商下單爲例進行方案解析,這裏把整個過程簡單分爲扣減庫存,訂單建立 2 個步驟。

庫存服務和訂單服務分別在不一樣的服務器節點上,其中庫存服務是事務主動方,訂單服務是事務被動方。

事務的主動方須要額外新建事務消息表,用於記錄分佈式事務的消息的發生、處理狀態。

整個業務處理流程以下:

步驟1:事務主動方處理本地事務。

事務主動方在本地事務中處理業務更新操做和寫消息表操做。上面例子中庫存服務階段在本地事務中完成扣減庫存和寫消息表(圖中 一、2)。

步驟 2:事務主動方經過消息中間件,通知事務被動方處理事務通知事務待消息。

消息中間件能夠基於 Kafka、RocketMQ 消息隊列,事務主動方主動寫消息到消息隊列,事務消費方消費並處理消息隊列中的消息。

上面例子中,庫存服務把事務待處理消息寫到消息中間件,訂單服務消費消息中間件的消息,完成新增訂單(圖中 3 - 5)。

步驟 3:事務被動方經過消息中間件,通知事務主動方事務已處理的消息。

上面例子中,訂單服務把事務已處理消息寫到消息中間件,庫存服務消費中間件的消息,並將事務消息的狀態更新爲已完成(圖中 6 - 8)。

爲了數據的一致性,當處理錯誤須要重試,事務發送方和事務接收方相關業務處理須要支持冪等。

具體保存一致性的容錯處理以下:

  • 當步驟 1 處理出錯,事務回滾,至關於什麼都沒發生。
  • 當步驟 二、步驟 3 處理出錯,因爲未處理的事務消息仍是保存在事務發送方,事務發送方能夠定時輪詢爲超時消息數據,再次發送到消息中間件進行處理。事務被動方消費事務消息重試處理。
  • 若是是業務上的失敗,事務被動方能夠發消息給事務主動方進行回滾。
  • 若是多個事務被動方已經消費消息,事務主動方須要回滾事務時須要通知事務被動方回滾。

方案總結

方案的優勢以下:

  • 從應用設計開發的角度實現了消息數據的可靠性,消息數據的可靠性不依賴於消息中間件,弱化了對 MQ 中間件特性的依賴。
  • 方案輕量,容易實現。

缺點以下:

  • 與具體的業務場景綁定,耦合性強,不可公用。
  • 消息數據與業務數據同庫,佔用業務系統資源。
  • 業務系統在使用關係型數據庫的狀況下,消息服務性能會受到關係型數據庫併發性能的侷限。

MQ 事務:最終一致性

方案簡介

基於 MQ 的分佈式事務方案實際上是對本地消息表的封裝,將本地消息表基於 MQ 內部,其餘方面的協議基本與本地消息表一致。

處理流程

下面主要基於 RocketMQ 4.3 以後的版本介紹 MQ 的分佈式事務方案。

在本地消息表方案中,保證事務主動方發寫業務表數據和寫消息表數據的一致性是基於數據庫事務,RocketMQ 的事務消息相對於普通 MQ,相對於提供了 2PC 的提交接口,方案以下:

正常狀況:事務主動方發消息

這種狀況下,事務主動方服務正常,沒有發生故障,發消息流程以下:

  • 圖中 1:發送方向 MQ 服務端(MQ Server)發送 half 消息。
  • 圖中 2:MQ Server 將消息持久化成功以後,向發送方 ack 確認消息已經發送成功。
  • 圖中 3:發送方開始執行本地事務邏輯。
  • 圖中 4:發送方根據本地事務執行結果向 MQ Server 提交二次確認(commit 或是 rollback)。
  • 圖中 5:MQ Server 收到 commit 狀態則將半消息標記爲可投遞,訂閱方最終將收到該消息;MQ Server 收到 rollback 狀態則刪除半消息,訂閱方將不會接受該消息。

異常狀況:事務主動方消息恢復

在斷網或者應用重啓等異常狀況下,圖中 4 提交的二次確認超時未到達 MQ Server,此時處理邏輯以下:

  • 圖中 5:MQ Server 對該消息發起消息回查。
  • 圖中 6:發送方收到消息回查後,須要檢查對應消息的本地事務執行的最終結果。
  • 圖中 7:發送方根據檢查獲得的本地事務的最終狀態再次提交二次確認。
  • 圖中 8:MQ Server基於 commit/rollback 對消息進行投遞或者刪除。

介紹完 RocketMQ 的事務消息方案後,因爲前面已經介紹過本地消息表方案,這裏就簡單介紹 RocketMQ 分佈式事務:

事務主動方基於 MQ 通訊通知事務被動方處理事務,事務被動方基於 MQ 返回處理結果。

若是事務被動方消費消息異常,須要不斷重試,業務處理邏輯須要保證冪等。

若是是事務被動方業務上的處理失敗,能夠經過 MQ 通知事務主動方進行補償或者事務回滾。

方案總結

相比本地消息表方案,MQ 事務方案優勢是:

  • 消息數據獨立存儲 ,下降業務系統與消息系統之間的耦合。
  • 吞吐量因爲使用本地消息表方案。

缺點是:

  • 一次消息發送須要兩次網絡請求(half 消息 + commit/rollback 消息) 。
  • 業務處理服務須要實現消息狀態回查接口。

Saga 事務:最終一致性

方案簡介

Saga 事務源於 1987 年普林斯頓大學的 Hecto 和 Kenneth 發表的如何處理 long lived transaction(長活事務)論文。

Saga 事務核心思想是將長事務拆分爲多個本地短事務,由 Saga 事務協調器協調,若是正常結束那就正常完成,若是某個步驟失敗,則根據相反順序一次調用補償操做。

處理流程

Saga 事務基本協議以下:

  • 每一個 Saga 事務由一系列冪等的有序子事務(sub-transaction) Ti 組成。
  • 每一個 Ti 都有對應的冪等補償動做 Ci,補償動做用於撤銷 Ti 形成的結果。

能夠看到,和 TCC 相比,Saga 沒有「預留」動做,它的 Ti 就是直接提交到庫。

下面如下單流程爲例,整個操做包括:建立訂單、扣減庫存、支付、增長積分。

Saga 的執行順序有兩種,如上圖:

  • 事務正常執行完成:T1, T2, T3, ..., Tn,例如:扣減庫存(T1),建立訂單(T2),支付(T3),依次有序完成整個事務。
  • 事務回滾:T1, T2, ..., Tj, Cj,..., C2, C1,其中 0 < j < n,例如:扣減庫存(T1),建立訂單(T2),支付(T3,支付失敗),支付回滾(C3),訂單回滾(C2),恢復庫存(C1)。

Saga 定義了兩種恢復策略:

向前恢復(forward recovery):對應於上面第一種執行順序,適用於必需要成功的場景,發生失敗進行重試,執行順序是相似於這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn,其中j是發生錯誤的子事務(sub-transaction)。該狀況下不須要Ci。

向後恢復(backward recovery):對應於上面提到的第二種執行順序,其中 j 是發生錯誤的子事務(sub-transaction),這種作法的效果是撤銷掉以前全部成功的子事務,使得整個 Saga 的執行結果撤銷。

Saga 事務常見的有兩種不一樣的實現方式:

①命令協調(Order Orchestrator):中央協調器負責集中處理事件的決策和業務邏輯排序。

中央協調器(Orchestrator,簡稱 OSO)以命令/回覆的方式與每項服務進行通訊,全權負責告訴每一個參與者該作什麼以及何時該作什麼。

以電商訂單的例子爲例:

  • 事務發起方的主業務邏輯請求 OSO 服務開啓訂單事務
  • OSO 向庫存服務請求扣減庫存,庫存服務回覆處理結果。
  • OSO 向訂單服務請求建立訂單,訂單服務回覆建立結果。
  • OSO 向支付服務請求支付,支付服務回覆處理結果。
  • 主業務邏輯接收並處理 OSO 事務處理結果回覆。

中央協調器必須事先知道執行整個訂單事務所需的流程(例如經過讀取配置)。若是有任何失敗,它還負責經過向每一個參與者發送命令來撤銷以前的操做來協調分佈式的回滾。

基於中央協調器協調一切時,回滾要容易得多,由於協調器默認是執行正向流程,回滾時只要執行反向流程便可。

②事件編排(Event Choreography0):沒有中央協調器(沒有單點風險)時,每一個服務產生並觀察其餘服務的事件,並決定是否應採起行動。

在事件編排方法中,第一個服務執行一個事務,而後發佈一個事件。該事件被一個或多個服務進行監聽,這些服務再執行本地事務併發布(或不發佈)新的事件。

當最後一個服務執行本地事務而且不發佈任何事件時,意味着分佈式事務結束,或者它發佈的事件沒有被任何 Saga 參與者聽到都意味着事務結束。

以電商訂單的例子爲例:

  • 事務發起方的主業務邏輯發佈開始訂單事件。
  • 庫存服務監聽開始訂單事件,扣減庫存,併發布庫存已扣減事件。
  • 訂單服務監聽庫存已扣減事件,建立訂單,併發布訂單已建立事件。
  • 支付服務監聽訂單已建立事件,進行支付,併發布訂單已支付事件。
  • 主業務邏輯監聽訂單已支付事件並處理。

事件/編排是實現 Saga 模式的天然方式,它很簡單,容易理解,不須要太多的代碼來構建。若是事務涉及 2 至 4 個步驟,則多是很是合適的。

方案總結

命令協調設計的優勢以下:

  • 服務之間關係簡單,避免服務之間的循環依賴關係,由於 Saga 協調器會調用 Saga 參與者,但參與者不會調用協調器。
  • 程序開發簡單,只須要執行命令/回覆(其實回覆消息也是一種事件消息),下降參與者的複雜性。
  • 易維護擴展,在添加新步驟時,事務複雜性保持線性,回滾更容易管理,更容易實施和測試。

命令協調設計缺點以下:

  • 中央協調器容易處理邏輯容易過於複雜,致使難以維護。
  • 存在協調器單點故障風險。

事件/編排設計優勢以下:

  • 避免中央協調器單點故障風險。
  • 當涉及的步驟較少服務開發簡單,容易實現。

事件/編排設計缺點以下:

  • 服務之間存在循環依賴的風險。
  • 當涉及的步驟較多,服務間關係混亂,難以追蹤調測。

值得補充的是,因爲 Saga 模型中沒有 Prepare 階段,所以事務間不能保證隔離性。

當多個 Saga 事務操做同一資源時,就會產生更新丟失、髒數據讀取等問題,這時須要在業務層控制併發,例如:在應用層面加鎖,或者應用層面預先凍結資源。

總結

各方案使用場景

介紹完分佈式事務相關理論和常看法決方案後,最終的目的在實際項目中運用,所以,總結一下各個方案的常見的使用場景:

  • 2PC/3PC:依賴於數據庫,可以很好的提供強一致性和強事務性,但相對來講延遲比較高,比較適合傳統的單體應用,在同一個方法中存在跨庫操做的狀況,不適合高併發和高性能要求的場景。
  • TCC:適用於執行時間肯定且較短,實時性要求高,對數據一致性要求高,好比互聯網金融企業最核心的三個服務:交易、支付、帳務。
  • 本地消息表/MQ 事務:都適用於事務中參與方支持操做冪等,對一致性要求不高,業務上能容忍數據不一致到一我的工檢查週期,事務涉及的參與方、參與環節較少,業務上有對帳/校驗系統兜底。
  • Saga 事務:因爲 Saga 事務不能保證隔離性,須要在業務層控制併發,適合於業務場景事務併發操做同一資源較少的狀況。 Saga 相比缺乏預提交動做,致使補償動做的實現比較麻煩,例如業務是發送短信,補償動做則得再發送一次短信說明撤銷,用戶體驗比較差。Saga 事務較適用於補償動做容易處理的場景。

分佈式事務方案設計

本文介紹的偏向於原理,業界已經有很多開源的或者收費的解決方案,篇幅所限,就再也不展開介紹。

實際運用理論時進行架構設計時,許多人容易犯「手裏有了錘子,看什麼都以爲像釘子」的錯誤,設計方案時考慮的問題場景過多,各類重試,各類補償機制引入系統,致使系統過於複雜,落地遙遙無期。

世界上解決一個計算機問題最簡單的方法:「剛好」不須要解決它!

—— 阿里中間件技術專家沈詢

有些問題,看起來很重要,但實際上咱們能夠經過合理的設計或者將問題分解來規避。

設計分佈式事務系統也不是須要考慮全部異常狀況,沒必要過分設計各類回滾,補償機制。

若是硬要把時間花在解決問題自己,實際上不只效率低下,並且也是一種浪費。

若是系統要實現回滾流程的話,有可能系統複雜度將大大提高,且很容易出現 Bug,估計出現 Bug 的機率會比須要事務回滾的機率大不少。

在設計系統時,咱們須要衡量是否值得花這麼大的代價來解決這樣一個出現機率很是小的問題,能夠考慮當出現這個機率很小的問題,可否採用人工解決的方式,這也是你們在解決疑難問題時須要多多思考的地方。

參考資料:

  • technology-talk —— 事務
  • MySQL 中事務的實現
  • 分佈式一致性算法 2PC 和 3PC
  • 分佈式開放消息系統(RocketMQ)的原理與實踐
  • RocketMQ 事務消息入門介紹
  • Saga 分佈式事務解決方案與實踐 —— 姜寧
  • 分佈式事務 Saga 模式
  • 從一筆金幣充值去思考分佈式事務
相關文章
相關標籤/搜索