深入理解分佈式事務

什麼是事務

百度百科中是這樣解釋事務的,指作為單個邏輯工作單元執行的一系列操作,要麼完全地執行,要麼完全地不執行。簡單的說,事務就是併發控制的單位,是用戶定義的一個操作序列。

事務的ACID

1.原子性(Atomicity)

原子性是指事務是一個不可再分割的工作單元,事務中的操作要麼都發生,要麼都不發生。可採用“A向B轉賬”這個例子來說明解釋在DBMS中,默認情況下一條SQL就是一個單獨事務,事務是自動提交的。只有顯式的使用start transaction開啟一個事務,才能將一個代碼塊放在事務中執行。

2.一致性(Consistent)

一致性是指在事務開始之前和事務結束以後,數據庫的完整性約束沒有被破壞。這是說數據庫事務不能破壞關係數據的完整性以及業務邏輯上的一致性。

如A給B轉賬,不論轉賬的事務操作是否成功,其兩者的存款總額不變(這是業務邏輯的一致性,至於數據庫關係約束的完整性就更好理解了)。

保障機制(也從兩方面著手):數據庫層面會在一個事務執行之前和之後,數據會符合你設置的約束(唯一約束,外鍵約束,check約束等)和觸發器設置;此外,數據庫的內部數據結構(如 B 樹索引或雙向鏈表)都必須是正確的。業務的一致性一般由開發人員進行保證,亦可轉移至數據庫層面。

3. 隔離性 (Isolation)

多個事務併發訪問時,事務之間是隔離的,一個事務不應該影響其它事務運行效果。

在併發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。事務查看數據更新時,數據所處的狀態要麼是另一事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看到中間狀態的數據。

事務最複雜問題都是由事務隔離性引起的。完全的隔離性是不現實的,完全的隔離性要求數據庫同一時間只執行一條事務,這樣會嚴重影響性能。

4. 持久性(Durability)

這是最好理解的一個特性:持久性,意味著在事務完成以後,該事務所對數據庫所作的更改便持久的保存在數據庫之中,並不會被回滾。(完成的事務是系統永久的部分,對系統的影響是永久性的,該修改即使出現致命的系統故障也將一直保持)

write ahead logging:SQL Server中使用了WAL(Write-Ahead Logging)技術來保證事務日誌的ACID特性,在數據寫入到數據庫之前,先寫入到日誌,再將日誌記錄變更到存儲器中。

事務的隔離級別

數據庫事務的隔離級別有4種,由低到高分別為Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事務的併發操作中可能會出現髒讀,不可重複讀,幻讀。

1. Read uncommitted(讀未提交)

讀未提交,顧名思義,就是一個事務可以讀取另一個未提交事務的數據。

事例:老闆要給程序員發工資,程序員的工資是3.6萬/月。但是發工資時老闆不小心按錯了數字,按成3.9萬/月,該錢已經打到程序員的戶口,但是事務還沒有提交,就在這時,程序員去查看自己這個月的工資,發現比往常多了3千元,以為漲工資了非常高興。但是老闆及時發現了不對,馬上回滾差點就提交了的事務,將數字改成3.6萬再提交。

分析:實際程序員這個月的工資還是3.6萬,但是程序員看到的是3.9萬。他看到的是老闆還沒提交事務時的數據。這就是髒讀。

那怎麼解決髒讀呢?Read committed!讀提交,能解決髒讀問題。

2. Read committed (讀已提交)

讀提交,顧名思義,就是一個事務要等另一個事務提交後才能讀取數據。

事例:程序員拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(程序員事務開啟),收費系統事先檢測到他的卡里有3.6萬,就在這個時候!!程序員的妻子要把錢全部轉出充當家用,並提交。當收費系統準備扣款時,再檢測卡里的金額,發現已經沒錢了(第二次檢測金額當然要等待妻子轉出金額事務提交完)。程序員就會很鬱悶,明明卡里是有錢的…

分析:這就是讀提交,若有事務對數據進行更新(UPDATE)操作時,讀操作事務要等待這個更新操作事務提交後才能讀取數據,可以解決髒讀問題。但在這個事例中,出現了一個事務範圍內兩個相同的查詢卻返回了不同數據,這就是不可重複讀。

3. Repeatable read (可重複讀)

重複讀,就是在開始讀取數據(事務開啟)時,不再允許修改操作

事例:程序員拿著信用卡去享受生活(卡里當然是只有3.6萬),當他埋單時(事務開啟,不允許其他事務的UPDATE修改操作),收費系統事先檢測到他的卡里有3.6萬。這個時候他的妻子不能轉出金額了。接下來收費系統就可以扣款了。

