分布式事務各大方案詳細解讀2PC、3PC、TCC、本地消息表、MQ事務、Saga

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

相信耐心看完這篇文章,談到分布式事務,不再只是有“2PC”、“3PC”、“MQ的消息事務”、“最終一致性”、“TCC”等這些知識碎片,而是能夠將知識連成一片,形成知識體系。

什么是事務

介紹分布式事務之前,先介紹什么是事務。

事務的具體定義

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

簡單地說,事務提供一種“ 要么什么都不做,要么做全套(All or Nothing)”機制。

image

數據庫事務的 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)

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

image

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

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

一個支持事務(Transaction)的數據庫,需要具有這 4 種特性,否則在事務過程當中無法保證數據的正確性,處理結果極可能達不到請求方的要求。

什么時候使用數據庫事務

在介紹完事務基本概念之后,什么時候該使用數據庫事務?

簡單而言,就是業務上有一組數據操作,需要如果其中有任何一個操作執行失敗,整組操作全部不執行并恢復到未執行狀態,要么全部成功,要么全部失敗。

在使用數據庫事務時需要注意,盡可能短的保持事務,修改多個不同表的數據的冗長事務會嚴重妨礙系統中的所有其他用戶,這很有可能導致一些性能問題。

什么是分布式事務

介紹完事務相關基本概念之后,下面介紹分布式事務。

分布式產生背景與概念

隨著互聯網快速發展,微服務,SOA 等服務架構模式正在被大規模的使用,現在分布式系統一般由多個獨立的子系統組成,多個子系統通過網絡通信互相協作配合完成各個功能。

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

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

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

舉個互聯網常用的交易業務為例:

image

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

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

image

可以看到,如果多個數據庫之間的數據更新沒有保證事務,將會導致出現子系統數據不一致,業務出現問題。

分布式事務的難點

事務的原子性

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

事務的一致性

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

事務的隔離性

事務隔離性的本質就是如何正確處理多個并發事務的讀寫沖突和寫寫沖突,因為在分布式事務控制中,可能會出現提交不同步的現象,這個時候就有可能出現“部分已經提交”的事務。

此時并發應用訪問數據如果沒有加以控制,有可能出現“臟讀”問題。

分布式系統的一致性

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

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

image

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

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

布魯爾在提出 CAP 猜想時并沒有具體定義 Consistency、Availability、Partition Tolerance 這 3 個詞的含義,不同資料的具體定義也有差別。

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

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 (一致性 + 分區容忍性)

image

如上圖所示,因為 Node1 節點和 Node2 節點連接中斷導致分區現象,Node1 節點的數據已經更新到 y,但是 Node1 和 Node2 之間的復制通道中斷,數據 y 無法同步到 Node2,Node2 節點上的數據還是舊數據 x。

這時客戶端 C 訪問 Node2 時,Node2 需要返回 error,提示客戶端 “系統現在發生了錯誤”,這種處理方式違背了可用性(Availability)的要求,因此 CAP 三者只能滿足 CP。

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

image

同樣是 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 理論

image

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)消息。

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

image

情況 1,當所有參與者均反饋 yes,提交事務,如上圖:

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

情況 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 操作。根據響應情況,有以下兩種可能。

image

情況 1:階段 1 所有參與者均反饋 yes,參與者預執行事務,如上圖:

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

情況 2:階段 1 任何一個參與者反饋 no,或者等待超時后協調者尚無法收到所有參與者的反饋,即中斷事務,如上圖:

  • 協調者向所有參與者發出 abort 請求。
  • 無論收到協調者發出的 abort 請求,或者在等待協調者請求過程中出現超時,參與者均會中斷事務。

階段 3:do Commit

該階段進行真正的事務提交,也可以分為以下兩種情況。

image

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

  • 如果協調者處于工作狀態,則向所有參與者發出 do Commit 請求。
  • 參與者收到 do Commit 請求后,會正式執行事務提交,并釋放整個事務期間占用的資源。
  • 各參與者向協調者反饋 ack 完成的消息。
  • 協調者收到所有參與者反饋的 ack 消息后,即完成事務提交。
image

情況 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)可以將其執行結果撤銷。

image

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

②Confirm / Cancel 階段

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

Confirm 和 Cancel 操作滿足冪等性,如果 Confirm 或 Cancel 操作執行失敗,將會不斷重試直到執行完成。

