分布式事務方案整合
通過幾天的資料查找,對解決分布式事務的方法有兩階段提交、支付寶分享的TCC(try-confirm-cancel)和基於消息的最終一致解決方案,其中第一條和第二條雖然也能解決問題,但普遍對第三種基於消息隊列的最終一致解決方案推薦多比較高,所以第一條和第二條可以參考使用。
一、設計原則
業務拆分,架構設計時應“盡量避免”分布式事務,如果實在避免不了(這已經是高並發、用戶量比較多的網站了)則使用“最終一致性”處理。
二、設計原理
2.1 CAP原理
C(一致性)一致性是指數據的原子性,在經典的數據庫中通過事務來保障,事務完成時,無論成功或回滾,數據都會處於一致的狀態,在分布式環境下,一致性是指多個節點數據是否一致;
A(可用性)服務一直保持可用的狀態,當用戶發出一個請求,服務能在一定的時間內返回結果;
P(分區容忍性)在分布式應用中,可能因為一些分布式的原因導致系統無法運轉,好的分區容忍性,使應用雖然是一個分布式系統,但是好像一個可以正常運轉的整體
2.2 BASE原理
1. BA: Basic Availability 基本業務可用性;
2. S: Soft state 柔性狀態,中間狀態;
3. E: Eventual consistency 最終一致性;
三、設計方案
3.1 兩階段提交
兩階段提交分為准備階段和提交階段兩個階段,其原理架構圖如下:
圖3-1
概述:在提交事務的過程中需要在多個節點之間進行協調,而各節點對鎖資源的釋放必須等到事務最終提交時,比較耗時,鎖資源發生沖突的概率增加,當事務的並發量達到一定數量的時候,就會出現大量事務積壓甚至出現死鎖,系統性能就會嚴重下滑。
3.2 TCC(Try-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
四、最佳實踐
根據不同的業務場景對一致性、可用性和分區容忍性的要求不同,在此三者間尋找一個最佳的平衡點來滿足不同的業務場景,可以根據以上方案靈活選擇,不局限於某一種方案。