分析:重複讀可以解決不可重複讀問題。寫到這裡,應該明白的一點就是,不可重複讀對應的是修改,即UPDATE操作。但是可能還會有幻讀問題。因為幻讀問題對應的是插入INSERT操作,而不是UPDATE操作。

什麼時候會出現幻讀?

事例:程序員某一天去消費,花了2千元,然後他的妻子去查看他今天的消費記錄(全表掃描FTS,妻子事務開啟),看到確實是花了2千元,就在這個時候,程序員花了1萬買了一部電腦,即新增INSERT了一條消費記錄,並提交。當妻子打印程序員的消費記錄清單時(妻子事務提交),發現花了1.2萬元,似乎出現了幻覺,這就是幻讀。

那怎麼解決幻讀問題?Serializable!

4. Serializable 序列化

Serializable 是最高的事務隔離級別,在該級別下,事務串行化順序執行,可以避免髒讀、不可重複讀與幻讀。但是這種事務隔離級別效率低下,比較耗數據庫性能,一般不使用。

值得一提的是:大多數數據庫默認的事務隔離級別是Read committed,比如Sql Server , Oracle。Mysql的默認隔離級別是Repeatable read。

什麼是分佈式事務

分佈式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不同的分佈式系統的不同節點之上。簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分佈在不同的服務器上,且屬於不同的應用,分佈式事務需要保證這些小操作要麼全部成功,要麼全部失敗。本質上來說,分佈式事務就是為了保證不同數據庫的數據一致性。

CAP理論

CAP原則又稱CAP定理,指的是在一個分佈式系統中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。

  CAP原則是NOSQL數據庫的基石。Consistency(一致性)。 Availability(可用性)。Partition tolerance(分區容錯性)。

分佈式系統的CAP理論:理論首先把分佈式系統中的三個特性進行了如下歸納:

  • 一致性(C):在分佈式系統中的所有數據備份,在同一時刻是否同樣的值。(等同於所有節點訪問同一份最新的數據副本)
  • 可用性(A):在集群中一部分節點故障後,集群整體是否還能響應客戶端的讀寫請求。(對數據更新具備高可用性)
  • 分區容忍性(P):以實際效果而言,分區相當於對通信的時限要求。系統如果不能在時限內達成數據一致性,就意味著發生了分區的情況,必須就當前操作在C和A之間做出選擇。


BASE理論

BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的簡寫,BASE是對CAP中一致性和可用性權衡的結果,其來源於對大規模互聯網系統分佈式實踐的結論,是基於CAP定理逐步演化而來的,其核心思想是即使無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。接下來我們著重對BASE中的三要素進行詳細講解。

基本可用

基本可用是指分佈式系統在出現不可預知故障的時候,允許損失部分可用性——但請注意,這絕不等價於系統不可用,以下兩個就是“基本可用”的典型例子。

  • 響應時間上的損失:正常情況下,一個在線搜索引擎需要0.5秒內返回給用戶相應的查詢結果,但由於出現異常(比如系統部分機房發生斷電或斷網故障),查詢結果的響應時間增加到了1~2秒。
  • 功能上的損失:正常情況下,在一個電子商務網站上進行購物,消費者幾乎能夠順利地完成每一筆訂單,但是在一些節日大促購物高峰的時候,由於消費者的購物行為激增,為了保護購物系統的穩定性,部分消費者可能會被引導到一個降級頁面。

弱狀態

弱狀態也稱為軟狀態,和硬狀態相對,是指允許系統中的數據存在中間狀態,並認為該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程存在延時。

最終一致性

最終一致性強調的是系統中所有的數據副本,在經過一段時間的同步後,最終能夠達到一個一致的狀態。因此,最終一致性的本質是需要系統保證最終數據能夠達到一致,而不需要實時保證系統數據的強一致性。

亞馬遜首席技術官Werner Vogels在於2008年發表的一篇文章中對最終一致性進行了非常詳細的介紹。他認為最終一致性是一種特殊的弱一致性:系統能夠保證在沒有其他新的更新操作的情況下,數據最終一定能夠達到一致的狀態,因此所有客戶端對系統的數據訪問都能夠取到最新的值。同時,在沒有發生故障的前提下,數據達到一致狀態的時間延遲,取決於網絡延遲,系統負載和數據複製方案設計等因素。

在實際工程實踐中,最終一致性存在以下五類主要變種。

因果一致性:

因果一致性是指,如果進程A在更新完某個數據項後通知了進程B,那麼進程B之後對該數據項的訪問都應該能夠獲取到進程A更新後的最新值,並且如果進程B要對該數據項進行更新操作的話,務必基於進程A更新後的最新值,即不能發生丟失更新情況。與此同時,與進程A無因果關係的進程C的數據訪問則沒有這樣的限制。

