一、 分佈式事務的含義
事務都是基於單數據庫的本地事務,目前的數據庫僅支持單庫事務,並不支持跨庫事務。而隨著微服務架構的普及,一個大型業務系統往往由若干個子系統構成,這些子系統又擁有各自獨立的數據庫。往往一個業務流程需要由多個子系統共同完成,而且這些操作可能需要在一個事務中完成。在微服務系統中,這些業務場景是普遍存在的。此時,我們就需要在數據庫之上通過某種手段,實現支持跨數據庫的事務支持,這也就是大家常說的“分佈式事務”。
這裡舉一個分佈式事務的典型例子——用戶下單過程。
當我們的系統採用了微服務架構後,一個電商系統往往被拆分成如下幾個子系統:商品系統、訂單系統、支付系統、積分系統等。整個下單的過程如下:
1. 用戶通過商品系統瀏覽商品,他看中了某一項商品,便點擊下單
2. 此時訂單系統會生成一條訂單
3. 訂單創建成功後,支付系統提供支付功能
4. 當支付完成後,由積分系統為該用戶增加積分
上述步驟2、3、4需要在一個事務中完成。對於傳統單體應用而言,實現事務非常簡單,只需將這三個步驟放在一個方法A中,再用Spring的@Transactional註解標識該方法即可。Spring通過數據庫的事務支持,保證這些步驟要麼全都執行完成,要麼全都不執行。但在這個微服務架構中,這三個步驟涉及三個系統,涉及三個數據庫,此時我們必須在數據庫和應用系統之間,通過某項黑科技,實現分佈式事務的支持。

二、 CAP理論
CAP理論說的是:在一個分佈式系統中,最多隻能滿足C、A、P中的兩個需求。
1. CAP的含義
C:Consistency 一致性
同一數據的多個副本是否實時相同。
A:Availability 可用性
可用性:一定時間內 & 系統返回一個明確的結果 則稱為該系統可用。
P:Partition tolerance 分區容錯性
將同一服務分佈在多個系統中,從而保證某一個系統宕機,仍然有其他系統提供相同的服務。
2.
CAP的選型CAP理論告訴我們,在分佈式系統中,C、A、P三個條件中我們最多隻能選擇兩個。那麼問題來了,究竟選擇哪兩個條件較為合適呢?
對於一個業務系統來說,可用性和分區容錯性是必須要滿足的兩個條件,並且這兩者是相輔相成的。業務系統之所以使用分佈式系統,主要原因有兩個:
1) 提升整體性能
當業務量猛增,單個服務器已經無法滿足我們的業務需求的時候,就需要使用分佈式系統,使用多個節點提供相同的功能,從而整體上提升系統的性能,這就是使用分佈式系統的第一個原因。
2) 實現分區容錯性
單一節點 或 多個節點處於相同的網絡環境下,那麼會存在一定的風險,萬一該機房斷電、該地區發生自然災害,那麼業務系統就全面癱瘓了。為了防止這一問題,採用分佈式系統,將多個子系統分佈在不同的地域、不同的機房中,從而保證系統高可用性。
這說明分區容錯性是分佈式系統的根本,如果分區容錯性不能滿足,那使用分佈式系統將失去意義。
此外,可用性對業務系統也尤為重要。在大談用戶體驗的今天,如果業務系統時常出現“系統異常”、響應時間過長等情況,這使得用戶對系統的好感度大打折扣,在互聯網行業競爭激烈的今天,相同領域的競爭者不甚枚舉,系統的間歇性不可用會立馬導致用戶流向競爭對手。因此,我們只能通過犧牲一致性來換取系統的可用性和分區容錯性。這也就是下面要介紹的BASE理論。

