分佈式事務解決方案全解析

分佈式事務解決方案

對於剛剛接觸分佈式系統的夥伴來說,分佈式看起來非常高大上、深不可測。目前已有Dubbo、SpringCloud等較好的分佈式框架,但分佈式事務仍是分佈式系統一大痛點,本文結合一些經典博客文章,簡單解析一些常見的分佈式事務解決方案。

基礎介紹

事務

事務可以看成是由多個小事件一起組成的一個大事件,這些小事件要麼全部成功,則整個事件成功;如果有任意一個事件失敗,則所有事件均宣告失敗,並恢復成事件執行之前的樣子。

本地事務

在單應用開發場景中,較多的是通過關係型數據庫來控制事務,利用數據庫本身的事務特性來實現的,稱爲數據庫事務,也可以稱爲本地事務。數據庫事務在實現時會將一次事務的所有操作全部納入到一個不可分割的執行單元,該執行單元的所有操作要麼全部成功,要麼全部失敗,若其中任意操作執行失敗,都將導致整個事務的回滾。

ACID

嚴格意義上的事務實現應該是具備原子性一致性隔離性持久性,簡稱 ACID

  • A(Atomic)原子性:一個事務中所有操作,要麼都執行完成,要麼全部不執行,不可能出現部分成功部分失敗的情況。
  • C(Consistency)一致性:在事務執行前後,數據庫的一致性約束沒有被破壞。可以理解爲數據是滿足完整性約束的,也就是不會存在中間狀態的數據。比如:張三給李四轉 100 元,轉賬前和轉賬後的數據是正確的狀態這叫一致性,如果出現張三轉出 100 元,李四賬戶沒有增加 100 元這就出現了中間狀態的錯誤數據,沒有達到一致性。
  • I(Isolation)隔離性:數據庫中的事務一般都是併發的,隔離性是指併發的兩個事務的執行互不干擾,一個事務不能看到其他事務的運行過程的中間狀態。通過配置事務隔離級別可以比避免髒讀、重複讀問題。
  • D(Durability)持久性:一個事務完成了之後數據就被被持久化到數據庫,之後的其他操作或故障都不會對事務的結果產生影響或者回滾操作。

分佈式事務

隨着互聯網技術的發展與數據體量的擴增,軟件系統逐漸由單體應用演變爲分佈式系統/微服務應用。分佈式系統把一個單體應用系統拆分成可獨立部署的多個微服務,很多場景下需要服務之間遠程協作才能完成事務操作,這種分佈式系統環境下由不同的服務之間通過網絡遠程協作完成事務稱之爲分佈式事務。分佈式系統中實現事務,其實是由多個本地事務組合而成。對於分佈式事務而言幾乎滿足不了 ACID。

場景介紹

  • 跨JVM進程產生分佈式事務
    典型的場景就是微服務架構:微服務之間通過遠程調用完成事務操作。比如:訂單微服務和庫存微服務,下單的同時訂單微服務請求庫存微服務減少庫存。

    跨JVM進程產生分佈式事務

  • 跨數據庫實例產生分佈式事務
    當單體應用需要訪問多個數據庫(實例)時就會產生分佈式事務。比如:用戶信息和訂單信息分別在兩個MySQL實例存儲,用戶管理系統刪除用戶信息,需要分別刪除用戶信息及用戶的訂單信息,由於用戶信息和訂單數據分佈在不同的數據實例,需要通過不同的數據庫鏈接去操作數據,就會產生分佈式事務。

    跨數據庫實例產生分佈式事務

  • 多服務訪問同一個數據庫實例
    訂單微服務和庫存微服務即使訪問同一個數據庫也會產生分佈式事務,原因就是跨JVM進程,兩個微服務持有了不同的數據庫鏈接進行數據庫操作,此時產生分佈式事務。

    多服務訪問同一個數據庫實例

CAP理論

