1. 程式人生 > >分布式系統事務一致性

分布式系統事務一致性

自身 roc 原理 會有 異步消息 body htm 可見 pre

單數據庫一致性:

1. 利用事務

分布式系統事務一致性:

1. 本地事務消息隊列:兩段提交,利用本地事務保證消息的可靠性

生產者:

1). 在數據庫(mysql)增加一個消息表,將本地數據修改和消息記錄放到同一個事務中,保證同時成功或失敗。

2). 本地數據修改成功後,事務提交完畢。producer向MQ發送一個消息,發送成功,更新消息表消息為已讀

3). 為避免producer消息發送失敗的情況。有一個定時任務,定時查詢消息表,將未消費的消息放到MQ的消息隊列中

消費者:

1). consumer從MQ隊列中,取出消息去消費。

2). 先查詢這條消息對應的數據是否已被修改,如果已被修改則將消息置為無效。如果消息未被消費,則將消息消費掉,數據更新成功以後,將消息置為已消費。

3). 對於MQ消息量大、有順序要求的數據,可以通過 特性算法(hash)將制定下消息放到同一個隊列中,對應一個 消費者。

總結:

這方式是一種非常經典的實現,基本避免了分布式事務,實現了“最終一致性”。但是,關系型數據庫的吞吐量和性能方面存在瓶頸,頻繁的讀寫消息會給數據庫造成壓力。

所以,在真正的高並發場景下,該方案也會有瓶頸和限制的。

2. MQ(非事務消息)

生產者(數據庫修改和發送消息操作在一個事務中,發送消息操作可以通過異常可以進行數據回滾):

1). 操作數據庫成功,向MQ中投遞消息也成功,皆大歡喜

2). 操作數據庫失敗,不會向MQ中投遞消息了

3). 操作數據庫成功,但是向MQ中投遞消息時失敗,向外拋出了異常,剛剛執行的更新數據庫的操作將被回滾

消費者端面臨的問題:

1). 消息出列後,消費者對應的業務操作要執行成功。如果業務執行失敗,消息不能失效或者丟失。需要保證消息與業務操作一致

2). 盡量避免消息重復消費。如果重復消費,也不能因此影響業務結果

風險點:

如果生產段(假設為A),A對數據庫修改完成,發送MQ消息,但是事務還沒有提交。消費者可能已經開始消費MQ裏面的消息,如果有用到A端的數據,可能查不到。如果要保證流程的嚴謹性,

應該保證消費端的執行,對應A端數據庫提交的內容沒有依賴。

總結:

這種方式比較常見,性能和吞吐量是優於使用關系型數據庫消息表的方案。如果MQ自身和業務都具有高可用性,理論上是可以滿足大部分的業務場景的。不過在沒有充分測試的情況下,不建議在交易業務中直接使用。


3. MQ(事務消息)

下面以阿裏巴巴的RocketMQ中間件為例,分析下其設計和實現思路。

RocketMQ第一階段發送Prepared消息時,會拿到消息的地址,第二階段執行本地事物,第三階段通過第一階段拿到的地址去訪問消息,並修改狀態。細心的讀者可能又發現問題了,

如果確認消息發送失敗了怎麽辦?RocketMQ會定期掃描消息集群中的事物消息,這時候發現了Prepared消息,它會向消息發送者確認,Bob的錢到底是減了還是沒減呢?如果減了是

回滾還是繼續發送確認消息呢?RocketMQ會根據發送端設置的策略來決定是回滾還是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。

發送端設置的策略 可以通過實現 指定的接口,由業務方自己來實現。

總結:

據筆者的了解,各大知名的電商平臺和互聯網公司,幾乎都是采用類似的設計思路來實現“最終一致性”的。這種方式適合的業務場景廣泛,而且比較可靠。不過這種方式技術實現的難度比較大。目前

主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實現對事務消息的支持,所以需二次開發或者新造輪子。比較遺憾的是,RocketMQ事務消息部分的代碼也並未開源,需要自己去實現。

4. 補償機制(重試補償機制:調用超時,服務短暫宕機等):

做過支付寶交易接口的同學都知道,我們一般會在支付寶的回調頁面和接口裏,解密參數,然後調用系統中更新交易狀態相關的服務,將訂單更新為付款成功。同時,只有當我們回調頁面中輸出了success字樣

或者標識業務處理成功相應狀態碼時,支付寶才會停止回調請求。否則,支付寶會每間隔一段時間後,再向客戶方發起回調請求,直到輸出成功標識為止。

其實這就是一個很典型的補償例子,跟一些MQ重試補償機制很類似。

5. 人工補償機制:

為了交易系統更可靠,我們一般會在類似交易這種高級別的服務代碼中,加入詳細日誌記錄的,一旦系統內部引發類似致命異常,會有郵件通知。同時,後臺會有定時任務掃描和分析此類日誌,檢查出

這種特殊的情況,會嘗試通過程序來補償並郵件通知相關人員。在某些特殊的情況下,還會有“人工補償”的,這也是最後一道屏障。

如何避免消息被重復消費造成的問題?

1). 保證消費者調用業務的服務接口的冪等性

