分佈式事務解決方案

見於:https://blog.csdn.net/mine_song/article/details/64118963

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

BASE是下面三個術語的縮寫:
基本可用(Basically Available)
軟狀態(Soft state)
最終一致(Eventually consistent)

根據cap理論,分佈式事務 acid 是很難保證的。一般做法是犧牲一致性,滿足可用性和分區容錯。
分佈式事務可以基於 mq/rpc 實現最終一致性,但在分庫分表,立即可見的應用上是不能滿足業務需求的,故分佈式事務還是有必要完善的。
BASE理論是對CAP中的一致性和可用性進行一個權衡的結果,理論的核心思想就是:我們無法做到強一致,但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

分佈式事務服務(Distributed Transaction Service,DTS)是一個分佈式事務框架,用來保障在大規模分佈式環境下事務的最終一致性。CAP理論告訴我們在分佈式存儲系統中,最多隻能實現上面的兩點。而由於當前的網絡硬件肯定會出現延遲丟包等問題,所以分區容忍性是我們必須需要實現的,所以我們只能在一致性和可用性之間進行權衡。爲了保障系統的可用性,互聯網系統大多將強一致性需求轉換成最終一致性的需求,並通過系統執行冪等性的保證,保證數據的最終一致性。數據一致性理解:

        強一致性:當更新操作完成之後,任何多個後續進程或者線程的訪問都會返回最新的更新過的值。這種是對用戶最友好的,就是用戶上一次寫什麼,下一次就保證能讀到什麼。根據 CAP 理論,這種實現需要犧牲可用性。

        弱一致性:系統並不保證後續進程或者線程的訪問都會返回最新的更新過的值。系統在數據寫入成功之後,不承諾立即可以讀到最新寫入的值,也不會具體的承諾多久之後可以讀到。

        最終一致性:弱一致性的特定形式。系統保證在沒有後續更新的前提下,系統最終返回上一次更新操作的值。在沒有故障發生的前提下,不一致窗口的時間主要受通信延遲,系統負載和複製副本的個數影響。DNS 是一個典型的最終一致性系統。

        在分佈式系統中各個節點在物理上都是相對獨立的,獨立每一臺節點數據操作都可以滿足ACID。但是,各獨立節點之間無法知道其他節點事務的執行情況,如果要想讓分多臺機器中數據保存一致,就必須保證所有節點上面的數據操作要麼全部執行成功,要麼全部不執行,比較常規的解決方法是引入“協調者”來統一調度所有節點執行

1 基於消息的最終一致性

在這裏首先要回答的是我們需要時實時一致性還是最終一致性的問題,如果需要的是最終一致性,那麼BASE策略中的基於消息的最終一致性是比較好的解決方案。這種方案真正實現了兩個服務的真正解耦,解耦的關鍵就是異步消息和消息持久化機制。

還是以上面的例子來看。對於轉賬操作,原有的兩個服務調用變化爲第一步調用本地的取款服務,第二步發送異地取款的異步消息到消息中間件。如果第二步在本地,則保證事務的完整性基本無任何問題,即本身就是本地事務的管理機制。只要兩個操作都成功即可以返回客戶成功。

由於解耦,我們看到客戶得到成功返回的時候,如果是上面一種情況則異地卡馬上就能查詢賬戶存款增加。而第二種情況則不一定,因爲本身是一種異步處理機制。消息中間件得到消息後會去對消息解析,然後調用異地銀行提供的存款服務進行存款,如果服務調用失敗則進行重試。

異地銀行存款操作不應該長久地出現異常而無法使用,因此一旦發現異常我們可以迅速的解決,消息中間件中異常服務自然會進行重試以保證事務的最終一致性。這種方式假設問題一定可以解決,在不到萬不得已的情況下本地的取款服務一般不進行可逆操作。

在本地取款到異地存款兩個服務調用之間,會存在一個真空期,這段時間相關現金不在任何一個賬戶,而只是在一個事務的中間狀態,但是客戶並不關心這個,只要在約定的時間保證事務最終的一致性即可。

XA規範
        X/Open組織(即現在的Open Group)定義了分佈式事務處理模型。X/Open DTP模型包括應用程序(AP)、事務管理器(TM)、資源管理器(RM)、通信資源管理器(CRM)四部分。事務管理器(TM)是交易中間件,資源管理器(RM)是數據庫,通信資源管理器(CRM)是消息中間件。通常把一個數據庫內部的事務處理作爲本地事務看待,而分佈式事務處理的對象是全局事務。所謂全局事務,是指分佈式事務處理環境中,多個數據庫可能需要共同完成一個工作,這個工作即是一個全局事務,一個事務中可能更新幾個不同的數據庫,此時一個數據庫對自己內部所做操作的提交不僅依賴本身操作是否成功,還要依賴與全局事務相關的其它數據庫的操作是否成功,如果任一數據庫的任一操作失敗,則參與此事務的所有數據庫所做的所有操作都必須回滾。XA就是X/Open DTP 定義的交易中間件與數據庫之間的接口規範(即接口函數),交易中間件用它來通知數據庫事務的開始、結束以及提交、回滾等,XA接口函數由數據庫廠商提供,根據這一思想衍生出二階提交協議和三階提交協議。