三、 BASE理論
CAP理論告訴我們一個悲慘但不得不接受的事實——我們只能在C、A、P中選擇兩個條件。而對於業務系統而言,我們往往選擇犧牲一致性來換取系統的可用性和分區容錯性。不過這裡要指出的是,所謂的“犧牲一致性”並不是完全放棄數據一致性,而是犧牲強一致性換取弱一致性。下面來介紹下BASE理論。
1. BASE的含義
BA:Basic Available 基本可用
整個系統在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內仍然能夠返回一個明確的結果。只不過“基本可用”和“高可用”的區別是:
“一定時間”可以適當延長
當舉行大促時,響應時間可以適當延長
給部分用戶返回一個降級頁面
給部分用戶直接返回一個降級頁面,從而緩解服務器壓力。但要注意,返回降級頁面仍然是返回明確結果。
S:Soft State:柔性狀態
同一數據的不同副本的狀態,可以不需要實時一致。
E:Eventual Consisstency:最終一致性
同一數據的不同副本的狀態,可以不需要實時一致,但一定要保證經過一定時間後仍然是一致的。
2. BASE的選型
ACID能夠保證事務的強一致性,即數據是實時一致的。這在本地事務中是沒有問題的,在分佈式事務中,強一致性會極大影響分佈式系統的性能,因此分佈式系統中遵循BASE理論即可。但分佈式系統的不同業務場景對一致性的要求也不同。如交易場景下,就要求強一致性,此時就需要遵循ACID理論,而在註冊成功後發送短信驗證碼等場景下,並不需要實時一致,因此遵循BASE理論即可。因此要根據具體業務場景,在ACID和BASE之間尋求平衡。
四、 分佈式事務協議
下面介紹幾種實現分佈式事務的協議。
1. 2PC
二階段提交(Two-phaseCommit)是指,在計算機網絡以及數據庫領域內,為了使基於分佈式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法(Algorithm)。通常,二階段提交也被稱為是一種協議(Protocol))。在分佈式系統中,每個節點雖然可以知曉自己的操作時成功或者失敗,卻無法知道其他節點的操作的成功或失敗。當一個事務跨越多個節點時,為了保持事務的ACID特性,需要引入一個作為協調者的組件來統一掌控所有節點(稱作參與者)的操作結果並最終指示這些節點是否要把操作結果進行真正的提交(比如將更新後的數據寫入磁盤等等)。因此,
二階段提交的算法思路可以概括為:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。所謂的兩個階段是指:第一階段:準備階段(投票階段)和第二階段:提交階段(執行階段)。
1) 準備階段
事務協調者(事務管理器)給每個參與者(資源管理器)發送Prepare消息,每個參與者要麼直接返回失敗(如權限驗證失敗),要麼在本地執行事務,寫本地的redo和undo日誌,但不提交,到達一種“萬事俱備,只欠東風”的狀態。
可以進一步將準備階段分為以下三個步驟:
1)協調者節點向所有參與者節點詢問是否可以執行提交操作(vote),並開始等待各參與者節點的響應。
2)參與者節點執行詢問發起為止的所有事務操作,並將Undo信息和Redo信息寫入日誌。(注意:若成功這裡其實每個參與者已經執行了事務操作)
3)各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參與者節點的事務操作實際執行失敗,則它返回一個”中止”消息。
2) 提交階段
如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(Rollback)消息;否則,發送提交(Commit)消息;參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源。(注意:必須在最後階段釋放鎖資源)
接下來分兩種情況分別討論提交階段的過程。
當協調者節點從所有參與者節點獲得的相應消息都為”同意”時:
1)協調者節點向所有參與者節點發出”正式提交(commit)”的請求。
2)參與者節點正式完成操作,並釋放在整個事務期間內佔用的資源。
3)參與者節點向協調者節點發送”完成”消息。
4)協調者節點受到所有參與者節點反饋的”完成”消息後,完成事務。
如果任一參與者節點在第一階段返回的響應消息為”中止”,或者 協調者節點在第一階段的詢問超時之前無法獲取所有參與者節點的響應消息時:
1)協調者節點向所有參與者節點發出”回滾操作(rollback)”的請求。
2)參與者節點利用之前寫入的Undo信息執行回滾,並釋放在整個事務期間內佔用的資源。
3)參與者節點向協調者節點發送”回滾完成”消息。
4)協調者節點受到所有參與者節點反饋的”回滾完成”消息後,取消事務。
不管最後結果如何,第二階段都會結束當前事務。
二階段提交看起來確實能夠提供原子性的操作,但是不幸的事,二階段提交還是有幾個缺點的:
1、同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。
2、單點故障 。由於協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那麼所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因為協調者宕機導致的參與者處於阻塞狀態的問題)
3、數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之後就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分佈式系統便出現了數據部一致性的現象。
4、二階段無法解決的問題。協調者再發出commit消息之後宕機,而唯一接收到這條消息的參與者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。
由於二階段提交存在著諸如同步阻塞、單點問題、腦裂等缺陷,所以,研究者們在二階段提交的基礎上做了改進,提出了三階段提交。
2. 3PC
三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。
與兩階段提交不同的是,三階段提交有兩個改動點。
1、引入超時機制。同時在協調者和參與者中都引入超時機制。
2、在第一階段和第二階段中插入一個準備階段。保證了在最後提交階段之前各參與節點的狀態是一致的。
也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。
1) CanCommit階段
3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。
1.事務詢問 協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然後開始等待參與者的響應。
2.響應反饋 參與者接到CanCommit請求之後,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,並進入預備狀態。否則反饋No
2) PreCommit階段
協調者根據參與者的反應情況來決定是否可以記性事務的PreCommit操作。根據響應情況,有以下兩種可能。
假如協調者從所有的參與者獲得的反饋都是Yes響應,那麼就會執行事務的預執行。
1.發送預提交請求 協調者向參與者發送PreCommit請求,並進入Prepared階段。
2.事務預提交 參與者接收到PreCommit請求後,會執行事務操作,並將undo和redo信息記錄到事務日誌中。
3.響應反饋 如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。
假如有任何一個參與者向協調者發送了No響應,或者等待超時之後,協調者都沒有接到參與者的響應,那麼就執行事務的中斷。
1.發送中斷請求 協調者向所有參與者發送abort請求。
2.中斷事務 參與者收到來自協調者的abort請求之後(或超時之後,仍未收到協調者的請求),執行事務的中斷。
3) doCommit階段
該階段進行真正的事務提交,也可以分為以下兩種情況。
執行提交
1.發送提交請求 協調接收到參與者發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有參與者發送doCommit請求。
2.事務提交 參與者接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。
3.響應反饋 事務提交完之後,向協調者發送Ack響應。
4.完成事務 協調者接收到所有參與者的ack響應之後,完成事務。
中斷事務 協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。
1.發送中斷請求 協調者向所有參與者發送abort請求
2.事務回滾 參與者接收到abort請求之後,利用其在階段二記錄的undo信息來執行事務的回滾操作,並在完成回滾之後釋放所有的事務資源。
3.反饋結果 參與者完成事務回滾之後,向協調者發送ACK消息
4.中斷事務 協調者接收到參與者反饋的ACK消息之後,執行事務的中斷。
五、 分佈式事務的解決方案
1. 結合MQ消息中間件實現的可靠消息最終一致性
可靠消息最終一致性,需要業務系統結合MQ消息中間件實現,在實現過程中需要保證消息的成功發送及成功消費。即需要通過業務系統控制MQ的消息狀態
2. TCC補償性事務解決方案
TCC補償性,分為三個階段TRYING-CONFIRMING-CANCELING。每個階段做不同的處理。
①TRYING階段主要是對業務系統進行檢測及資源預留
②CONFIRMING階段是做業務提交,通過TRYING階段執行成功後,再執行該階段。默認如果TRYING階段執行成功,CONFIRMING就一定能成功。
③CANCELING階段是回對業務做回滾,在TRYING階段中,如果存在分支事務TRYING失敗,則需要調用CANCELING將已預留的資源進行釋放。
3. 最大努力通知型方案
這種方案主要用在與第三方系統通訊時,比如:調用微信或支付寶支付後的支付結果通知。這種方案也是結合MQ進行實現,例如:通過MQ發送http請求,設置最大通知次數。達到通知次數後即不再通知。
基於事務消息的MQ方案是目前公認的較為理想的分佈式事務解決方案,各大電商都在應用這一方案。這種方式適合的業務場景廣泛,而且比較可靠。不過這種方式技術實現的難度比較大。目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實現對事務消息的支持,所以需二次開發或者新造輪子。
-------END-------
本文參考了一些大牛的精華,如果覺的對自己有幫助,可以關注一下,支持一下
閱讀更多 JAVA程序人生 的文章