CAP 是 Consistency、Availability、Partition tolerance 三個單詞的縮寫,分別表示一致性可用性分區容忍性。一個分佈式系統最多隻能同時滿足一致性、可用性和分區容錯性這三項中的兩項。

CAP簡介
  • C(Consistency)一致性

一致性是指寫操作後的讀操作可以讀取到最新的數據狀態,當數據分佈在多個節點上,從任意結點讀取到的數據都是最新的狀態,即所有節點在同一時間的數據完全一致

A. 從客戶端和服務端來看一致性:

​ 1.從客戶端來看,一致性主要指的是多併發訪問時更新過的數據如何獲取的問題。

​ 2.從服務端來看,則是更新如何分佈到整個系統,以保證數據最終一致。

B. 從一致性的程度來看一致性:

​ 1.強一致性:對於關係型數據庫,要求更新過的數據能被後續的訪問都能看到。

​ 2.弱一致性:能容忍後續的部分或者全部訪問不到。

​ 3.最終一致性:經過一段時間後要求能訪問到更新後的數據。

C. 分佈式系統一致性的特點:

​ 1.由於存在數據同步的過程,寫操作的響應會有一定的延遲。

​ 2.爲了保證數據一致性會對資源暫時鎖定,待數據同步完成釋放鎖定資源。

​ 3.如果請求數據同步失敗的結點則會返回錯誤信息,一定不會返回舊數據。

  • A(Availability)可用性

可用性指服務一直可用,任何事務操作都可以得到響應結果,且不會出現響應超時或響應錯誤。

  • P(Partition tolerance)分區容錯性

分區容錯性指在遇到某節點或網絡分區故障的時候,仍然能夠對外提供滿足一致性和可用性的服務。通常分佈式系統的各各結點部署在不同的子網,不可避免的會出現由於網絡問題而導致結點之間通信失敗,此時仍可對外提供服務,即爲分區容錯性。分區容錯性分是布式系統具備的基本能力。

CAP組合

在所有分佈式事務場景中不會同時具備 CAP 三個特性,因爲在具備了P的前提下C和A是不能共存的。在生產中對分佈式事務處理時要根據需求來確定滿足 CAP 的哪兩個方面。

  • AP:滿足可用性和容錯性,捨棄一致性。這也就是意味着你的系統在併發訪問的時候可能會出現數據不一致的情況。這是很多分佈式系統設計時的選擇,大多數都是犧牲了一致性。但通常實現 AP 都會保證最終一致性,比如現在的12306搶票,本來你看到的是還有一張票,其實在這個時刻已經被買走了,你填好了信息準備提交訂單的時候發現系統提示你已售罄。這就是犧牲了一致性,但過一段時間再重新查詢,就會發現無票,這是實現了最終一致性,只要用戶可以接受在一定的時間查到正確的數據即可。
  • CP:滿足一致性和容錯性,捨棄可用性。如果你的系統允許有段時間的訪問失效等問題,這個是可以滿足的。比如多個人併發買票,後臺網絡出現故障,你買的時候系統就崩潰了。zookeeper 其實就是追求的強一致,又比如跨行轉賬,一次轉賬請求要等待雙方銀行系統都完成整個事務纔算完成。
  • CA:滿足一致性和可用性,捨棄容錯性。不考慮由於網絡不通或結點掛掉的問題,那麼系統將不是一個標準的分佈式系統,涉及分佈式的想法就是把功能分開,部署到不同的機器上。最常用的關係型數據就滿足了 CA。
小結

CAP 是一個已經被證實的理論,一個分佈式系統最多隻能同時滿足:一致性(Consistency)、可用性(Availability)和分區容忍性(Partition tolerance)這三項中的兩項。它可以作爲我們進行架構設計、技術選型的考量標準。對於多數大型互聯網應用的場景,節點多、部署分散,而且現在的集羣規模越來越大,所以節點故障、網絡故障是常態,而且要保證服務可用性達到 N 個 9(99.99…%),並要達到良好的響應性能來提高用戶體驗,因此一般都會做出如下選擇:保證 P 和 A ,捨棄 C 的強一致性,但保證最終一致性。

