六種常用事務解決方案 沒有最好只有更好
1 事務概念
在分布式系統中,為了保證數據的高可用,通常,我們會將數據保留多個副本(replica),這些副本會放置在不同的物理的機器上。為了對用戶提供正確的 CRUD 等語義,我們需要保證這些放置在不同物理機器上的副本是一致的。分布式事務在現在遍地都是分布式部署的系統中幾乎是必要的。
我們的項目用到了數據庫,也和事務有關,我們先分析一下項目的問題,再描述一下事務。
如上圖,如果用戶打車成功,需要修改司機狀態、下單、記錄支付日志,而每個操作都是調用了不同的服務,比如此時 hailtaxi-driver? 服務執行成功了,但是 hailtaxi-order 有可能執行失敗了,這時候如何實現跨服務事務回滾呢?這就要用到分布式事務。
1.1 事務簡介
事務(Transaction)是訪問并可能更新數據庫中各種數據項的一個程序執行單元(unit)。在關系數據庫中,一個事務由一組SQL語句組成。事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。
原子性(atomicity) :事務是一個不可分割的工作單位,事務中包括的諸操作要么都做,要么都不做。
一致性(consistency) :事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態,事務的中間狀態不能被觀察到的。
隔離性(isolation) :一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。隔離性又分為四個級別:讀未提交(read uncommitted)、讀已提交(read committed,解決臟讀)、可重復讀(repeatable read,解決虛讀)、串行化(serializable,解決幻讀)。
持久性(durability) :持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
任何事務機制在實現時,都應該考慮事務的ACID特性,包括:本地事務、分布式事務,及時不能都很好的滿足,也要考慮支持到什么程度。
1.2 本地事務
大多數場景下,我們的應用都只需要操作單一的數據庫,這種情況下的事務稱之為本地事務( Local Transaction )。本地事務的ACID特性是數據庫直接提供支持。本地事務應用架構如下所示:
很多java應用都整合了spring,并使用其聲明式事務管理功能來完成事務功能。一般使用的步驟如下:
? 1、配置事務管理器。spring提供了一個 PlatformTransactionManager 接口,其有2個重要的實現類:
DataSourceTransactionManager? :用于支持本地事務,事實上,其內部也是通過操作 java.sql.Connection 來開啟、提交和回滾事務。
JtaTransactionManager :用于支持分布式事務,其實現了JTA規范,使用XA協議進行兩階段提交。需要注意的是,這只是一個代理,我們需要為其提供一個JTA provider,一般是Java EE容器提供的事務協調器(Java EE server's transaction coordinator),也可以不依賴容器,配置一個本地的JTA provider。
? 2、 在需要開啟的事務的bean的方法上添加 @Transitional 注解
可以看到,spring除了支持本地事務,也支持分布式事務,下面我們先對分布式事務的典型應用場景進行介紹。
1.3 分布式事務
當下互聯網發展如火如荼,絕大部分公司都進行了數據庫拆分和服務化(SOA)。在這種情況下,完成某一個業務功能可能需要橫跨多個服務,操作多個數據庫。這就涉及到到了分布式事務,用需要操作的資源位于多個資源服務器上,而應用需要保證對于多個資源服務器的數據的操作,要么全部成功,要么全部失敗。本質上來說,分布式事務就是為了保證不同資源服務器的數據一致性。
1.3.1 跨庫事務
跨庫事務指的是,一個應用某個功能需要操作多個庫,不同的庫中存儲不同的業務數據。下圖演示了一個服務同時操作2個庫的情況:
1.3.2 分庫分表事務
通常一個庫數據量比較大或者預期未來的數據量比較大,都會進行水平拆分,也就是分庫分表。如下圖,將數據庫B拆分成了2個庫:
? 對于分庫分表的情況,一般開發人員都會使用一些數據庫中間件來降低sql操作的復雜性。如,對于sql: insert into user(id,name) values (1,"gupaoedu"),(2,"gpvp") 。這條sql是操作單庫的語法,單庫情況下,可以保證事務的一致性。
但是由于現在進行了分庫分表,開發人員希望將1號記錄插入分庫1,2號記錄插入分庫2。所以數據庫中間件要將其改寫為2條sql,分別插入兩個不同的分庫,此時要保證兩個庫要不都成功,要不都失敗,因此基本上所有的數據庫中間件都面臨著分布式事務的問題。
1.3.3 跨應用事務
微服務架構是目前一個比較一個比較火的概念。例如上面提到的一個案例,某個應用同時操作了9個庫,這樣的應用業務邏輯必然非常復雜,對于開發人員是極大的挑戰,應該拆分成不同的獨立服務,以簡化業務邏輯。拆分后,獨立服務之間通過RPC框架來進行遠程調用,實現彼此的通信。下圖演示了一個3個服務之間彼此調用的架構:
Service A完成某個功能需要直接操作數據庫,同時需要調用Service B和Service C,而Service B又同時操作了2個數據庫,Service C也操作了一個庫。需要保證這些跨服務的對多個數據庫的操作要不都成功,要不都失敗,實際上這可能是最典型的分布式事務場景。
上述討論的分布式事務場景中,無一例外的都直接或者間接的操作了多個數據庫。如何保證事務的ACID特性,對于分布式事務實現方案而言,是非常大的挑戰。同時,分布式事務實現方案還必須要考慮性能的問題,如果為了嚴格保證ACID特性,導致性能嚴重下降,那么對于一些要求快速響應的業務,是無法接受的。
2 分布式理論
分布式事務可以有多種分類,比如柔性事務和強一致性事務,這些事務操作會遵循一定的定理,比如CAP原理、BASE理論。
2.1 CAP原理
CAP 定理又被稱作布魯爾定理,是加州大學的計算機科學家布魯爾在 2000 年提出的一個猜想。2002 年,麻省理工學院的賽斯·吉爾伯特和南?!ち制姘l表了布魯爾猜想的證明,使之成為分布式計算領域公認的一個定理。
布魯爾在提出CAP猜想時并沒有具體定義 Consistency、Availability、Partition Tolerance 這3個詞的含義,不同資料的具體定義也有差別,為了更好地解釋,下面選擇Robert Greiner的文章《CAP Theorem》作為參考基礎。
CAP定理是這樣描述的:在一個分布式系統(指互相連接并共享數據的節點的集合)中,當涉及讀寫操作時,只能保證一致性(Consistence)、可用性(Availability)、分區容錯性(PartitionTolerance)三者中的兩個,另外一個必須被犧牲。
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 的選擇
雖然 CAP 理論定義是三個要素中只能取兩個,但放到分布式環境下來思考,我們會發現必須選擇 P(分區容忍)要素,因為網絡本身無法做到 100% 可靠,有可能出故障,所以分區是一個必然的現象。
為什么必須要選擇P:
如果我們選擇了 CA(一致性 + 可用性) 而放棄了 P(分區容忍性),那么當發生分區現象時,為了保證 C(一致性),系統需要禁止寫入,當有寫入請求時,系統返回 error(例如,當前系統不允許寫入),這又和 A(可用性) 沖突了,因為 A(可用性)要求返回 no error 和 no timeout。
因此,分布式系統理論上不可能選擇 CA (一致性 + 可用性)架構, 只能選擇 CP(一致性 + 分區容忍性) 或者 AP (可用性 + 分區容忍性)架構,在一致性和可用性做折中選擇 。
針對這兩種選擇再來看一下:
1、CP - Consistency + Partition Tolerance (一致性 + 分區容忍性)
如上圖所示,因為Node1節點和Node2節點連接中斷導致分區現象,Node1節點的數據已經更新到y,但是Node1 和 Node2 之間的復制通道中斷,數據 y 無法同步到 Node2,Node2 節點上的數據還是舊數據x。
這時客戶端C 訪問 Node2 時,Node2 需要返回 Error,提示客戶端 “系統現在發生了錯誤”,這種處理方式違
背了可用性(Availability)的要求,因此 CAP 三者只能滿足 CP。
2、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。也就是說選了AP不意味著放棄了C,選了CP不意味著放棄了A
2.2 BASE理論
BASE 是指基本可用(Basically Available)、軟狀態( Soft State)、最終一致性( Eventual Consistency),是基于CAP定理演化而來,是對CAP中一致性和可用性權衡的結果;核心思想是即使無法做到強一致性(CAP 的一致性就是強一致性),但應用可以采用適合的方式達到最終一致性。
BA-Basically Available基本可用
分布式系統在出現故障時,允許損失部分可用性,即保證核心可用。
這里的關鍵詞是“ 部分 ”和“ 核心 ”,實際實踐上,哪些是核心需要根據具體業務來權衡。例如登錄功能相對注冊功能更加核心,注冊不了最多影響流失一部分用戶,如果用戶已經注冊但無法登錄,那就意味用戶無法使用系統,造成的影響范圍更大。
S-Soft State 軟狀態
允許系統存在中間狀態,而該中間狀態不會影響系統整體可用性。這里的中間狀態就是 CAP 理論中的數據不一致。
E-Eventual Consistency 最終一致性
系統中的所有數據副本經過一定時間后,最終能夠達到一致的狀態。
這里的關鍵詞是“一定時間” 和 “最終”,“ 一定時間 ”和數據的特性是強關聯的,不同業務不同數據能夠容忍的不一致時間是不同的。例如支付類業務是要求秒級別內達到一致,因為用戶時時關注;用戶發的最新微博,可以容忍30分鐘內達到一致的狀態,因為用戶短時間看不到明星發的微博是無感知的。而“ 最終 ”的含義就是不管多長時間,最終還是要達到一致性的狀態。
BASE 理論本質上是對 CAP 的延伸和補充,更具體地說,是對 CAP 中 AP 方案的一個補充:
CP 理論是忽略延時的,而實際應用中延時是無法避免的。
這一點就意味著完美的 CP 場景是不存在的,即使是幾毫秒的數據復制延遲,在這幾毫秒時間間隔內,系統是不符合 CP 要求的。因此 CAP 中的 CP 方案,實際上也是實現了最終一致性,只是“一定時間”是指幾毫秒而已。
AP 方案中犧牲一致性只是指發生分區故障期間,而不是永遠放棄一致性。
這一點其實就是 BASE 理論延伸的地方,分區期間犧牲一致性,但分區故障恢復后,系統應該達到最終一致性。
2.3 剛柔事務
何謂剛柔事務?剛性事務它的事務是原子的,要么都成功要么都失敗,也就是需要保障ACID理論,而柔性事務只需要保障數據最終一致即可,需要遵循BASE理論。
剛性事務滿足ACID理論
柔性事務滿足BASE理論(基本可用,最終一致)
基于BASE理論的設計思想,柔性事務下,在不影響系統整體可用性的情況下(Basically Available 基本可用),允許系統存在數據不一致的中間狀態(Soft State 軟狀態),在經過數據同步的延時之后,最終數據能夠達到一致。 并不是完全放棄了ACID,而是通過放寬一致性要求,借助本地事務來實現最終分布式事務一致性的同時也保證系統的吞吐 。
3 常用事務解決方案模型
分布式事務解決方案幾乎都是柔性事務,分布式事務的實現有許多種,其中較經典是由Tuxedo提出的XA分布式事務協議,XA協議包含二階段提交(2PC)和三階段提交(3PC)兩種實現。
其他還有 TCC、MQ 等最終一致性解決方案,至于工作中用哪種方案,需要根據業務場景選取, 2PC/3PC、TCC 數據強一致性高,而MQ是最終數據一致。
3.1 DTP模型
X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 這個組織定義的一套分布式事務的標準,也就是了定義了規范和API接口,由廠商進行具體的實現
X/Open DTP中的角色
AP(Application Program):應用程序,主要是定義事務邊界以及那些組成事務的特定于應用程序的操作。
RM(Resouces Manager):資源管理器,管理一些共享資源的自治域,如提供對諸如數據庫之類的共享資源的訪問。譬如:數據庫、文件系統等,并且提供了這些資源的訪問方式。
TM(Transaction Manager):事務管理器,管理全局事務,協調事務的提交或者回滾,并協調故障恢復。
DTP模型里面定義了XA協議接口,TM 和 RM 通過XA接口進行雙向通信
3.2 2PC
2PC? 、 3PC? ,都是基于 XA 協議的
方案簡介
二階段提交協議(Two-phase Commit,即2PC)是常用的分布式事務解決方案,即將事務的提交過程分為兩個階段來進行處理: 準備階段和提交階段。事務的發起者稱協調者,事務的執行者稱參與者 。
在分布式系統里,每個節點都可以知曉自己操作的成功或者失敗,卻無法知道其他節點操作的成功或失敗。當一個事務跨多個節點時,為了保持事務的原子性與一致性,而引入一個協調者來統一掌控所有參與者的操作結果,并指示它們是否要把操作結果進行真正的提交或者回滾(rollback)。
二階段提交的算法思路可以概括為: 參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作 。
核心思想就是對每一個事務都采用先嘗試后提交的處理方式,處理后所有的讀操作都要能獲得最新的數據,因此也可以將二階段提交看作是一個強一致性算法。
處理流程
簡單一點理解,可以把協調者節點比喻為帶頭大哥,參與者理解比喻為跟班小弟,帶頭大哥統一協調跟班小弟的任務執行。
階段1:準備階段
1、協調者向所有參與者發送事務內容,詢問是否可以提交事務,并等待所有參與者答復。
2、各參與者執行事務操作,將undo和redo信息記入事務日志中(但不提交事務)。
3、如參與者執行成功,給協調者反饋yes,即可以提交;如執行失敗,給協調者反饋no,即不可提交。
階段2:提交階段
如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(rollback)消息;否則,發送提交(commit)消息;參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源。(注意:必須在最后階段釋放鎖資源)
接下來分兩種情況分別討論提交階段的過程。
情況1,當所有參與者均反饋yes,提交事務:
- 1、協調者向所有參與者發出正式提交事務的請求(即commit請求)。
- 2、參與者執行commit請求,并釋放整個事務期間占用的資源。
- 3、各參與者向協調者反饋ack(應答)完成的消息。
- 4、協調者收到所有參與者反饋的ack消息后,即完成事務提交。
情況2,當任何階段1一個參與者反饋no,中斷事務:
- 1、協調者向所有參與者發出回滾請求(即rollback請求)。
- 2、參與者使用階段1中的undo信息執行回滾操作,并釋放整個事務期間占用的資源。
- 3、各參與者向協調者反饋ack完成的消息。
- 4、協調者收到所有參與者反饋的ack消息后,即完成事務中斷。
方案總結
2PC是一個強一致性的同步阻塞協議,事務執?過程中需要將所需資源全部鎖定,也就是俗稱的 剛性事務
2PC方案實現起來簡單,實際項目中使用比較少,主要因為以下問題:
- 性能問題所有參與者在事務提交階段處于同步阻塞狀態,占用系統資源,容易導致性能瓶頸。
- 可靠性問題如果協調者存在單點故障問題,如果協調者出現故障,參與者將一直處于鎖定狀態。
- 數據一致性問題在階段2中,如果發生局部網絡問題,一部分事務參與者收到了提交消息,另一部分事務參與者沒收到提交消息,那么就導致了節點之間數據的不一致。
- 只適用于單個服務中因為使用了XA規范,只支持單體服務的跨庫分布式事務,不支持跨服務間的分布式事務
3.3 3PC
方案簡介
三階段提交協議,是二階段提交協議的改進版本,與二階段提交不同的是,引入超時機制。同時在協調者和參與者中都引入超時機制( 2PC 中只有協調者有超時機制)。
三階段提交將二階段的準備階段拆分為2個階段,插入了一個preCommit階段,使得原先在二階段提交中,參與者在準備之后,由于協調者發生崩潰或錯誤,而導致參與者處于無法知曉是否提交或者中止的“不確定狀態”所產生的可能相當長的延時的問題得以解決。
處理流程
階段1:canCommit
協調者向參與者發送commit請求,參與者如果可以提交就返回yes響應(參與者不執行事務操作),否則返回no響應:
- 1、協調者向所有參與者發出包含事務內容的canCommit請求,詢問是否可以提交事務,并等待所有參與者答復。
- 2、參與者收到canCommit請求后,如果認為可以執行事務操作,則反饋yes并進入預備狀態,否則反饋no。
階段2:preCommit
協調者根據階段1 canCommit參與者的反應情況來決定是否可以基于事務的preCommit操作。根據響應情況,有以下兩種可能。
情況1,階段1所有參與者均反饋yes,參與者預執行事務:
- 1、協調者向所有參與者發出preCommit請求,進入準備階段。
- 2、參與者收到preCommit請求后,執行事務操作,將undo和redo信息記入事務日志中(但不提交事務)。
- 3、各參與者向協調者反饋ack響應或no響應,并等待最終指令。
情況2,階段1任何一個參與者反饋no,或者等待超時后協調者尚無法收到所有參與者的反饋,即中斷事務:
- 1、協調者向所有參與者發出abort請求。
- 2、無論收到協調者發出的abort請求,或者在等待協調者請求過程中出現超時,參與者均會中斷事務。
?階段3:do Commit
該階段進行真正的事務提交,也可以分為以下兩種情況:
情況1:階段2所有參與者均反饋ack響應,執行真正的事務提交:
- 1、如果協調者處于工作狀態,則向所有參與者發出do Commit請求。
- 2、參與者收到do Commit請求后,會正式執行事務提交,并釋放整個事務期間占用的資源。
- 3、各參與者向協調者反饋ack完成的消息。
- 4、協調者收到所有參與者反饋的ack消息后,即完成事務提交。
階段2任何一個參與者反饋no,或者等待超時后協調者尚無法收到所有參與者的反饋,即中斷事務:
- 1、如果協調者處于工作狀態,向所有參與者發出abort請求。
- 2、參與者使用階段1中的undo信息執行回滾操作,并釋放整個事務期間占用的資源。
- 3、各參與者向協調者反饋ack完成的消息。
- 4、協調者收到所有參與者反饋的ack消息后,即完成事務中斷
注意:進入階段3后,無論協調者出現問題,或者協調者與參與者網絡出現問題,都會導致參與者無法接收到協調者發出的do Commit請求或abort請求。此時,參與者都會在等待超時之后,繼續執行事務提交。
階段三 只允許成功不允許失敗,如果服務器宕機或者停電,因為記錄的階段二的數據,重啟服務后在提交事務,所以,到了階段三,失敗了也不進行回滾, 只允許成功 。
方案總結
優點
相比二階段提交,三階段提交降低了阻塞范圍,在等待超時后協調者或參與者會中斷事務。避免了協調者單點問題,階段3中協調者出現問題時,參與者會繼續提交事務。
缺點
數據不一致問題依然存在,當在參與者收到preCommit請求后等待do commite指令時,此時如果協調者請求中斷事務,而協調者無法與參與者正常通信,會導致參與者繼續提交事務,造成數據不一致。
3.4 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。
TCC? 為在業務層編寫代碼實現的兩階段提交。 TCC? 分別指 Try? 、 Confirm? 、 Cancel ,一個業務操作要對應的寫這三個方法。
處理流程
為了方便理解,下面以電商下單為例進行方案解析,這里把整個過程簡單分為扣減庫存,訂單創建2個步驟,庫存服務和訂單服務分別在不同的服務器節點上。
階段1:Try 階段
從執行階段來看,與傳統事務機制中業務邏輯相同。但從業務角度來看,卻不一樣。TCC機制中的Try僅是一個初步操作,它和后續的確認一起才能真正構成一個完整的業務邏輯,這個階段主要完成:
- 完成所有業務檢查( 一致性 )
- 預留必須業務資源( 準隔離性 )
- Try 嘗試執行業務TCC事務機制以初步操作(Try)為中心的,確認操作(Confirm)和取消操作(Cancel)都是圍繞初步操作(Try)而展開。因此,Try階段中的操作,其保障性是最好的,即使失敗,仍然有取消操作(Cancel)可以將其執行結果撤銷。
假設商品庫存為100,購買數量為2,這里檢查和更新庫存的同時,凍結用戶購買數量的庫存,同時創建訂單,訂單狀態為待確認。
階段2:Confirm / Cancel 階段
根據Try階段服務是否全部正常執行,繼續執行確認操作(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操作功能要按具體業務來實現,業務耦合度較高,提高了開發成本。
3.5 本地消息表
方案簡介
本地消息表的方案最初是由ebay提出,核心思路是將分布式事務拆分成本地事務進行處理。
方案通過在事務主動發起方額外新建事務消息表,事務發起方處理業務和記錄事務消息在本地事務中完成,輪詢事務消息表的數據發送事務消息,事務被動方基于消息中間件消費事務消息表中的事務。
這樣設計可以避免” 業務處理成功 + 事務消息發送失敗 “,或” 業務處理失敗 + 事務消息發送成功 “的棘手情況出現,保證2個系統事務的數據一致性。
處理流程
下面把分布式事務最先開始處理的事務方成為事務主動方,在事務主動方之后處理的業務內的其他事務成為事務被動方。
為了方便理解,下面繼續以電商下單為例進行方案解析,這里把整個過程簡單分為扣減庫存,訂單創建2個步驟,庫存服務和訂單服務分別在不同的服務器節點上,其中庫存服務是事務主動方,訂單服務是事務被動方。
事務的主動方需要額外新建事務消息表,用于記錄分布式事務的消息的發生、處理狀態。
整個業務處理流程如下:
步驟1 事務主動方處理本地事務。
事務主動方在本地事務中處理業務更新操作和寫消息表操作。
上面例子中庫存服務階段在本地事務中完成扣減庫存和寫消息表(圖中1、2)。
步驟2 事務主動方通過消息中間件,通知事務被動方處理事務通知事務待消息。
消息中間件可以基于Kafka、RocketMQ消息隊列,事務主動方法主動寫消息到消息隊列,事務消費方消費并處理消息隊列中的消息。
上面例子中,庫存服務把事務待處理消息寫到消息中間件,訂單服務消費消息中間件的消息,完成新增訂單(圖中3 - 5)。
步驟3 事務被動方通過消息中間件,通知事務主動方事務已處理的消息。
上面例子中,訂單服務把事務已處理消息寫到消息中間件,庫存服務消費中間件的消息,并將事務消息的狀態更新為已完成(圖中6 - 8)
為了數據的一致性,當處理錯誤需要重試,事務發送方和事務接收方相關業務處理需要支持冪等。具體保存一致性的容錯處理如下:
1、當步驟1處理出錯,事務回滾,相當于什么都沒發生。
2、當步驟2、步驟3處理出錯,由于未處理的事務消息還是保存在事務發送方,事務發送方可以定時輪詢為超時消息數據,再次發送的消息中間件進行處理。事務被動方消費事務消息重試處理。
3、如果是業務上的失敗,事務被動方可以發消息給事務主動方進行回滾。
4、如果多個事務被動方已經消費消息,事務主動方需要回滾事務時需要通知事務被動方回滾。
方案總結
方案的優點如下:
- 從應用設計開發的角度實現了消息數據的可靠性,消息數據的可靠性不依賴于消息中間件,弱化了對MQ中間件特性的依賴。
- 方案輕量,容易實現。
缺點如下:
- 與具體的業務場景綁定,耦合性強,不可公用。
- 消息數據與業務數據同庫,占用業務系統資源。
- 業務系統在使用關系型數據庫的情況下,消息服務性能會受到關系型數據庫并發性能的局限
3.6 MQ事務
MQ事務保證最終一致性。
方案簡介
基于MQ的分布式事務方案其實是對本地消息表的封裝,將本地消息表存于MQ 內部,其他方面的協議基本與本地消息表一致。
處理流程
下面主要基于RocketMQ4.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消息)
- 業務處理服務需要實現消息狀態回查接口