分佈式事務方案整合

分佈式事務方案整合

經過幾天的資料查找,對解決分佈式事務的方法有兩階段提交、支付寶分享的TCCtry-confirm-cancel)和基於消息的最終一致解決方案,其中第一條和第二條雖然也能解決問題,但廣泛對第三種基於消息隊列的最終一致解決方案推薦多比較高,因此第一條和第二條能夠參考使用。java

1、設計原則

業務拆分,架構設計時應「儘可能避免」分佈式事務,若是實在避免不了(這已是高併發、用戶量比較多的網站了)則使用「最終一致性」處理。git

2、設計原理

2.1 CAP原理

  C(一致性)一致性是指數據的原子性,在經典的數據庫中經過事務來保障,事務完成時,不管成功或回滾,數據都會處於一致的狀態,在分佈式環境下,一致性是指多個節點數據是否一致;github

  A(可用性)服務一直保持可用的狀態,當用戶發出一個請求,服務能在必定的時間內返回結果;數據庫

  P(分區容忍性)在分佈式應用中,可能由於一些分佈式的緣由致使系統沒法運轉,好的分區容忍性,使應用雖然是一個分佈式系統,可是好像一個能夠正常運轉的總體架構

2.2 BASE原理

  1. BA: Basic Availability 基本業務可用性;併發

  2. S: Soft state 柔性狀態,中間狀態;app

  3. E: Eventual consistency 最終一致性;框架

3、設計方案

3.1 兩階段提交

兩階段提交分爲準備階段和提交階段兩個階段,其原理架構圖以下:分佈式

3-1高併發

概述:在提交事務的過程當中須要在多個節點之間進行協調,而各節點對鎖資源的釋放必須等到事務最終提交時,比較耗時,鎖資源發生衝突的機率增長,當事務的併發量達到必定數量的時候,就會出現大量事務積壓甚至出現死鎖,系統性能就會嚴重下滑。

3.2 TCCTry-Confirm-Cancel

TCC分三部分,以下所述:

1. Try: 嘗試執行業務;

2. Confirm: 確認執行業務;

3. Cancel: 取消執行業務;

此方案開始由支付寶提出來的一種方案,在開源社區github上找到一個TCC型事務java實現(https://github.com/changmingxie/tcc-transaction),部署到本機環境測試了一下demo,能夠實現分佈式事務補償機制,因爲時間倉促,尚未來得及研究源碼。

優勢:現成的框架時間,使用不難,有開發使用文檔,能夠爲咱們之後設計分佈式事務時提供一種設計思路和參考。

缺點:不是很成熟,關注度不是很高,可能會存在一些問題。

3.3 基於事務型消息隊列的最終一致性

藉助消息隊列,在處理業務邏輯的地方,發送消息,業務邏輯處理成功後,提交消息,確保消息是發送成功的,以後消息隊列投遞來進行處理,若是成功,則結束,若是沒有成功,則重試,直到成功,不過僅僅適用業務邏輯中,第一階段成功,第二階段必須成功的場景。架構圖以下C流程:

 

圖3-3

3.4 基於消息隊列+定時補償機制的最終一致性

前面部分和上面基於事務型消息的隊列,不一樣的是,第二階段重試的地方,再也不是消息中間件自身的重試邏輯了,而是單獨的補償任務機制。其實在大多數的邏輯中,第二階段失敗的機率比較小,因此單獨獨立補償任務表出來,能夠更加清晰,可以比較明確的直到當前多少任務是失敗的。對應上圖的E流程。

至於如何實現冪等性操做,能夠經過增長一個message_applied(msg_id)記錄被成功應用的消息,在事務完成以後刪除記錄便可。

 

舉個例子。假設系統中有如下兩個表

  user(id, name, amt_sold, amt_bought)

  transaction(xid, seller_id, buyer_id, amount)

其中user表記錄用戶交易彙總信息,transaction表記錄每一個交易的詳細信息。

 

這樣,在進行一筆交易時,若使用事務,就須要對數據庫進行如下操做:

begin;

  INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);

  UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id;

  UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;

commit;

即在transaction表中記錄交易信息,而後更新賣家和買家的狀態。假設transaction表和user表存儲在不一樣的節點上,那麼上述事務就是一個分佈式事務

則使用基於消息隊列的最終一致解決方案的僞代碼以下所示:

第一步:

begin;

    INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);

   put_to_queue "update user("seller", $seller_id, amount);

   put_to_queue "update user("buyer", $buyer_id, amount);

commit;

第二步:

for each message in queue

  begin;

    SELECT count(*) as cnt FROM message_applied WHERE msg_id = message.id;

    if cnt = 0 then

      if message.type = "seller" then

        UPDATE user SET amt_sold = amt_sold + message.amount WHERE id = message.user_id;

      else

        UPDATE user SET amt_bought = amt_bought + message.amount WHERE id = message.user_id;

      end

      INSERT INTO message_applied VALUES(message.id);

    end

  commit;

第三步:

  if 上述事務成功

    dequeue message

    DELETE FROM message_applied WHERE msg_id = message.id;

  end

end

 

4、最佳實踐

根據不一樣的業務場景對一致性、可用性和分區容忍性的要求不一樣,在此三者間尋找一個最佳的平衡點來知足不一樣的業務場景,能夠根據以上方案靈活選擇,不侷限於某一種方案。

相關文章
相關標籤/搜索