BASE理論

BASE理論簡介

BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。BASE 理論是對 CAP 中 AP 的一個擴展,通過犧牲強一致性來獲得可用性,當出現故障允許部分不可用但要保證核心功能可用,允許數據在一段時間內是不一致的,但最終達到一致狀態。滿足BASE理論的事務,我們稱之爲“柔性事務”。

  • 基本可用:分佈式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。如電商網站交易付款出現問題了,商品依然可以正常瀏覽。
  • 軟狀態:由於不要求強一致性,所以BASE允許系統中存在中間狀態(也叫軟狀態),這個狀態不影響系統可用性,如訂單的"支付中"、“數據同步中”等狀態,待數據最終一致後狀態改爲“成功”狀態。
  • 最終一致:最終一致是指經過一段時間後,所有節點數據都將會達到一致。如訂單的"支付中"狀態,最終會變 爲“支付成功”或者"支付失敗",使訂單狀態與實際交易結果達成一致,但需要一定時間的延遲、等待。
強一致性和最終一致性

CAP 理論告訴我們一個分佈式系統最多隻能同時滿足一致性(Consistency)、可用性(Availability)和分區容忍性(Partition tolerance)這三項中的兩項,其中AP在實際應用中較多,AP 即捨棄一致性,保證可用性和分區容忍性,但是在實際生產中很多場景都要實現一致性,比如主數據庫向從數據庫同步數據,即使不要一致性,但是最終也要將數據同步成功來保證數據一致,這種一致性和 CAP 中的一致性不同,CAP 中的一致性要求在任何時間查詢每個結點數據都必須一致,它強調的是強一致性,但是最終一致性是允許可以在一段時間內每個結點的數據不一致,但是經過一段時間每個結點的數據必須一致,它強調的是最終數據的一致性。

ACID與BASE
ACID (剛性事務) BASE (柔性事務)
原子性(Atomicity) 基本可用(Basically Available)
一致性(Consistency) 柔性狀態(Soft state)
隔離性(Isolation) 最終一致性(Eventually Consistent)
持久性(Durability)

二階段提交(2PC)

方案簡介

2PC(Two-phase commit protocol),中文叫二階段提交。 2PC 是一種強一致性設計,有事務協調者事務參與者兩個主要角色,事務的發起者爲事務協調者,事務的其他執行者爲事務參與者。事務協調者協調管理各事務參與者的提交和回滾,。我們可以把事務協調者想象爲帶頭大哥,而事務參與者則理解爲跟班小弟,由帶頭大哥協調所有跟班小弟的任務執行。

流程分析

準備階段

  1. 事務協調者,給所有事務參與者發送事務內容,並詢問能否提交事務,然後等待參與者回覆。
  2. 事務參與者,收到事務內容,開始執行事務,同時將 undoredo 信息記入事務日誌中,但此時事務參與者並不提交事務。
  3. 如果參與者執行事務成功,則給協調者響應 yes ,回答可以進行事務提交;若果參與者事務執行失敗,則給協調者回復 no ,表示不可進行事務提交。

提交階段

事務協調者會等待收到所有事務參與者響應後纔會進行下一步操作,且事務協調者在該階段中有超時機制。如果事務協調者收到事務參與者響應信息爲 yes,則向所有事務參與者發送提交(commit)信息;如果事務協調者收到事務參與者的失敗信息或超時信息,則會給所有事務參與者發送回滾(rollback)信息進行事務回滾。