讀己之所寫:

讀己之所寫是指,進程A更新一個數據項之後,它自己總是能夠訪問到更新過的最新值,而不會看到舊值。也就是說,對於單個數據獲取者而言,其讀取到的數據一定不會比自己上次寫入的值舊。因此,讀己之所寫也可以看作是一種特殊的因果一致性。

會話一致性:

會話一致性將對系統數據的訪問過程框定在了一個會話當中:系統能保證在同一個有效的會話中實現“讀己之所寫”的一致性,也就是說,執行更新操作之後,客戶端能夠在同一個會話中始終讀取到該數據項的最新值。

單調讀一致性:

單調讀一致性是指如果一個進程從系統中讀取出一個數據項的某個值後,那麼系統對於該進程後續的任何數據訪問都不應該返回更舊的值。

單調寫一致性:

單調寫一致性是指,一個系統需要能夠保證來自同一個進程的寫操作被順序地執行。

以上就是最終一致性的五類常見的變種,在時間系統實踐中,可以將其中的若干個變種互相結合起來,以構建一個具有最終一致性的分佈式系統。事實上,最終一致性並不是只有那些大型分佈式系統才設計的特性,許多現代的關係型數據庫都採用了最終一致性模型。在現代關係型數據庫中,大多都會採用同步和異步方式來實現主備數據複製技術。在同步方式中,數據的複製通常是更新事務的一部分,因此在事務完成後,主備數據庫的數據就會達到一致。而在異步方式中,備庫的更新往往存在延時,這取決於事務日誌在主備數據庫之間傳輸的時間長短,如果傳輸時間過長或者甚至在日誌傳輸過程中出現異常導致無法及時將事務應用到備庫上,那麼很顯然,從備庫中讀取的數據將是舊的,因此就出現了不一致的情況。當然,無論是採用多次重試還是認為數據訂正,關係型數據庫還是能保證最終數據達到一致——這就是系統提供最終一致性保證的經典案例。

總的來說,BASE理論面向的是大型高可用可擴展的分佈式系統,和傳統事務的ACID特性是相反的,它完全不同於ACID的強一致性模型,而是提出通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。但同時,在實際的分佈式場景中,不同業務單元和組件對數據一致性的要求是不同的,因此在具體的分佈式系統架構設計過程中,ACID特性與BASE理論往往又會結合在一起使用。

酸鹼平衡

從ACID和BASE來說,ACID是為了保證一致性而誕生,因而側重一致性;BASE是為了高可用系統的設計而誕生,因而側重可用性。在分解C和A的情況時,肯定要涉及P,所以CAP理論統一了這一切。如果非要說酸鹼,或者說酸鹼平衡,那就是平衡於CAP理論。

CAP並不與ACID中的A(原子性)衝突,值得討論的是ACID中的C(一致性)和I(隔離性)。ACID的C指的是事務不能破壞任何數據庫規則,如鍵的唯一性。與之相比,CAP的C僅指單一副本這個意義上的一致性,因此只是ACID一致性約束的一個嚴格的子集。如果系統要求ACID中的I(隔離性),那麼它在分區期間最多可以在分區一側維持操作。事務的可串行性(serializability)要求全局的通信,因此在分區的情況下不能成立。

C與A之間的取捨可以在同一系統內以非常細小的粒度反覆發生,而每一次的決策可能因為具體的操作,乃至因為牽涉到特定的數據或用戶而有所不同。我們在分佈式系統設計的兩大原則中討論過保持一致性的手段:同步複製和異步複製,結合複製協議的各種模式,請參考下表。例如簡單滿足了C,但延遲升高了,吞吐量下來了,還有什麼可用性?我覺得延遲是包含在可用性的範圍內的,不可用就是延遲的極大極限值。還有文章就只討論一致性,可用性和性能問題(比如阿里何登成的《數據一致性-分區可用性-性能——多副本強同步數據庫系統實現之我見》),說明在不考慮分區的情況下,CA問題依然是系統設計的難點。

深入理解分佈式事務


可用性並不是簡單的網絡連通,服務可以訪問,數據可以讀取就是可用性,對於互聯網業務,可用性是完整的用戶體驗,甚至會延伸到用戶現實生活中(補償)。有的系統必須容忍大規模可靠分佈式系統中的數據不一致,其中原因就是為了在高併發條件下提高讀寫性能。