2). 通過消費日誌或者類似狀態表來記錄消費狀態,便於判斷(建議在業務上自行實現,而不依賴MQ產品提供該特性)

參見:http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

https://www.zhihu.com/question/27707687 (消息隊列順序)


互聯網系統大多將 強一致性 需求轉換成 最終一致性 的需求,並通過系統執行 冪等性 的保證,保證數據的最終一致性。

2PC(Two Phase Commit,兩階段提交):

第一階段:

1). 協調者會問所有的參與者結點,是否可以執行提交操作。

2). 各個參與者開始事務執行的準備工作:如:為資源上鎖,預留資源,寫undo/redo log……

3). 參與者響應協調者,如果事務的準備工作成功,則回應“可以提交”,否則回應“拒絕提交”。

第二階段:

1). 如果所有的參與者都回應“可以提交”,那麽,協調者向所有的參與者發送“正式提交”的命令。參與者完成正式提交,並釋放所有資源,然後回應“完成”,協調者收集各結點的“完成”回應後結束這個Global Transaction。

2). 如果有一個參與者回應“拒絕提交”,那麽,協調者向所有的參與者發送“回滾操作”,並釋放所有資源,然後回應“回滾完成”,協調者收集各結點的“回滾”回應後,取消這個Global Transaction。

3PC(Three Phase Commit):

1). 三段提交的核心理念是:在詢問的時候並不鎖定資源,除非所有人都同意了,才開始鎖資源。

2). 理論上來說,如果第一階段所有的結點返回成功,那麽有理由相信成功提交的概率很大。這樣一來,可以降低參與者Cohorts的狀態未知的概率。也就是說,一旦參與者收到了PreCommit,意味他知道大家其實都同意修改了。

Two Generals Problem(兩將軍問題):

這個問題是無解的。

從工程上來說,一個解決兩個將軍問題的實際方法是使用一個能夠承受通信信道不可靠性的方案,並不試圖去消除這個不可靠性,但要將不可靠性削減到一個可以接受的程度。比如,第一位將軍排出了100位信使並

預計他們都被捕的可能性很小。

Paxos算法:

簡單說來,Paxos的目的是讓整個集群的結點對某個值的變更達成一致。Paxos算法基本上來說是個民主選舉的算法——大多數的決定會成個整個集群的統一決定。任何一個點都可以提出要修改某個數據的提案,

是否通過這個提案取決於這個集群中是否有超過半數的結點同意(所以Paxos算法需要集群中的結點是單數)。

第一階段:Prepare階段;第二階段:Accept階段

參見:https://coolshell.cn/articles/10910.html

1. eBay 模式:

此方案的核心是將需要分布式處理的任務通過消息日誌的方式來異步執行。消息日誌可以存儲到本地文本、數據庫或消息隊列,再通過業務規則自動或人工發起重試。人工重試

更多的是應用於支付場景,通過對賬系統對事後問題的處理。消息日誌方案的核心是保證服務接口的冪等性。

2. 去哪兒:

1). 異步消息:一種方式是業務邏輯保證冪等;另外一種方式如果業務邏輯無法保證冪等,則要增加一個去重表或者類似的實現。發消息和業務操作在同一個本地事務裏

2). 有的業務不適合異步消息的方式,事務的各個參與方都需要同步的得到結果。每個參與方的本地業務庫的同實例上面放一個事務記錄庫。由一個中心服務對比三方的事務記錄表,

做一個最終決定。假設現在三方的事務記錄是 A 成功,B 失敗,C 成功。那麽最終決定有兩種方式,根據具體場景:

重試 B,直到 B 成功,事務記錄表裏記錄了各項調用參數等信息;

執行 A 和 B 的補償操作(一種可行的補償方式是回滾)。

總結起來,其實兩種方式的根本原理是類似的,也就是將分布式事務轉換為多個本地事務,然後依靠重試等方式達到最終一致性。

3). 那麽可能有人覺得在業務庫的同實例裏放消息庫或事務記錄庫,會對業務侵入,業務還要關心這個庫,是否一個合理的設計?

實際上可以依靠運維的手段來簡化開發的侵入,我們的方法是讓 DBA 在公司所有 MySQL 實例上預初始化這個庫,通過框架層(消息的客戶端或事務 RPC 框架)透明的在背後操作這個庫,

業務開發人員只需要關心自己的業務邏輯,不需要直接訪問這個庫。

3. 蘑菇街:

1). 消息通知往往不能保證 100% 成功;且消息通知後,接收方業務是否能執行成功還是未知數。前者問題可以通過重試解決;後者可以選用事務消息來保證。

2). 我們在交易創建流程中,首先創建一個不可見訂單,然後在同步調用鎖券和扣減庫存時,針對調用異常(失敗或者超時),發出廢單消息到MQ。如果消息發送失敗,本地會做時間階梯式的異步重試;

優惠券系統和庫存系統收到消息後,會進行判斷是否需要做業務回滾,這樣就準實時地保證了多個本地事務的最終一致性。

參見:https://weibo.com/ttarticle/p/show?id=2309403965965003062676

分布式系統事務一致性