事務參與者根據來自事務協調者的指令執行提交事務或者回滾事務的操作,並釋放所有事務處理過程中佔用的鎖資源(必須在最後階段釋放鎖資源) 。以下是兩種情況具體步驟解析:

  • 當所有事務參與者均響應 yes,則提交事務,如下圖。
  1. 事務協調者向所有事務參與者發出正式提交事務(commit)的請求。
  2. 事務參與者執行事務提交操作,若提交失敗,則會不斷重試,直到提交成功,然後釋放整個事務期間佔用的資源。
  3. 所有事務參與者向事務協調者響應提交事務完成的 ack 消息。
  4. 事務協調者收到所有事務參與者響應的 ack 消息後,完成提交事務操作。

2PC-正常事務

  • 當準備階段任意一個事務參與者響應 no,則中斷事務,如下圖:
  1. 事務協調者向所有參與者發出回滾(rollback)請求。
  2. 事務參與者使用準備階段中的 undo 信息執行事務回滾操作,若回滾失敗則會不斷重試,直到所有事務參與者都回滾成功,然後釋放整個事務期間佔用的資源。
  3. 各事務參與者向事務協調者響應回滾事務完成的 ack 消息。
  4. 事務協調者收到所有參與者響應的 ack 消息後,完成事務中斷操作。

2PC-事務中斷

方案小結

2PC 方案實現起來相對簡單,但實際項目中用的比較少,主要因爲以下問題:

  1. 性能較低:所有事務參與者在事務提交階段處於同步阻塞狀態,佔用系統資源,效率低,有性能瓶頸。
  2. 可靠性問題:協調者存在單點故障問題,如果事務協調者出現故障,事務參與者將一直處於鎖死狀態。
  3. 數據一致性問題:在提交階段中,如果發生網絡波動,部分事務參與者收到了提交消息,另一些事務參與者沒收到提交消息,導致了節點之間數據不一致的問題。

三階段提交(3PC)

方案簡介

3PC 也是強一致性,該方案是 2PC 上的改進版本,主要是在事務協調者和事務參與者中都引入超時機制,並 將 2PC 方案的準備階段拆分爲2個階段,插入了一個預提交階段(PreCommit),以此來處理2PC的提交階段中事務參與者發生崩潰或錯誤,或者網絡波動,導致事務參與者無法知曉是否提交或回滾的不確定狀態所引起的延時問題。

流程分析

準備階段(CanCommit)

事務協調者向事務參與者發送 commit 請求,參與者如果可以提交就 yes 並進入預提交狀態(與2PC中的準備階段不同,參與者不執行事務操作),否則返回 no 響應。

  1. 事務協調者向所有參與者發出包含事務內容的 canCommit 請求,詢問是否可以提交事務,並等待所有事務參與者的響應。
  2. 事務參與者收到 canCommit 請求後,如果認爲可以執行事務操作,則響應 yes 並進入預備狀態,否則反饋 no (與2PC中的準備階段不同,參與者不執行事務操作)。

預提交階段(PreCommit)

3PC中的預提交階段和2PC中的準備階段一樣,執行事務但不提交。事務協調者根據準備階段中事務參與者的響應決定是否可以進行事務的預提交操作。

  • 準備階段中所有事務參與者均響應 yes,事務參與者進入預提交狀態。
  1. 事務協調者向所有事務參與者發出 preCommit 請求,進入預提交階段。
  2. 事務參與者收到 preCommit 請求後,執行事務操作,將 undo 和 redo 信息記入事務日誌中(但不提交事務)。
  3. 所有事務參與者向事務協調者響應執行事務成功的 ack 消息或失敗的 no消息 ,並等待事務協調者發出最終指令。

3PC-預提交響應yes

  • 當準備階段任意一個事務參與者響應 no,或者等待超時後事務協調者尚無法收到所有參與者的響應,則中斷事務
  1. 事務協調者向所有事務參與者發出 abort 請求。
  2. 事務參與者收到事務協調者發出的 abort 請求,或在等待事務協調者的指令過程中出現超時,均會中斷事務。

3PC-預提交響應no

提交階段(DoCommit)