必須容忍大規模可靠分佈式系統中的數據不一致,有兩個原因:在高併發條件下提高讀寫性能, 並要區分物理上導致的不一致和協議規定的不一致。

節點已經宕機,副本無法訪問(物理)

法定數模型會使部分系統不可用的分區情況,即使節點已啟動並運行(paxos協議)

網絡斷開,節點孤立(物理)

所以,保證不發生分區,CA也不是免費午餐:儘管保證了網絡可靠性,儘量不發生分區,同時獲得CA也不是一件簡單的事情。

CA系統才是真正的難點。

宣稱是CA系統的,目前有兩家:一家是Google的Spanner,一家是Alibaba的OceanBase。

分佈式事務協議

常見的分佈式事務協議分為兩階段提交和三階段提交,下面重點介紹下這兩種分佈式協議。

兩階段提交

兩階段提交協議是協調所有分佈式原子事務參與者,並決定提交或取消(回滾)的分佈式算法。

(1)協議參與者

在兩階段提交協議中,系統一般包含兩類機器(或節點):一類為協調者(coordinator),通常一個系統中只有一個;另一類為事務參與者(participants,cohorts或workers),一般包含多個,在數據存儲系統中可以理解為數據副本的個數。協議中假設每個節點都會記錄寫前日誌(write-ahead log)並持久性存儲,即使節點發生故障日誌也不會丟失。協議中同時假設節點不會發生永久性故障而且任意兩個節點都可以互相通信。

深入理解分佈式事務



(2)兩個階段的執行

1.請求階段(commit-request phase,或稱表決階段,voting phase)在請求階段,協調者將通知事務參與者準備提交或取消事務,然後進入表決過程。在表決過程中,參與者將告知協調者自己的決策:同意(事務參與者本地作業執行成功)或取消(本地作業執行故障)。

2.提交階段(commit phase)在該階段,協調者將基於第一個階段的投票結果進行決策:提交或取消。當且僅當所有的參與者同意提交事務協調者才通知所有的參與者提交事務,否則協調者將通知所有的參與者取消事務。參與者在接收到協調者發來的消息後將執行響應的操作。

3)兩階段提交的缺點

1.同步阻塞問題。執行過程中,所有參與節點都是事務阻塞型的。當參與者佔有公共資源時,其他第三方節點訪問公共資源不得不處於阻塞狀態。

2.單點故障。由於協調者的重要性,一旦協調者發生故障。參與者會一直阻塞下去。尤其在第二階段,協調者發生故障,那麼所有的參與者還都處於鎖定事務資源的狀態中,而無法繼續完成事務操作。(如果是協調者掛掉,可以重新選舉一個協調者,但是無法解決因為協調者宕機導致的參與者處於阻塞狀態的問題)

3.數據不一致。在二階段提交的階段二中,當協調者向參與者發送commit請求之後,發生了局部網絡異常或者在發送commit請求過程中協調者發生了故障,這回導致只有一部分參與者接受到了commit請求。而在這部分參與者接到commit請求之後就會執行commit操作。但是其他部分未接到commit請求的機器則無法執行事務提交。於是整個分佈式系統便出現了數據部一致性的現象。

(4)兩階段提交無法解決的問題

當協調者出錯,同時參與者也出錯時,兩階段無法保證事務執行的完整性。考慮協調者再發出commit消息之後宕機,而唯一接收到這條消息的參與者同時也宕機了。那麼即使協調者通過選舉協議產生了新的協調者,這條事務的狀態也是不確定的,沒人知道事務是否被已經提交。

三階段提交

三階段提交協議在協調者和參與者中都引入超時機制,並且把兩階段提交協議的第一個階段拆分成了兩步:詢問,然後再鎖資源,最後真正提交。

深入理解分佈式事務



(1)三個階段的執行

1.CanCommit階段3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

2.PreCommit階段Coordinator根據Cohort的反應情況來決定是否可以繼續事務的PreCommit操作。根據響應情況,有以下兩種可能。A.假如Coordinator從所有的Cohort獲得的反饋都是Yes響應,那麼就會進行事務的預執行:發送預提交請求。Coordinator向Cohort發送PreCommit請求,並進入Prepared階段。事務預提交。Cohort接收到PreCommit請求後,會執行事務操作,並將undo和redo信息記錄到事務日誌中。響應反饋。如果Cohort成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

B.假如有任何一個Cohort向Coordinator發送了No響應,或者等待超時之後,Coordinator都沒有接到Cohort的響應,那麼就中斷事務:發送中斷請求。Coordinator向所有Cohort發送abort請求。中斷事務。Cohort收到來自Coordinator的abort請求之後(或超時之後,仍未收到Cohort的請求),執行事務的中斷。

3.DoCommit階段

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

執行提交

A.發送提交請求。Coordinator接收到Cohort發送的ACK響應,那麼他將從預提交狀態進入到提交狀態。並向所有Cohort發送doCommit請求。B.事務提交。Cohort接收到doCommit請求之後,執行正式的事務提交。並在完成事務提交之後釋放所有事務資源。C.響應反饋。事務提交完之後,向Coordinator發送ACK響應。D.完成事務。Coordinator接收到所有Cohort的ACK響應之後,完成事務。

中斷事務

Coordinator沒有接收到Cohort發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那麼就會執行中斷事務。

(2)三階段提交協議和兩階段提交協議的不同

對於協調者(Coordinator)和參與者(Cohort)都設置了超時機制(在2PC中,只有協調者擁有超時機制,即如果在一定時間內沒有收到cohort的消息則默認失敗)。在2PC的準備階段和提交階段之間,插入預提交階段,使3PC擁有CanCommit、PreCommit、DoCommit三個階段。PreCommit是一個緩衝,保證了在最後提交階段之前各參與節點的狀態是一致的。

(2)三階段提交協議的缺點

如果進入PreCommit後,Coordinator發出的是abort請求,假設只有一個Cohort收到並進行了abort操作,而其他對於系統狀態未知的Cohort會根據3PC選擇繼續Commit,此時系統狀態發生不一致性。

分佈式事務的解決方案

分佈式事務的解決方案有如下幾種:

  • 全局事務(DTP模型)
  • 基於可靠消息服務的分佈式事務
  • 最大努力通知
  • TCC


全局事務(DTP模型)

全局事務基於DTP模型實現。DTP是由X/Open組織提出的一種分佈式事務模型——X/Open Distributed Transaction Processing Reference Model。它規定了要實現分佈式事務,需要三種角色:

1、AP:Application 應用系統 它就是我們開發的業務系統,在我們開發的過程中,可以使用資源管理器提供的事務接口來實現分佈式事務。

2、TM:Transaction Manager 事務管理器

分佈式事務的實現由事務管理器來完成,它會提供分佈式事務的操作接口供我們的業務系統調用。這些接口稱為TX接口。事務管理器還管理著所有的資源管理器,通過它們提供的XA接口來同一調度這些資源管理器,以實現分佈式事務。DTP只是一套實現分佈式事務的規範,並沒有定義具體如何實現分佈式事務,TM可以採用2PC、3PC、Paxos等協議實現分佈式事務。3、RM:Resource Manager 資源管理器

能夠提供數據服務的對象都可以是資源管理器,比如:數據庫、消息中間件、緩存等。大部分場景下,數據庫即為分佈式事務中的資源管理器。資源管理器能夠提供單數據庫的事務能力,它們通過XA接口,將本數據庫的提交、回滾等能力提供給事務管理器調用,以幫助事務管理器實現分佈式的事務管理。XA是DTP模型定義的接口,用於向事務管理器提供該資源管理器(該數據庫)的提交、回滾等能力。DTP只是一套實現分佈式事務的規範,RM具體的實現是由數據庫廠商來完成的。

基於可靠消息服務的分佈式事務

這種實現分佈式事務的方式需要通過消息中間件來實現。假設有A和B兩個系統,分別可以處理任務A和任務B。此時系統A中存在一個業務流程,需要將任務A和任務B在同一個事務中處理。下面來介紹基於消息中間件來實現這種分佈式事務。


深入理解分佈式事務


1.在系統A處理任務A前,首先向消息中間件發送一條消息2.消息中間件收到後將該條消息持久化,但並不投遞。此時下游系統B仍然不知道該條消息的存在。3.消息中間件持久化成功後,便向系統A返回一個確認應答;4.系統A收到確認應答後,則可以開始處理任務A;5.任務A處理完成後,向消息中間件發送Commit請求。該請求發送完成後,對系統A而言,該事務的處理過程就結束了,此時它可以處理別的任務了。 6.但commit消息可能會在傳輸途中丟失,從而消息中間件並不會向系統B投遞這條消息,從而系統就會出現不一致性。這個問題由消息中間件的事務回查機制完成,下文會介紹。7.消息中間件收到Commit指令後,便向系統B投遞該消息,從而觸發任務B的執行;8.當任務B執行完成後,系統B向消息中間件返回一個確認應答,告訴消息中間件該消息已經成功消費,此時,這個分佈式事務完成。

