1. 程式人生 > >還不理解“分布式事務”?這篇給你講清楚!

還不理解“分布式事務”?這篇給你講清楚!

事務日誌 all 傳輸 sql 事務 return 禁止 使用 一致性算法 出錯

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

相信耐心看完這篇文章,談到分布式事務,不再只是有“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:事務主動方處理本地事務。

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

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

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

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

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

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

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

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

  • 當步驟 1 處理出錯,事務回滾,相當於什麽都沒發生。
  • 當步驟 2、步驟 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 模式
  • 從一筆金幣充值去思考分布式事務

轉自:

還不理解“分布式事務”?這篇給你講清楚!_數據庫
http://www.sohu.com/a/295542601_505827

還不理解“分布式事務”?這篇給你講清楚!