該階段與2PC的提交階段一樣,進行真正的事務提交。進入提交階段後,無論是事務協調者出現問題,或者事務協調者與事務參與者網絡出現問題,都會導致事務參與者無法接收到事務協調者發出的 do Commit 請求或 abort 請求,事務參與者都會在等待超時之後,繼續執行事務提交,因爲預提交階段的引入起到了一個統一狀態的作用,進入到提交階段則事務參與者默認認爲事務應該被提交。

  • 預提交階段中所有事務參與者均響應 ack 消息,執行真正的事務提交
  1. 如果事務協調者處於工作狀態,則向所有事務參與者發出 do Commit 請求。
  2. 事務參與者收到 do Commit 請求後,會正式執行事務提交操作,並釋放整個事務期間佔用的資源。
  3. 所有事務參與者向協調者響應提交事務完成的 ack 消息。
  4. 事務協調者收到所有事務參與者響應的 ack 消息後,完成事務提交。

3PC-提交事務

  • 當預提交階段中任意一個事務參與者響應 no,或者在預提交階段中事務協調者無法收到所有參與者的響應,則中斷事務
  1. 如果事務協調者處於工作狀態,向所有事務參與者發出 abort 請求。
  2. 事務參與者使用預提交階段中的 undo 信息執行回滾操作,並釋放整個事務期間佔用的資源。
  3. 所有事務參與者向事務協調者響應回滾事務完成的 ack 消息。
  4. 事務協調者收到所有事務參與者響應的 ack 消息後,完成事務中斷。

3PC-事務中斷

方案小結

優點:3PC相比2PC,會先詢問事務參與者是否有條件接事務,不會直接鎖資源,降低了阻塞範圍,在等待超時後事務協調者或事務參與者會中斷事務。避免了事務協調者單點故障問題,3PC的提交階段中即使事務協調者出現問題時,事務參與者會繼續提交事務。

缺點:引入一個階段,多一次交互,性能比2PC更低,且絕大部分情況事務參與者都由能執行事務的條件,仍要先詢問一次。而且數據不一致問題依然存在,當在參與者收到 preCommit 請求後等待 do commite 指令時,此時如果協調者請求中斷事務,而協調者無法與參與者正常通信,會導致參與者繼續提交事務,造成數據不一致。

TCC

方案簡介

2PC 和3PC 都是數據庫層面的,而 TCC 是基於業務層的分佈式事務。TCC 的概念,最早是由 Pat Helland 於 2007 年發表的一篇名爲《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出的。其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。

TCC 是 Try、Confirm、Cancel 三個詞語的縮寫,TCC 要求每個分支事務實現三個操作:預處理 Try、確認 Confirm、撤銷,Try、Confirm、Cancel 可以理解爲 SQL 事務中的 Lock、Commit、Rollback。可以將TCC 簡單理解爲服務化的 2PC 編程模型,都是先試探性的執行,如果都可以那就真正的執行,如果不行就回滾。

TCC中還可以有一個事務管理者的角色 - TM 事務管理器:TM事務管理器可以實現爲獨立的服務,也可以讓全局事務發起方充當 TM 的角色,TM 獨立出來是爲了成爲公用組件,是爲了考慮系統結構和軟件複用。TM 在發起全局事務時生成全局事務記錄,全局事務 ID 貫穿整個分佈式事務調用鏈條,用來記錄事務上下文, 追蹤和記錄狀態,由於 Confirm 和 Cancel 失敗需進行重試,因此需要實現爲冪等,冪等性是指同一個操作無論請求多少次,其結果都相同。

TCC 的 Try、Confirm、Cancel 3 個方法均由業務編碼實現:

  • Try 操作作爲一階段,負責資源的檢查、預留和鎖定。
  • Confirm 操作相當於2PC的提交階段,執行真正的業務。
  • Cancel 是預留資源的取消,可以理解爲撤銷預留階段的動作。

流程分析

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

Try 階段

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

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

  • 完成所有業務檢查工作( 一致性 ) 。
  • 預留鎖定必須業務資源( 準隔離性 ) 。
  • Try 嘗試執行業務。

TCC 事務機制是以初步操作(Try)爲中心的,確認操作(Confirm)和取消操作(Cancel)都是圍繞初步操作(Try)而展開。因此,Try 階段中的操作,其保障性是最好的,即使失敗,仍然有取消操作(Cancel)可以將其執行結果撤銷。

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

TCC-Try

Confirm階段

Confirm:當 Try 階段服務全部正常執行, 執行確認業務邏輯操作。Try 階段所有分支事務執行成功後開始執行 Confirm。通常情況下,採用 TCC 則認爲 Confirm 階段是不會出錯的。即:只要 Try 成功,Confirm 一定成功,若 Confirm 階段真的出錯了,需引入重試機制或人工處理,所以 Confirm 操作需要滿足冪等性。

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

TCC-Confirm

Cancel 階段

Cancel:當 Try 階段存在服務執行失敗需要回滾, 進入 Cancel 階段,執行分支事務的業務取消,釋放Try階段中的預留業務員資源。通常情況下,採用 TCC 則認爲 Cancel 階段也是一定成功的。若 Cancel 階段真的出錯了,需引入重試機制或人工處理,所以 Cancel 操作需要滿足冪等性。

TCC-CANCEL

方案小結

TCC 事務機制相對於傳統事務機制(X/Open XA),有以下優點:

  • 性能提升:讓應用自己定義數據操作的粒度,使得降低鎖衝突、不會鎖定整個資源,提高吞吐量。
  • 適用範圍廣:基於業務實現,TCC 可以跨數據庫,跨不同業務系統實現事務。
  • 最終一致性:基於 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事務消息方案可以算是最大努力通知方案,最大努力通知方案的目標:發起通知方通過一定的機制最大努力將業務處理結果通知到接收方。具體包括:

  1. 有一定的消息重複通知機制。因爲接收通知方可能沒有接收到通知,此時要有一定的機制對消息重複通知。
  2. 消息校對機制。如果盡最大努力也沒有通知到接收方,或者接收方消費消息後要再次消費,此時可由接收方主動向通知方查詢消息信息來滿足需求。

所以最大努力通知其實只是表明了一種柔性事務的思想:我已經盡力我最大的努力想達成事務的最終一致了。

基於 MQ 的分佈式事務方案其實是對本地消息表的封裝,將本地消息表基於 MQ 內部,其他方面的協議基本與本地消息表一致。有一些第三方的MQ是支持事務消息的,比如RocketMQ,他們支持事務消息的方式也是類似於採用的二階段提交,但是市面上一些主流的MQ都是不支持事務消息的,比如 RabbitMQ 和 Kafka 都不支持。

流程分析

下面主要基於 RocketMQ 介紹 MQ 的分佈式事務方案。

在本地消息表方案中,保證事務主動方發寫業務表數據和寫消息表數據的一致性是基於數據庫事務,RocketMQ 的事務消息相對於普通 MQ,相當於提供了 2PC 的提交接口,第一階段Prepared消息,會拿到消息的地址。 第二階段執行本地事務,第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。方案如下:

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

MQ事務-事務主動方發消息

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

  1. 發送方向 MQ 服務端(MQ Server)發送事務消息即半消息(half),半消息不是說一半消息,而是這個消息對消費者來說不可見(圖中 1)。
  2. MQ Server 將消息持久化成功之後,向發送方 ack 確認消息已經發送成功(圖中 2)。
  3. 發送方開始執行本地事務邏輯(圖中 3)。
  4. 發送方根據本地事務執行結果向 MQ Server 提交二次確認(commit 或是 rollback)(圖中 4)。
  5. MQ Server 收到 commit 狀態則將半消息標記爲可投遞,訂閱方最終將收到該消息;MQ Server 收到 rollback 狀態則刪除半消息,訂閱方將不會接受該消息(圖中 5)。
  • 異常情況:事務主動方消息恢復