上述過程可以得出如下幾個結論:

  1. 消息中間件扮演者分佈式事務協調者的角色。
  2. 系統A完成任務A後,到任務B執行完成之間,會存在一定的時間差。在這個時間差內,整個系統處於數據不一致的狀態,但這短暫的不一致性是可以接受的,因為經過短暫的時間後,系統又可以保持數據一致性,滿足BASE理論。


上述過程中,如果任務A處理失敗,那麼需要進入回滾流程,如下圖所示:


深入理解分佈式事務



1.若系統A在處理任務A時失敗,那麼就會向消息中間件發送Rollback請求。和發送Commit請求一樣,系統A發完之後便可以認為回滾已經完成,它便可以去做其他的事情。

2.消息中間件收到回滾請求後,直接將該消息丟棄,而不投遞給系統B,從而不會觸發系統B的任務B。

此時系統又處於一致性狀態,因為任務A和任務B都沒有執行。


上面所介紹的Commit和Rollback都屬於理想情況,但在實際系統中,Commit和Rollback指令都有可能在傳輸途中丟失。那麼當出現這種情況的時候,消息中間件是如何保證數據一致性呢?——答案就是超時詢問機制。


深入理解分佈式事務



系統A除了實現正常的業務流程外,還需提供一個事務詢問的接口,供消息中間件調用。當消息中間件收到一條事務型消息後便開始計時,如果到了超時時間也沒收到系統A發來的Commit或Rollback指令的話,就會主動調用系統A提供的事務詢問接口詢問該系統目前的狀態。該接口會返回三種結果:

1.提交 若獲得的狀態是“提交”,則將該消息投遞給系統B。2.回滾 若獲得的狀態是“回滾”,則直接將條消息丟棄。3.處理中 若獲得的狀態是“處理中”,則繼續等待。

消息中間件的超時詢問機制能夠防止上游系統因在傳輸過程中丟失Commit/Rollback指令而導致的系統不一致情況,而且能降低上游系統的阻塞時間,上游系統只要發出Commit/Rollback指令後便可以處理其他任務,無需等待確認應答。而Commit/Rollback指令丟失的情況通過超時詢問機制來彌補,這樣大大降低上游系統的阻塞時間,提升系統的併發度。

下面來說一說消息投遞過程的可靠性保證。 當上遊系統執行完任務並向消息中間件提交了Commit指令後,便可以處理其他任務了,此時它可以認為事務已經完成,接下來消息中間件一定會保證消息被下游系統成功消費掉!那麼這是怎麼做到的呢?這由消息中間件的投遞流程來保證。

消息中間件向下遊系統投遞完消息後便進入阻塞等待狀態,下游系統便立即進行任務的處理,任務處理完成後便向消息中間件返回應答。消息中間件收到確認應答後便認為該事務處理完畢!

如果消息在投遞過程中丟失,或消息的確認應答在返回途中丟失,那麼消息中間件在等待確認應答超時之後就會重新投遞,直到下游消費者返回消費成功響應為止。當然,一般消息中間件可以設置消息重試的次數和時間間隔,比如:當第一次投遞失敗後,每隔五分鐘重試一次,一共重試3次。如果重試3次之後仍然投遞失敗,那麼這條消息就需要人工干預。


深入理解分佈式事務


深入理解分佈式事務



有的同學可能要問:消息投遞失敗後為什麼不回滾消息,而是不斷嘗試重新投遞?

這就涉及到整套分佈式事務系統的實現成本問題。 我們知道,當系統A將向消息中間件發送Commit指令後,它便去做別的事情了。如果此時消息投遞失敗,需要回滾的話,就需要讓系統A事先提供回滾接口,這無疑增加了額外的開發成本,業務系統的複雜度也將提高。對於一個業務系統的設計目標是,在保證性能的前提下,最大限度地降低系統複雜度,從而能夠降低系統的運維成本。

不知大家是否發現,上游系統A向消息中間件提交Commit/Rollback消息採用的是異步方式,也就是當上遊系統提交完消息後便可以去做別的事情,接下來提交、回滾就完全交給消息中間件來完成,並且完全信任消息中間件,認為它一定能正確地完成事務的提交或回滾。然而,消息中間件向下遊系統投遞消息的過程是同步的。也就是消息中間件將消息投遞給下游系統後,它會阻塞等待,等下游系統成功處理完任務返回確認應答後才取消阻塞等待。為什麼這兩者在設計上是不一致的呢?

首先,上游系統和消息中間件之間採用異步通信是為了提高系統併發度。業務系統直接和用戶打交道,用戶體驗尤為重要,因此這種異步通信方式能夠極大程度地降低用戶等待時間。此外,異步通信相對於同步通信而言,沒有了長時間的阻塞等待,因此係統的併發性也大大增加。但異步通信可能會引起Commit/Rollback指令丟失的問題,這就由消息中間件的超時詢問機制來彌補。

