常見的分布式解決方案
1、兩階段提交協議(2PC)
解決分布式系統的數據一致性問題出現了兩階段提交協議(2 Phase Commitment Protocol),
兩階段提交由協調者和參與者組成,共經過兩個階段和三個操作,部分關系數據庫如Oracle、MySQL支持兩階段提交協議。
說到2pc就不得不聊聊數據庫分布式事務中的XA transactions
在XA協議中分為兩階段:
- 第一階段:事務管理器要求每個涉及到事務的數據庫預提交(precommit)此操作,並反映是否可以提交.
- 第二階段:事務協調器要求每個數據庫提交數據,或者回滾數據。
舉一個例子:
1、應用程序通過事務協調器向兩個庫發起prepare,兩個數據庫收到消息分別執行本地事務(記錄日志),
但不提交,如果執行成功則回復yes,否則回復no。
2、事務協調器收到回復,只要有一方回復no則分別向參與者發起回滾事務,參與者開始回滾事務。
3、事務協調器收到回復,全部回復yes,此時向參與者發起提交事務。
如果參與者有一方提交事務失敗則由事務協調器發起回滾事務。
優點:
盡量保證了數據的強一致,實現成本較低,在各大主流數據庫都有自己實現,對於MySQL是從5.5開始支持。
缺點:
單點問題:事務管理器在整個流程中扮演的角色很關鍵,如果其宕機,比如在第一階段已經完成,
在第二階段正准備提交的時候事務管理器宕機,資源管理器就會一直阻塞,導致數據庫無法使用。
同步阻塞:在准備就緒之后,資源管理器中的資源一直處於阻塞,直到提交完成,釋放資源。
數據不一致:兩階段提交協議雖然為分布式數據強一致性所設計,但仍然存在數據不一致性的可能,比如在第二階段中,
假設協調者發出了事務commit的通知,但是因為網絡問題該通知僅被一部分參與者所收到並執行了commit操作,
其余的參與者則因為沒有收到通知一直處於阻塞狀態,這時候就產生了數據的不一致性。
2、事務補償(TCC)
TCC事務補償是基於2PC實現的業務層事務控制方案,它是Try、Confirm和Cancel三個單詞的首字母,含義如下:
- 1、Try 檢查及預留業務資源完成提交事務前的檢查,並預留好資源。
- 2、Confirm 確定執行業務操作,對try階段預留的資源正式執行。
- 3、Cancel 取消執行業務操作,對try階段預留的資源釋放。
TCC方案在電商、金融領域落地較多。TCC方案其實是兩階段提交的一種改進。
其將整個業務邏輯的每個分支顯式的分成了Try、Confirm、Cancel三個操作。
Try部分完成業務的准備工作,confirm部分完成業務的提交,cancel部分完成事務的回滾。
基本原理如下圖所示。
事務開始時,業務應用會向事務協調器注冊啟動事務。之后業務應用會調用所有服務的try接口,
完成一階段准備。之后事務協調器會根據try接口返回情況,決定調用confirm接口或者cancel接口。
如果接口調用失敗,會進行重試。
舉一個例子:下單減庫存的業務
1、Try
下單業務由訂單服務和庫存服務協同完成,在try階段訂單服務和庫存服務完成檢查和預留資源。
訂單服務檢查當前是否滿足提交訂單的條件(比如:當前存在未完成訂單的不允許提交新訂單)。
庫存服務檢查當前是否有充足的庫存,並鎖定資源。
2、Confirm
訂單服務和庫存服務成功完成Try后開始正式執行資源操作。
訂單服務向訂單寫一條訂單信息。
庫存服務減去庫存。
3、Cancel
如果訂單服務和庫存服務有一方出現失敗則全部取消操作。
訂單服務需要刪除新增的訂單信息。
庫存服務將減去的庫存再還原。
優點:
最終保證數據的一致性,在業務層實現事務控制,靈活性好。
缺點:
開發成本高,每個事務操作每個參與者都需要實現try/confirm/cancel三個接口。
注意:
TCC的try/confirm/cancel接口都要實現冪等性(冪等性是指同一個操作無論請求多少次,其結果都相同。),
在為在try、confirm、cancel失敗后要不斷重試。
3、消息隊列實現最終一致
本方案是將分布式事務拆分成多個本地事務來完成,並且由消息隊列異步協調完成,如下圖:下邊以下單減少庫存為例來說明:
1、訂單服務和庫存服務完成檢查和預留資源。
2、訂單服務在本地事務中完成添加訂單表記錄和添加“減少庫存任務消息”。
3、由定時任務根據消息表的記錄發送給MQ通知庫存服務執行減庫存操作。
4、庫存服務執行減少庫存,並且記錄執行消息狀態(為避免重復執行消息,在執行減庫存之前查詢是否執行過此消息)。
5、庫存服務向MQ發送完成減少庫存的消息。
6、訂單服務接收到完成庫存減少的消息后刪除原來添加的“減少庫存任務消息”。
實現最終事務一致要求:預留資源成功理論上要求正式執行成功,如果執行失敗會進行重試,要求業務執行方法實現冪等。
優點 :
由MQ按異步的方式協調完成事務,性能較高。
不用實現try/confirm/cancel接口,開發成本比TCC低。
缺點:
此方式基於關系數據庫本地事務來實現,會出現頻繁讀寫數據庫記錄,浪費數據庫資源,另外對於高並發操作不是最佳方案。