MQ事務-事務主動方消息恢復

有斷網或者應用重啓等異常情況,流程如下:

  1. 提交的二次確認超時未到達 MQ Server(圖中 4)。
  2. MQ Server 對該消息發起消息回查(圖中 5)。
  3. 發送方收到消息回查後,需要檢查對應消息的本地事務執行的最終結果(圖中 6)。
  4. 發送方根據檢查得到的本地事務的最終狀態再次提交二次確認(圖中 7)。
  5. MQ Server基於 commit/rollback 對消息進行投遞或者刪除(圖中 8)。

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

MQ分佈式事務

方案小結

最大努力通知與可靠消息一致性有什麼不同?

  1. 解決方案思想不同

    可靠消息一致性,發起通知方需要保證將消息發出去,並且將消息發到接收通知方,消息的可靠性關鍵由發起通知方來保證。最大努力通知,發起通知方盡最大的努力將業務處理結果通知爲接收通知方,但是可能消息接收不到,此時需要接 收通知方主動調用發起通知方的接口查詢業務處理結果,通知的可靠性關鍵在接收通知方。

  2. 兩者的業務應用場景不同

    可靠消息一致性關注的是交易過程的事務一致,以異步的方式完成交易。最大努力通知關注的是交易後的通知事務,即將交易結果可靠的通知出去。

  3. 技術解決方向不同

    可靠消息一致性要解決消息從發出到接收的一致性,即消息發出並且被接收到。最大努力通知無法保證消息從發出到接收的一致性,只提供消息接收的可靠性機制。可靠機制是,最大努力的將消息通知給接收方,當消息無法被接收方接收時,由接收方主動查詢消息(業務處理結果)

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

  • 消息數據獨立存儲 ,降低業務系統與消息系統之間的耦合。
  • 系統吞吐量高於本地消息表方案。

缺點是:

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

Saga 事務

方案簡介

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

Saga 事務核心思想是將長事務拆分爲多個本地短事務組成,由 Saga 事務協調器協調,每個本地事務有相應的執行模塊和補償模塊,如果正常結束那就正常完成,當 Saga 事務中的任意一個本地事務出錯了, 可以根據相反順序調用相關事務對應的補償方法恢復,達到事務的最終一致性。在服務請求的過程中,可能會出現超時重試的情況,需要通過冪等來避免多次請求所帶來的問題。

ACID與Saga

ACID (剛性事務) Saga 只提供ACD保證
原子性(Atomicity) 原子性(通過Saga協調器實現)
一致性(Consistency) 一致性(本地事務 + Saga Log)
隔離性(Isolation) 隔離性(Saga 不保證)
持久性(Durability) 持久性(Saga Log)

流程分析

Saga 事務基本協議如下:

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

可以看到,和 TCC 相比,Saga 沒有“預留”動作,它的 Ti 就是直接提交到庫。

Saga 的執行順序有兩種,如下圖。下面以下單流程爲例,整個操作包括:創建訂單、扣減庫存、支付、增加積分。

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。如下圖:

Saga-向前恢復

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

Saga-向後恢復

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

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

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

以電商訂單的例子爲例:

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

Saga-命令協調

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

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

  • 分佈式的實現方式 - 事件編排(Event Choreography0):沒有中央協調器(沒有單點風險)時,每個服務產生並觀察其他服務的事件,並決定是否應採取行動。

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

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

以電商訂單的例子爲例:

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

Saga-事件編排

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

隔離性

由於 Saga 模型中只支持ACD,沒有 Prepare 階段,因此事務間不能保證隔離性。

當多個 Saga 事務操作同一資源時,就會產生數據語義不一致、更新丟失、髒數據讀取等問題。需要在業務層控制併發,解決方案如下:

  • 在應用層面加入邏輯鎖。
  • Session層面隔離保證串行化操作。
  • 業務層面預先凍結資源數據。
  • 業務操作過程中通過及時讀取當前狀態的方式獲取更新。