二階段提交
所謂的兩個階段是指:準備階段、提交階段。

準備階段:事務協調者(事務管理器)給每個參與者(資源管理器)發送準備消息,每個參與者要麼直接返回失敗(如權限驗證失敗),要麼在本地執行事務,寫本地的redo和undo日誌但不提交,可以進一步將準備階段分爲以下三個步驟:

1)協調者節點向所有參與者節點詢問是否可以執行提交操作(do),並開始等待各參與者節點的響應。

2)參與者節點執行詢問發起爲止的所有事務操作,並將Undo信息和Redo信息寫入日誌。

3)各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個”同意”消息;如果參與者節點的事務操作實際執行失敗,則它返回一個”中止”消息。

提交階段:如果協調者收到了參與者的失敗消息或者超時,直接給每個參與者發送回滾(Rollback)消息,否則發送提交(Commit)消息,參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源。

二階段提交所存在缺點的:

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

2)單點故障,由於協調者的重要性一旦協調者發生故障參與者會一直阻塞下去。

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

由於二階段提交存在着諸如同步阻塞、單點問題、數據不一致、宕機等缺陷,所以,研究者們在二階段提交的基礎上做了改進,提出了三階段提交。

三階段提交
三階段提交(Three-phase commit),也叫三階段提交協議(Three-phase commit protocol),是二階段提交(2PC)的改進版本。3PC把2PC的準備階段再次一分爲二,這樣三階段提交就有CanCommit、PreCommit、DoCommit三個階段。

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

個人覺得沒有什麼改進。。。根本上無法解決網絡腦裂,數據不一致的情況。

Mycat中實現
Mycat可以通過用戶會話Session中設置autocommit=false啓動事務,通過設置ServerConnection中變量txInterrupted=true來控制是否事務異常需要回滾。在Mycat中的事務是一種二階段提交事務方式,但是從實際應用場景出發這種出現故障的概率還是比較小的,因此這種實現方式可以滿足很多應用需求,但如果出現問題,會很麻煩。

JTA
補償事務(TCC)
TCC 其實就是採用的補償機制,其核心思想是:針對每個操作,都要註冊一個與其對應的確認和補償(撤銷)操作。它分爲三個階段:

1、Try 階段主要是對業務系統做檢測及資源預留
2、Confirm 階段主要是對業務系統做確認提交,Try階段執行成功並開始執行 Confirm階段時,默認 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。
3、Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放。

舉個例子,假入 Bob 要向 Smith 轉賬100,思路大概是:
我們有一個本地方法,裏面依次調用
1、首先在 Try 階段,要先調用遠程接口把 Smith 凍結金額+100和 Bob 凍結金額+100,可用金額-100。
2、在 Confirm 階段,Smith解凍出賬,Bob解凍加款。
3、如果第2步執行成功,那麼轉賬成功,如果第二步執行失敗,則調用遠程凍結接口對應的解凍還原。

優點: 跟2PC比起來,實現以及流程相對簡單了一些,但數據的一致性比2PC也要差一些

缺點: 缺點還是比較明顯的,在2,3步中都有可能失敗。TCC屬於應用層的一種補償方式,所以需要程序員在實現的時候多寫很多補償的代碼,在一些場景中,一些業務流程可能用TCC不太好定義及處理。

本地消息表(異步確保)
本地消息表這種實現方式應該是業界使用最多的,其核心思想是將分佈式事務拆分成本地事務進行處理,這種思路是來源於ebay。我們可以從下面的流程圖中看出其中的一些細節:
基本思路就是:

消息生產方,需要額外建一個消息表,並記錄消息發送狀態。消息表和業務數據要在一個事務裏提交,也就是說他們要在一個數據庫裏面。然後消息會經過MQ發送到消息的消費方。如果消息發送失敗,會進行重試發送。

消息消費方,需要處理這個消息,並完成自己的業務邏輯。此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那麼就會重試執行。如果是業務上面的失敗,可以給生產方發送一個業務補償消息,通知生產方進行回滾等操作。

生產方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

這種方案遵循BASE理論,採用的是最終一致性,筆者認爲是這幾種方案裏面比較適合實際業務場景的,即不會出現像2PC那樣複雜的實現(當調用鏈很長的時候,2PC的可用性是非常低的),也不會像TCC那樣可能出現確認或者回滾不了的情況。

優點: 一種非常經典的實現,避免了分佈式事務,實現了最終一致性。
缺點: 消息表會耦合到業務系統中,如果沒有封裝好的解決方案,會有很多雜活需要處理。

MQ 事務消息
有一些第三方的MQ是支持事務消息的,比如RocketMQ,他們支持事務消息的方式也是類似於採用的二階段提交,但是市面上一些主流的MQ都是不支持事務消息的,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中間件爲例,其思路大致爲:

第一階段Prepared消息,會拿到消息的地址。
第二階段執行本地事務,第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。

也就是說在業務方法內要向消息隊列提交兩次請求,一次提交消息和一次確認消息。如果確認消息發送失敗了RocketMQ會定期掃描消息集羣中的事務消息,這時候發現了Prepared消息,它會向消息發送者確認,所以生產方需要實現一個check接口,RocketMQ會根據發送端設置的策略來決定是回滾還是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章