那麼,消息中間件和下游系統之間為什麼要採用同步通信呢?

異步能提升系統性能,但隨之會增加系統複雜度;而同步雖然降低系統併發度,但實現成本較低。因此,在對併發度要求不是很高的情況下,或者服務器資源較為充裕的情況下,我們可以選擇同步來降低系統的複雜度。 我們知道,消息中間件是一個獨立於業務系統的第三方中間件,它不和任何業務系統產生直接的耦合,它也不和用戶產生直接的關聯,它一般部署在獨立的服務器集群上,具有良好的可擴展性,所以不必太過於擔心它的性能,如果處理速度無法滿足我們的要求,可以增加機器來解決。而且,即使消息中間件處理速度有一定的延遲那也是可以接受的,因為前面所介紹的BASE理論就告訴我們了,我們追求的是最終一致性,而非實時一致性,因此消息中間件產生的時延導致事務短暫的不一致是可以接受的。

最大努力通知

最大努力通知方案主要也是藉助MQ消息系統來進行事務控制,這一點與可靠消息最終一致方案一樣。看來MQ中間件確實在一個分佈式系統架構中,扮演者重要的角色。最大努力通知方案是比較簡單的分佈式事務方案,它本質上就是通過定期校對,實現數據一致性。

實現:

1.主要由業務活動的主動方,在完成相關業務處理之後,向業務活動的被動方發送消息;消息允許丟失。

2.主動方可以設置時間階梯型的通知規則,使通知效率達到最高;在通知N次之後就不再通知,需要人工介入。

3.業務活動的被動方根據定時的策略,向業務活動的主動方進行輪詢,進而恢復丟失的業務消息;這裡注意被動方還是需要實現業務冪等的; 冪等實現的幾種方式:一種基於某些業務規則進行判斷,根據業務主鍵實現冪等;第二張是本地使用一張消息記錄表記錄消息的消費情況,技術層面實現冪等。


深入理解分佈式事務


TCC

關於TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland於2007年發表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。在該論文中,TCC還是以Tentative-Confirmation-Cancellation作為名稱;正式以Try-Confirm-Cancel作為名稱的,可能是Atomikos(Gregor Hohpe所著書籍《Enterprise Integration Patterns》中收錄了關於TCC的介紹,提到了Atomikos的Try-Confirm-Cancel,並認為二者是相似的概念)。

國內最早關於TCC的報道,應該是InfoQ上對阿里程立博士的一篇採訪。經過程博士的這一次傳道之後,TCC在國內逐漸被大家廣為了解並接受。相應的實現方案和開源框架也先後被髮布出來,ByteTCC就是其中之一。

TCC事務機制相對於傳統事務機制(X/Open XA),其特徵在於它不依賴資源管理器(RM)對XA的支持,而是通過對(由業務系統提供的)業務邏輯的調度來實現分佈式事務。對於業務系統中一個特定的業務邏輯S,其對外提供服務時,必須接受一些不確定性,即對業務邏輯執行的一次調用僅是一個臨時性操作,調用它的消費方服務M保留了後續的取消權。如果M認為全局事務應該rollback,它會要求取消之前的臨時性操作,這就對應S的一個取消操作。而當M認為全局事務應該commit時,它會放棄之前臨時性操作的取消權,這對應S的一個確認操作。 每一個初步操作,最終都會被確認或取消。因此,針對一個具體的業務服務,TCC事務機制需要業務系統提供三段業務邏輯:初步操作Try、確認操作Confirm、取消操作Cancel。

1. 初步操作(Try)TCC事務機制中的業務邏輯(Try),從執行階段來看,與傳統事務機制中業務邏輯相同。但從業務角度來看,是不一樣的。TCC機制中的Try僅是一個初步操作,它和後續的次確認一起才能真正構成一個完整的業務邏輯。因此,可以認為[傳統事務機制]的業務邏輯 = [TCC事務機制]的初步操作(Try) + [TCC事務機制]的確認邏輯(Confirm)。TCC機制將傳統事務機制中的業務邏輯一分為二,拆分後保留的部分即為初步操作(Try);而分離出的部分即為確認操作(Confirm),被延遲到事務提交階段執行。

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

2. 確認操作(Confirm)確認操作(Confirm)是對初步操作(Try)的一個補充。當TCC事務管理器認為全局事務可以正確提交時,就會逐個執行初步操作(Try)指定的確認操作(Confirm),將初步操作(Try)未完成的事項最終完成。