Confirm:當 Try 階段服務全部正常執行, 執行確認業務邏輯操作

image

這里使用的資源一定是 Try 階段預留的業務資源。在 TCC 事務機制中認為,如果在 Try 階段能正常的預留資源,那 Confirm 一定能完整正確的提交。

Confirm 階段也可以看成是對 Try 階段的一個補充,Try+Confirm 一起組成了一個完整的業務邏輯。

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

image

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

方案總結

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

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

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

本地消息表:最終一致性

方案簡介

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

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

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

處理流程

下面把分布式事務最先開始處理的事務方稱為事務主動方,在事務主動方之后處理的業務內的其他事務稱為事務被動方。

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

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

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

整個業務處理流程如下:

image

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

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

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

消息中間件可以基于 Kafka、RocketMQ 消息隊列,事務主動方主動寫消息到消息隊列,事務消費方消費并處理消息隊列中的消息。

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

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

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

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

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

  • 當步驟 1 處理出錯,事務回滾,相當于什么都沒發生。
  • 當步驟 2、步驟 3 處理出錯,由于未處理的事務消息還是保存在事務發送方,事務發送方可以定時輪詢為超時消息數據,再次發送到消息中間件進行處理。事務被動方消費事務消息重試處理。
  • 如果是業務上的失敗,事務被動方可以發消息給事務主動方進行回滾。
  • 如果多個事務被動方已經消費消息,事務主動方需要回滾事務時需要通知事務被動方回滾。

方案總結

方案的優點如下:

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

缺點如下:

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

MQ 事務:最終一致性

方案簡介

基于 MQ 的分布式事務方案其實是對本地消息表的封裝,將本地消息表基于 MQ 內部,其他方面的協議基本與本地消息表一致。

處理流程

下面主要基于 RocketMQ 4.3 之后的版本介紹 MQ 的分布式事務方案。

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

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

image

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

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

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

image

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

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

介紹完 RocketMQ 的事務消息方案后,由于前面已經介紹過本地消息表方案,這里就簡單介紹 RocketMQ 分布式事務:

image

事務主動方基于 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 就是直接提交到庫。

下面以下單流程為例,整個操作包括:創建訂單、扣減庫存、支付、增加積分。

image

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

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

Saga 定義了兩種恢復策略:

image

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

image

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

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

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

中央協調器(Orchestrator,簡稱 OSO)以命令/回復的方式與每項服務進行通信,全權負責告訴每個參與者該做什么以及什么時候該做什么。

image

以電商訂單的例子為例:

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

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

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

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

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

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

image

以電商訂單的例子為例:

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

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

方案總結

命令協調設計的優點如下:

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

命令協調設計缺點如下:

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

事件/編排設計優點如下:

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

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

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

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

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

總結

各方案使用場景

image

介紹完分布式事務相關理論和常見解決方案后,最終的目的在實際項目中運用,因此,總結一下各個方案的常見的使用場景:

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

分布式事務方案設計

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

實際運用理論時進行架構設計時,許多人容易犯“手里有了錘子,看什么都覺得像釘子”的錯誤,設計方案時考慮的問題場景過多,各種重試,各種補償機制引入系統,導致系統過于復雜,落地遙遙無期。

世界上解決一個計算機問題最簡單的方法:“恰好”不需要解決它!

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

有些問題,看起來很重要,但實際上我們可以通過合理的設計或者將問題分解來規避。

設計分布式事務系統也不是需要考慮所有異常情況,不必過度設計各種回滾,補償機制。

如果硬要把時間花在解決問題本身,實際上不僅效率低下,而且也是一種浪費。

如果系統要實現回滾流程的話,有可能系統復雜度將大大提升,且很容易出現 Bug,估計出現 Bug 的概率會比需要事務回滾的概率大很多。

在設計系統時,我們需要衡量是否值得花這么大的代價來解決這樣一個出現概率非常小的問題,可以考慮當出現這個概率很小的問題,能否采用人工解決的方式,這也是大家在解決疑難問題時需要多多思考的地方。

參考資料:

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

apache rocketmqpre-commit

本文轉載自:https://maimai.cn/article/detail?fid=1122653559&efid=jewbG8cL4Wikr2CJ3r_fAw

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容