方案小結

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

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

命令協調設計缺點如下:

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

事件編排設計優點如下:

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

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

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

Seata 方案

方案簡介

Seata 是由阿里中間件團隊發起的開源項目 Fescar,後更名爲 Seata,它是一個是開源的分佈式事務框架。

傳統 2PC 的問題在 Seata 中得到了解決,它通過對本地關係數據庫的分支事務的協調來驅動完成全局事務,是工作在應用層的中間件。主要優點是性能較好,且不長時間佔用連接資源,它以高效並且對業務 0 侵入的方式解決微服務場景下面臨的分佈式事務問題,它目前提供 AT 模式(即 2PC)及 TCC 模式的分佈式事務解決方案。

設計思想

Seata 的設計目標其一是對業務無侵入,因此從業務無侵入的 2PC 方案着手,在傳統 2PC的基礎上演進,並解決 2PC 方案面臨的問題。

Seata 把一個分佈式事務理解成一個包含了若干分支事務全局事務。全局事務的職責是協調其下管轄的分支事務達成一致,要麼一起成功提交,要麼一起失敗回滾。此外,通常分支事務本身就是一個關係數據庫的本地事務,下圖是全局事務與分支事務的關係圖:

seata1

與傳統 2PC 的模型類似,Seata 定義了 3 個組件來協議分佈式事務的處理過程:

seata2

  • Transaction Coordinator(TC):事務協調器,它是獨立的中間件,需要獨立部署運行,它維護全局事務的運行狀態,接收 TM 指令發起全局事務的提交與回滾,負責與 RM 通信協調各各分支事務的提交或回滾。
  • Transaction Manager(TM): 事務管理器,TM 需要嵌入應用程序中工作,它負責開啓一個全局事務,並最終向 TC 發起全局提交或全局回滾的指令。
  • Resource Manager(RM):控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器 TC 的指令,驅動分支(本地)事務的提交和回滾。

流程分析

拿新用戶註冊送積分舉例,簡單分析Seata的分佈式事務過程:

seata3

  1. 用戶服務的 TM 向 TC 申請開啓一個全局事務,全局事務創建成功並生成一個全局唯一的 XID。
  2. 用戶服務的 RM 向 TC 註冊分支事務,該分支事務在用戶服務執行新增用戶邏輯,並將其納入 XID 對應全局事務的管轄。
  3. 用戶服務執行分支事務,向用戶表插入一條記錄。
  4. 邏輯執行到遠程調用積分服務時(XID 在微服務調用鏈路的上下文中傳播)。積分服務的 RM 向 TC 註冊分支事務,該分支事務執行增加積分的邏輯,並將其納入 XID 對應全局事務的管轄。
  5. 積分服務執行分支事務,向積分記錄表插入一條記錄,執行完畢後,返回用戶服務。
  6. 用戶服務分支事務執行完畢。
  7. TM 向 TC 發起針對 XID 的全局提交或回滾決議。
  8. TC 調度 XID 下管轄的全部分支事務完成提交或回滾請求。

方案小結

Seata方案與傳統 2PC 方案對比,有以下優點:

  • 架構層次方面:傳統 2PC 方案的 RM 實際上是在數據庫層,RM 本質上就是數據庫自身,通過 XA 協議實現,而 Seata 的 RM 是以 jar 包的形式作爲中間件層部署在應用程序這一側的。
  • 二階段提交方面:傳統 2PC無論提交階段的決議是 commit 還是 rollback ,事務性資源的鎖都要保持到 Phase2 完成才釋放。而 Seata 的做法是在 Phase1 就將本地事務提交,這樣就可以省去 Phase2 持鎖的時間,整體提高效率。

由於 Seata 的 0 侵入性並且解決了傳統 2PC 長期鎖資源的問題,推薦採用 Seata 實現 2PC。

總結

方案對比

19

各方案使用場景

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

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