3. 取消操作(Cancel)取消操作(Cancel)是對初步操作(Try)的一個回撤。當TCC事務管理器認為全局事務不能正確提交時,就會逐個執行初步操作(Try)指定的取消操作(Cancel),將初步操作(Try)已完成的事項全部撤回。

在傳統事務機制中,業務邏輯的執行和事務的處理,是在不同的階段由不同的部件來處理的:業務邏輯部分訪問資源實現數據存儲,其處理是由業務系統負責;事務處理部分通過協調資源管理器以實現事務管理,其處理由事務管理器來負責。二者沒有太多交互的地方,所以,傳統事務管理器的事務處理邏輯,僅需要著眼於事務完成(commit/rollback)階段,而不必關注業務執行階段。而在TCC事務機制中的業務邏輯和事務處理,其關係就錯綜複雜:業務邏輯(Try/Confirm/Cancel)階段涉及所參與資源事務的commit/rollback;全局事務commit/rollback時又涉及到業務邏輯(Try/Confirm/Cancel)的執行。

TCC全局事務必須基於RM本地事務來實現全局事務

TCC服務是由Try/Confirm/Cancel業務構成的, 其Try/Confirm/Cancel業務在執行時,會訪問資源管理器(Resource Manager,下文簡稱RM)來存取數據。這些存取操作,必須要參與RM本地事務,以使其更改的數據要麼都commit,要麼都rollback。

這一點不難理解,考慮一下如下場景:


深入理解分佈式事務



假設圖中的服務B沒有基於RM本地事務(以RDBS為例,可通過設置auto-commit為true來模擬),那麼一旦[B:Try]操作中途執行失敗,TCC事務框架後續決定回滾全局事務時,該[B:Cancel]則需要判斷[B:Try]中哪些操作已經寫到DB、哪些操作還沒有寫到DB:假設[B:Try]業務有5個寫庫操作,[B:Cancel]業務則需要逐個判斷這5個操作是否生效,並將生效的操作執行反向操作。

不幸的是,由於[B:Cancel]業務也有n(0<=n<=5)個反向的寫庫操作,此時一旦[B:Cancel]也中途出錯,則後續的[B:Cancel]執行任務更加繁重。因為,相比第一次[B:Cancel]操作,後續的[B:Cancel]操作還需要判斷先前的[B:Cancel]操作的n(0<=n<=5)個寫庫中哪幾個已經執行、哪幾個還沒有執行,這就涉及到了冪等性問題。而對冪等性的保障,又很可能還需要涉及額外的寫庫操作,該寫庫操作又會因為沒有RM本地事務的支持而存在類似問題。。。可想而知,如果不基於RM本地事務,TCC事務框架是無法有效的管理TCC全局事務的。

反之,基於RM本地事務的TCC事務,這種情況則會很容易處理:[B:Try]操作中途執行失敗,TCC事務框架將其參與RM本地事務直接rollback即可。後續TCC事務框架決定回滾全局事務時,在知道“[B:Try]操作涉及的RM本地事務已經rollback”的情況下,根本無需執行[B:Cancel]操作。

換句話說,基於RM本地事務實現TCC事務框架時,一個TCC型服務的cancel業務要麼執行,要麼不執行,不需要考慮部分執行的情況。

TCC事務框架應該提供Confirm/Cancel服務的冪等性保障

一般認為,服務的冪等性,是指針對同一個服務的多次(n>1)請求和對它的單次(n=1)請求,二者具有相同的副作用。

在TCC事務模型中,Confirm/Cancel業務可能會被重複調用,其原因很多。比如,全局事務在提交/回滾時會調用各TCC服務的Confirm/Cancel業務邏輯。執行這些Confirm/Cancel業務時,可能會出現如網絡中斷的故障而使得全局事務不能完成。因此,故障恢復機制後續仍然會重新提交/回滾這些未完成的全局事務,這樣就會再次調用參與該全局事務的各TCC服務的Confirm/Cancel業務邏輯。

既然Confirm/Cancel業務可能會被多次調用,就需要保障其冪等性。 那麼,應該由TCC事務框架來提供冪等性保障?還是應該由業務系統自行來保障冪等性呢? 個人認為,應該是由TCC事務框架來提供冪等性保障。如果僅僅只是極個別服務存在這個問題的話,那麼由業務系統來負責也是可以的;然而,這是一類公共問題,毫無疑問,所有TCC服務的Confirm/Cancel業務存在冪等性問題。TCC服務的公共問題應該由TCC事務框架來解決;而且,考慮一下由業務系統來負責冪等性需要考慮的問題,就會發現,這無疑增大了業務系統的複雜度。


分享到:


相關文章: