淺談Java實現分佈式事務的三種方案

一、問題描述

用戶支付完成會將支付狀態及訂單狀態保存在訂單數據庫中,由訂單服務去維護訂單數據庫。由庫存服務去維護庫存數據庫的信息。下圖是系統結構圖:

如何實現兩個分佈式服務(訂單服務、庫存服務)共同完成一件事即訂單支付成功自動減庫存,這裡的關鍵是如何保證兩個分佈式服務的事務的一致性。

嘗試解決上邊的需求,在訂單服務中遠程調用減庫存接口,偽代碼如下:

訂單支付結果通知方法{

​ 更新支付表中支付狀態為“成功”。
​ 遠程調用減庫存接口減庫存。

上邊的邏輯說明:

1、更新支付表狀態為本地數據庫操作。
2、遠程調用減庫存接口為網絡遠程調用請求。
3、為保存事務上邊兩步操作由spring控制事務,當遇到Exception異常則回滾本地數據庫操作。

問題如下:

1、如果更新支付表失敗則拋出異常,不再執行遠程調用,此設想沒有問題。
2、如果更新支付表成功,網絡遠程調用超時會拉長本地數據庫事務時間,影響數據庫性能。
3、如果更新支付表成功,遠程調用減庫存成功(減庫存數據庫commit成功),最後更新支付表commit失敗,此時出現操作不一致。

上邊的問題涉及到分佈式事務控制。

二、分佈式事務

2.1、什麼是分佈式系統

部署在不同結點上的系統通過網絡交互來完成協同工作的系統。

比如:充值加積分的業務,用戶在充值系統向自己的賬戶充錢,在積分系統中自己積分相應的增加。充值系統和積分系統是兩個不同的系統,一次充值加積分的業務就需要這兩個系統協同工作來完成。

2.2、什麼是事務

事務是指由一組操作組成的一個工作單元,這個工作單元具有原子性(atomicity)、一致性(consistency)、隔離性(isolation)和持久性(durability)。

原子性:執行單元中的操作要麼全部執行成功,要麼全部失敗。如果有一部分成功一部分失敗那麼成功的操作要全部回滾到執行前的狀態。

一致性:執行一次事務會使用數據從一個正確的狀態轉換到另一個正確的狀態,執行前後數據都是完整的。

隔離性:在該事務執行的過程中,任何數據的改變隻存在於該事務之中,對外界沒有影響,事務與事務之間是完全的隔離的。隻有事務提交後其它事務才可以查詢到最新的數據。

持久性:事務完成後對數據的改變會永久性的存儲起來,即使發生斷電宕機數據依然在。

2.3、什麼是本地事務

本地事務就是用關系數據庫來控制事務,關系數據庫通常都具有ACID特性,傳統的單體應用通常會將數據全部存儲在一個數據庫中,會借助關系數據庫來完成事務控制。

2.4、什麼是分佈式事務

在分佈式系統中一次操作由多個系統協同完成,這種一次事務操作涉及多個系統通過網絡協同完成的過程稱為分佈式事務。這裡強調的是多個系統通過網絡協同完成一個事務的過程,並不強調多個系統訪問瞭不同的數據庫,即使多個系統訪問的是同一個數據庫也是分佈式事務,如下圖:

另外一種分佈式事務的表現是,一個應用程序使用瞭多個數據源連接瞭不同的數據庫,當一次事務需要操作多個數據源,此時也屬於分佈式事務,當系統作瞭數據庫拆分後會出現此種情況。

分佈式事務有哪些應用場景:

電商系統中的下單扣庫存

電商系統中,訂單系統和庫存系統是兩個系統,一次下單的操作由兩個系統協同完成

2)金融系統中的銀行卡充值
在金融系統中通過銀行卡向平臺充值需要通過銀行系統和金融系統協同完成。

3)教育系統中下單選課業務
在線教育系統中,用戶購買課程,下單支付成功後學生選課成功,此事務由訂單系統和選課系統協同完成。

4) SNS系統的消息發送
在社交系統中發送站內消息同時發送手機短信,一次消息發送由站內消息系統和手機通信系統協同完成。

三、如何進行分佈式事務控制

3.1、CAP理論

CAP理論是分佈式事務處理的理論基礎:分佈式系統在設計時隻能在一致性(Consistency)、可用性(Availability)、分區容忍性(PartitionTolerance)中滿足兩種,無法兼顧三種。

一致性(Consistency):服務A、B、C三個結點都存儲瞭用戶數據, 三個結點的數據需要保持同一時刻數據一致性。

可用性(Availability):服務A、B、C三個結點,其中一個結點宕機不影響整個集群對外提供服務,如果隻有服務A結點,當服務A宕機整個系統將無法提供服務,增加服務B、C是為瞭保證系統的可用性。

分區容忍性(Partition Tolerance):分區容忍性就是允許系統通過網絡協同工作,分區容忍性要解決由於網絡分區導致數據的不完整及無法訪問等問題。

分佈式系統不可避免的出現瞭多個系統通過網絡協同工作的場景,結點之間難免會出現網絡中斷、網延延遲等現象,這種現象一旦出現就導致數據被分散在不同的結點上,這就是網絡分區

3.2、分佈式系統如果兼顧CAP

在保證分區容忍性的前提下一致性和可用性無法兼顧,如果要提高系統的可用性就要增加多個結點,如果要保證數據的一致性就要實現每個結點的數據一致,結點越多可用性越好,但是數據一致性越差。

所以,在進行分佈式系統設計時,同時滿足“一致性”、“可用性”和“分區容忍性”三者是幾乎不可能的。

CAP有哪些組合方式?

1.CA:放棄分區容忍性,加強一致性和可用性,關系數據庫按照CA進行設計。

2.AP:放棄一致性,加強可用性和分區容忍性,追求最終一致性,很多NoSQL數據庫按照AP進行設計。
說明:這裡放棄一致性是指放棄強一致性,強一致性就是寫入成功立刻要查詢出最新數據。追求最終一致性是指允許暫時的數據不一致,隻要最終在用戶接受的時間內數據 一致即可。

3.CP:放棄可用性,加強一致性和分區容忍性,一些強一致性要求的系統按CP進行設計,比如跨行轉賬,一次轉賬請求要等待雙方銀行系統都完成整個事務才算完成。說明:由於網絡問題的存在CP系統可能會出現待等待超時,如果沒有處理超時問題則整理系統會出現阻塞。

總結

​ 在分佈式系統設計中AP的應用較多,即保證分區容忍性和可用性,犧牲數據的強一致性(寫操作後立刻讀取到最新數據),保證數據最終一致性。比如:訂單退款,今日退款成功,明日賬戶到賬,隻要在預定的用戶可以接受的時間內退款事務走完即可。

四、分佈式事務一致性解決方案

4.1、兩階段提交協議(2PC)

​ 為解決分佈式系統的數據一致性問題出現瞭兩階段提交協議(2 Phase Commitment Protocol),兩階段提交由協調者和參與者組成,共經過兩個階段和三個操作,部分關系數據庫如Oracle、MySQL支持兩階段提交協議,本節講解關系數據庫兩階段提交協議。

參考:

2PC:https://en.wikipedia.org/wiki/Two-phase_commit_protocol

2PC協議流程圖

1)第一階段:準備階段(prepare)
協調者通知參與者準備提交訂單,參與者開始投票。
參與者完成準備工作向協調者回應Yes|NO。

2)第二階段:提交(commit)/回滾(rollback)階段
協調者根據參與者的投票結果發起最終的提交指令。
如果有參與者沒有準備好則發起回滾指令。

一個下單減庫存的例子:

1、應用程序連接兩個數據源。

2、應用程序通過事務協調器向兩個庫發起prepare,兩個數據庫收到消息分別執行本地事務(記錄日志),但不提交,如果執行成功則回復yes,否則回復no。

3、事務協調器收到回復,隻要有一方回復no則分別向參與者發起回滾事務,參與者開始回滾事務。

4、事務協調器收到回復,全部回復yes,此時向參與者發起提交事務。如果參與者有一方提交事務失敗則由事務協調器發起回滾事務。

2PC的優點:實現強一致性,部分關系數據庫支持(Oracle、MySQL等)。

缺點:整個事務的執行需要由協調者在多個節點之間去協調,增加瞭事務的執行時間,性能低下。

解決方案有:springboot+Atomikos or Bitronix

3PC主要是解決協調者與參與者通信阻塞問題而產生的,它比2PC傳遞的消息還要多,性能不高。

詳細參考3PC:

https://en.wikipedia.org/wiki/Three-phase_commit_protocol

4.2、事務補償 TCC

TCC事務補償是基於2PC實現的業務層事務控制方案,它是Try、Confirm和Cancel三個單詞的首字母,含義如下:

1、Try 檢查及預留業務資源完成提交事務前的檢查,並預留好資源。
2、Confirm確定執行業務操作對try階段預留的資源正式執行。
3、Cancel取消執行業務操作對try階段預留的資源釋放。

下邊用一個下單減庫存的業務為例來說明:

1、Try

下單業務由訂單服務和庫存服務協同完成,在try階段訂單服務和庫存服務完成檢查和預留資源。
訂單服務檢查當前是否滿足提交訂單的條件(比如:當前存在未完成訂單的不允許提交新訂單)。
庫存服務檢查當前是否有充足的庫存,並鎖定資源。

2、Confirm

訂單服務和庫存服務成功完成Try後開始正式執行資源操作。
訂單服務向訂單寫一條訂單信息。
庫存服務減去庫存。

3、Cancel

如果訂單服務和庫存服務有一方出現失敗則全部取消操作。
訂單服務需要刪除新增的訂單信息。
庫存服務將減去的庫存再還原。
優點:最終保證數據的一致性,在業務層實現事務控制,靈活性好。
缺點:開發成本高,每個事務操作每個參與者都需要實現try/confirm/cancel三個接口。

註意:TCC的try/confirm/cancel接口都要實現冪等性,在為在try、confirm、cancel失敗後要不斷重試。

什麼是冪等性?

冪等性是指同一個操作無論請求多少次,其結果都相同。
冪等操作實現方式有:

1、操作之前在業務方法進行判斷如果執行過瞭就不再執行。
2、緩存所有請求和處理的結果,已經處理的請求則直接返回結果。
3、在數據庫表中加一個狀態字段(未處理,已處理),數據操作時判斷未處理時再處理。

4.3、消息隊列實現最終一致性

本方案是將分佈式事務拆分成多個本地事務來完成,並且由消息隊列異步協調完成,如下圖:
下邊以下單減少庫存為例來說明:

可以把MQ去掉不使用MQ

1、訂單服務和庫存服務完成檢查和預留資源。
2、訂單服務在本地事務中完成添加訂單表記錄和添加“減少庫存任務消息”。
3、由定時任務根據消息表的記錄發送給MQ通知庫存服務執行減庫存操作。
4、庫存服務執行減少庫存,並且記錄執行消息狀態(為避免重復執行消息,在執行減庫存之前查詢是否執行過此消息)。
5、庫存服務向MQ發送完成減少庫存的消息。
6、訂單服務接收到完成庫存減少的消息後刪除原來添加的“減少庫存任務消息”。
實現最終事務一致要求:預留資源成功理論上要求正式執行成功,如果執行失敗會進行重試,要求業務執行方法實現冪等。

優點 :

由MQ按異步的方式協調完成事務,性能較高。
不用實現try/confirm/cancel接口,開發成本比TCC低。

缺點:

此方式基於關系數據庫本地事務來實現,會出現頻繁讀寫數據庫記錄,浪費數據庫資源,另外對於高並發操作不是最佳方案。

以上就是淺談Java實現分佈式事務的三種方案的詳細內容,更多關於Java 分佈式事務的資料請關註WalkonNet其它相關文章!

推薦閱讀: