基于 XA 協議的兩階段提交 (2PC)。這是一種分布式事務協議,旨在保證在多個參與者(通常是不同的數據庫或資源管理器)共同參與的事務中,所有參與者要么都提交事務,要么都回滾事務,從而維護數據的一致性。
你可以把 2PC 想象成一個需要多個部門共同簽字才能生效的合同審批流程。每個部門都有同意或拒絕的權利,最終結果取決于所有部門的意見。
核心思想:協調者與參與者
在 2PC 中,有兩個關鍵的角色:
- 協調者 (Coordinator): 負責整個事務的協調和管理。它向所有參與者發出指令,并收集它們的響應,最終決定事務是提交還是回滾。
- 參與者 (Participants): 參與到分布式事務中的各個資源管理器(例如,數據庫)。它們接收協調者的指令,執行本地事務,并告知協調者自己的狀態。
兩階段提交的過程
2PC 的過程分為兩個關鍵階段:
第一階段(準備階段 - Prepare Phase)
-
協調者詢問 (Prepare Request): 協調者向所有參與者發送一個“準備請求”,詢問它們是否準備好提交事務。這個請求包含了要執行的事務操作信息。
-
參與者執行本地事務 (Local Transaction Execution): 收到準備請求后,每個參與者執行本地事務操作,并在本地保存相關的 undo/redo 日志等信息,以便在后續需要回滾或提交時使用。注意:這里參與者只是執行操作,但并不真正提交。
-
參與者響應 (Vote): 每個參與者根據本地事務的執行情況,向協調者發送一個“投票”:
- “同意 (Vote-Commit)”: 表示本地事務執行成功,參與者已經準備好提交。
- “拒絕 (Vote-Abort)”: 表示本地事務執行失敗,參與者不能提交,希望回滾。
第二階段(提交/回滾階段 - Commit/Rollback Phase)
協調者在收集到所有參與者的投票后,會根據投票結果決定下一步的操作:
情況一:所有參與者都投了“同意”票
-
協調者發送提交請求 (Commit Request): 協調者向所有參與者發送“提交請求”。
-
參與者提交本地事務 (Local Transaction Commit): 收到提交請求后,每個參與者正式提交之前在本地執行的事務。
-
參與者發送確認 (Acknowledgement): 參與者完成提交后,向協調者發送“提交確認”。
-
協調者完成事務 (Transaction Completion): 協調者收到所有參與者的提交確認后,認為整個分布式事務成功完成。
情況二:任何一個或多個參與者投了“拒絕”票,或者協調者在規定時間內沒有收到所有參與者的投票
-
協調者發送回滾請求 (Rollback Request): 協調者向所有投了“同意”票的參與者(如果存在)以及投了“拒絕”票的參與者發送“回滾請求”。
-
參與者回滾本地事務 (Local Transaction Rollback): 收到回滾請求后,每個參與者利用之前保存的 undo 日志撤銷之前執行的本地事務操作。
-
參與者發送確認 (Acknowledgement): 參與者完成回滾后,向協調者發送“回滾確認”。
-
協調者完成事務 (Transaction Completion): 協調者收到所有參與者的回滾確認后,認為整個分布式事務已回滾。
一個簡單的轉賬例子
假設我們有兩個不同的銀行數據庫參與一個跨行轉賬事務:
- 參與者 A: 存儲用戶 A 的賬戶信息(在 Bank A 的數據庫中)。
- 參與者 B: 存儲用戶 B 的賬戶信息(在 Bank B 的數據庫中)。
- 協調者: 一個專門的事務管理器。
事務: 用戶 A 向用戶 B 轉賬 100 元。
第一階段(準備階段)
-
協調者發送準備請求: “請參與者 A 準備從用戶 A 賬戶扣除 100 元,參與者 B 準備向用戶 B 賬戶增加 100 元。”
-
參與者 A 執行本地事務: Bank A 的數據庫執行了扣除操作,但未提交,記錄了 undo 日志(如果需要回滾,就加回 100 元)。參與者 A 投票“同意”。
-
參與者 B 執行本地事務: Bank B 的數據庫執行了增加操作,但未提交,記錄了 redo 日志(如果需要提交,就確認增加 100 元)。參與者 B 投票“同意”。
-
協調者收到所有“同意”票。
第二階段(提交階段)
-
協調者發送提交請求: “請參與者 A 和參與者 B 提交事務。”
-
參與者 A 提交本地事務: Bank A 的數據庫正式提交了扣除 100 元的操作。參與者 A 發送“提交確認”。
-
參與者 B 提交本地事務: Bank B 的數據庫正式提交了增加 100 元的操作。參與者 B 發送“提交確認”。
-
協調者收到所有“提交確認”,事務成功完成。
如果第一階段有參與者投了“拒絕”票(例如,用戶 A 賬戶余額不足):
第一階段(準備階段)
- 參與者 A 執行本地事務,發現余額不足,投票“拒絕”。
- 參與者 B 可能仍然投票“同意”(因為它不知道 A 的情況)。
- 協調者收到至少一個“拒絕”票。
第二階段(回滾階段)
-
協調者發送回滾請求: “請參與者 A 和參與者 B 回滾事務。”
-
參與者 A 回滾本地事務: Bank A 的數據庫利用 undo 日志撤銷了之前的扣除操作(實際上由于余額不足,可能并沒有真正扣除)。參與者 A 發送“回滾確認”。
-
參與者 B 回滾本地事務: Bank B 的數據庫利用 undo 日志撤銷了之前的增加操作(即使已經執行,也會被撤銷)。參與者 B 發送“回滾確認”。
-
協調者收到所有“回滾確認”,事務回滾成功,保證了兩個銀行賬戶數據的一致性。
2PC 的缺點
雖然 2PC 能夠保證數據的一致性,但也存在一些明顯的缺點:
- 同步阻塞 (Blocking): 在準備階段之后,如果協調者或某個參與者發生故障,其他參與者可能會一直處于阻塞狀態,等待協調者的指令或故障參與者的恢復,這會降低系統的可用性。
- 單點故障 (Single Point of Failure): 協調者是整個事務的關鍵,如果協調者發生故障,整個分布式事務將無法繼續進行。
- 數據不一致的風險 (Data Inconsistency Window): 在準備階段和提交/回滾階段之間,如果協調者在發送提交/回滾指令前崩潰,可能會導致部分參與者提交了事務,而另一部分參與者沒有,從而造成數據不一致。
TCC (Try-Confirm-Cancel) 分布式事務協議,并對比它與兩階段提交 (2PC) 的不同。
TCC:一種柔性事務
TCC 是一種基于補償機制的分布式事務解決方案。它將一個分布式事務的生命周期劃分為三個階段:
-
Try 階段(嘗試執行):
- 嘗試執行業務操作,完成所有業務檢查(一致性)。
- 預留必要的業務資源(準隔離性)。
- 這個階段的目標是盡量為后續的 Confirm 階段準備好執行條件。
-
Confirm 階段(確認執行):
- 在 Try 階段所有參與者都成功的情況下,Confirm 階段會被執行。
- 真正執行業務操作,不進行任何業務檢查。
- Confirm 階段只需要使用 Try 階段預留的資源,因此成功率要高。
- Confirm 操作需要保證冪等性,因為可能會重試。
-
Cancel 階段(取消執行):
- 如果在 Try 階段有任何參與者失敗,或者在后續階段發生異常,Cancel 階段會被執行。
- 釋放 Try 階段預留的業務資源,撤銷之前 Try 階段所做的操作,進行補償。
- Cancel 操作同樣需要保證冪等性,因為可能會重試。
TCC 的核心思想是“先嘗試,成功則確認,失敗則補償”。 它不依賴于資源管理器(如數據庫)的 XA 協議,而是將事務的控制權交還給業務層面,通過業務邏輯來實現事務的提交和回滾。
一個跨銀行轉賬的 TCC 例子
我們繼續使用跨銀行轉賬的場景:用戶 A 從 Bank A 轉賬 100 元給用戶 B 在 Bank B。
- 參與者 A: Bank A 的轉出服務。
- 參與者 B: Bank B 的轉入服務。
- 協調者: 仍然需要一個協調者來記錄事務狀態和驅動流程。
Try 階段:
- 協調者通知參與者 A (Try): “嘗試從用戶 A 賬戶凍結 100 元。”
- 參與者 A 執行 (Try): Bank A 的轉出服務檢查用戶 A 的余額是否足夠,如果足夠則凍結 100 元(例如,在一個中間狀態或單獨的凍結金額字段中),并記錄凍結日志。Try 階段成功,返回成功。
- 協調者通知參與者 B (Try): “嘗試在用戶 B 賬戶預增加 100 元的額度(但尚未真正增加)。”
- 參與者 B 執行 (Try): Bank B 的轉入服務在用戶 B 的賬戶上預留 100 元的額度(例如,在一個中間狀態或單獨的待入賬金額字段中),并記錄預留日志。Try 階段成功,返回成功。
Confirm 階段(如果 Try 階段都成功):
- 協調者通知參與者 A (Confirm): “確認轉出操作,真正扣減用戶 A 賬戶的 100 元。”
- 參與者 A 執行 (Confirm): Bank A 的轉出服務從用戶 A 賬戶的實際余額中扣除 100 元,并清除凍結記錄。Confirm 成功,返回成功。
- 協調者通知參與者 B (Confirm): “確認轉入操作,真正增加用戶 B 賬戶的 100 元。”
- 參與者 B 執行 (Confirm): Bank B 的轉入服務將預留的 100 元額度增加到用戶 B 的實際余額中,并清除預留記錄。Confirm 成功,返回成功。
Cancel 階段(如果在 Try 階段有失敗,或后續階段失敗):
假設在 Try 階段,Bank A 發現用戶 A 余額不足,Try 失敗。
- 協調者通知參與者 A (Cancel): “取消轉出操作,釋放之前凍結的金額。”
- 參與者 A 執行 (Cancel): Bank A 的轉出服務釋放之前凍結的 100 元(如果已經凍結),并清除凍結記錄。Cancel 成功,返回成功。
- 協調者通知參與者 B (Cancel): “取消轉入操作,撤銷之前預增加的額度。”
- 參與者 B 執行 (Cancel): Bank B 的轉入服務撤銷之前預留的 100 元額度,并清除預留記錄。Cancel 成功,返回成功。
TCC 與 2PC 的不同
特性 | 兩階段提交 (2PC) | Try-Confirm-Cancel (TCC) |
---|---|---|
協議層面 | 依賴資源管理器(如數據庫)的 XA 協議 | 基于業務邏輯實現 |
資源鎖定 | 在準備階段鎖定資源,直到提交或回滾 | Try 階段預留資源,Confirm/Cancel 階段釋放資源 |
一致性 | 強一致性(在理想情況下) | 最終一致性(通過補償機制保證) |
性能 | 可能存在長時間的資源鎖定,性能相對較低 | 資源鎖定時間短,性能通常更高 |
可用性 | 協調者或參與者故障可能導致長時間阻塞 | 業務層面實現補償,可用性相對較高 |
開發復雜度 | 對業務代碼侵入性較小,依賴中間件實現 | 對業務代碼侵入性較大,需要實現 Try/Confirm/Cancel 邏輯 |
適用場景 | 資源支持 XA 協議的場景,對強一致性要求高的場景 | 跨數據庫、跨服務等復雜場景,對性能和可用性要求高的場景 |
導出到 Google 表格
總結
- 2PC 追求強一致性,但在某些故障情況下可能導致長時間的阻塞,影響系統可用性。它依賴于底層數據庫等資源管理器對 XA 協議的支持。
- TCC 追求最終一致性,通過業務邏輯實現補償,減少了資源鎖定的時間,提高了系統的并發能力和可用性。但它對業務代碼的侵入性較大,需要開發人員實現 Try、Confirm 和 Cancel 三個操作,并且要考慮冪等性、空回滾、懸掛等問題,開發復雜度較高。
XA 和 TCC 的核心區別
1. 資源層面 vs. 業務層面
-
XA (資源層面): 你可以把它想象成是由數據庫(資源管理器,RM)自身提供的“官方”分布式事務解決方案。 它依賴于數據庫實現了 XA 協議,這個協議定義了協調者(Transaction Manager,TM)和參與者(RM)之間如何進行兩階段提交的通信。
- 類比: 這就像是不同銀行之間有統一的國際清算系統(XA 協議),大家都遵循這個標準流程來處理跨行交易,銀行內部的事務機制直接參與到這個流程中。
-
TCC (業務層面): 它不依賴于數據庫的 XA 協議。 而是將分布式事務的控制權放在了業務代碼層面。你需要自己設計每個參與服務的 Try、Confirm 和 Cancel 操作,通過編寫業務邏輯來模擬兩階段提交。
- 類比: 這就像是不同公司之間沒有統一的清算系統,它們需要自己協商一套跨公司交易的流程。每個公司都需要定義“預申請”、“確認”和“撤銷”這些操作的具體步驟。
2. 強一致性 vs. 最終一致性
-
XA (強一致性): 在理想情況下(沒有故障),XA 能夠保證嚴格的原子性。要么所有參與者都提交成功,要么都回滾成功,數據在事務結束后必須保持一致。
- 理解: 就像國際清算系統保證,只要交易開始,最終要么雙方賬戶都更新成功,要么都不更新,中間狀態對用戶來說是不可見的。
-
TCC (最終一致性): TCC 并不能保證在事務執行過程中所有參與者都處于完全一致的狀態。如果在 Confirm 或 Cancel 階段發生故障,可能需要進行重試或人工干預來保證最終的數據一致性。它的目標是經過一段時間后,數據能夠達到一致的狀態。
- 理解: 就像公司間的協商交易,如果在“確認”階段出了問題,可能需要重新發起確認或者執行“撤銷”操作,最終目標是讓雙方的賬目對得上,但中間可能會有短暫的不一致。
3. 資源鎖的處理
-
XA (長時間持有鎖): 這是 XA 的一個關鍵特點,也是其性能瓶頸所在。在準備階段 (Prepare),參與者(數據庫)通常會鎖定相關的資源(例如,數據庫行、表等)。這些鎖會一直保持到提交或回滾階段結束才會釋放。
- 影響: 長時間的資源鎖定會降低數據庫的并發處理能力,因為其他事務可能需要等待這些鎖被釋放才能繼續執行。
-
TCC (不長時間持有鎖): TCC 的設計目標之一就是盡量縮短資源鎖定的時間。
- Try 階段: Try 階段通常只是預留資源(例如,凍結金額、記錄中間狀態),而不是直接鎖定數據庫的業務資源。
- Confirm/Cancel 階段: 這兩個階段執行真正的業務操作或補償操作,通常只需要很短的時間,并且依賴于 Try 階段預留的信息,不再需要長時間鎖定核心業務資源。
- 優勢: 通過減少鎖的持有時間,TCC 可以提高系統的并發性和吞吐量。
更形象的比喻
想象你和你的朋友合伙買一件東西,需要分別從你們的銀行賬戶扣款:
-
XA 就像銀行的聯名賬戶: 銀行提供了一個“兩階段扣款”的功能。當發起扣款時,銀行會先凍結你們賬戶上的錢(準備階段,持有鎖),確認雙方賬戶都凍結成功后,才會真正扣款(提交階段,釋放鎖)。如果任何一方賬戶有問題,銀行會取消凍結(回滾階段)。這個過程由銀行的系統嚴格控制,保證要么都扣款成功,要么都不扣款。但如果銀行系統繁忙,凍結的時間可能會比較長,影響你們其他交易。
-
TCC 就像你們私下協商:
- Try: 你先告訴銀行 A:“我要給朋友轉 100 元,請先幫我記錄一下,這筆錢暫時不能動。”你的朋友也告訴銀行 B:“我可能會收到朋友的 100 元,請先預留一個收款的記錄。”(業務層面預留資源,不一定鎖定核心賬戶余額)
- Confirm: 如果你們都通知說“可以了,確認轉賬!”,銀行 A 才真正扣除你的錢,銀行 B 才真正增加你朋友的錢。
- Cancel: 如果任何一方出了問題(例如,你的余額不足),你們會通知銀行 A 取消之前的記錄,通知銀行 B 也取消之前的預留。
- 關鍵: 這個過程是你們自己(業務代碼)控制的,銀行只是根據你們的指令操作。銀行不會一直鎖定你的錢,但在“確認”或“取消”失敗時,可能需要你們重試或手動協調來保證最終賬目正確。
總結理解的關鍵點
- 誰來控制事務: XA 是資源管理器(數據庫)控制,TCC 是業務代碼控制。
- 如何保證一致性: XA 依賴數據庫的原子性能力,TCC 依賴業務邏輯的補償機制。
- 鎖的持有時間: XA 持有鎖的時間較長,TCC 盡量縮短鎖的持有時間。
- 適用場景: XA 適用于支持 XA 協議的同構數據源,對強一致性要求高的場景;TCC 適用于跨異構系統、需要更高并發和可用性的場景。
xa 相當于人家給我做了這個功能 而tcc則是自己實現功能?
沒錯?
XA 就像是數據庫(或資源管理器)自身提供了“現成的”分布式事務功能。 你只需要按照 XA 協議的規范來使用,協調者會和參與者(數據庫)進行通信,由數據庫內部的機制來保證事務的原子性和一致性,并負責資源的鎖定和釋放。你更多的是在配置和使用這個“內置”的功能。
TCC 則完全是“自己動手,豐衣足食”。 你需要基于業務邏輯,自己設計和實現 Try、Confirm 和 Cancel 這三個階段的操作。你需要考慮如何在 Try 階段預留資源,如何在 Confirm 階段真正執行業務操作,以及如何在 Cancel 階段進行補償,撤銷之前的操作。TCC 將分布式事務的控制權從資源管理器轉移到了業務代碼層面,你需要自己編寫代碼來模擬兩階段提交的流程。
更進一步理解:
-
XA: 就像使用支持分布式事務的中間件(例如,某些 JMS 實現、一些分布式數據庫)提供的事務管理能力。這些中間件已經幫你實現了兩階段提交的協議細節,你只需要配置和調用相應的 API。
-
TCC: 就像你需要自己編寫一個復雜的業務流程,這個流程包含“預處理”、“確認執行”和“撤銷處理”三個步驟,并且你需要自己保證這些步驟在分布式環境下的正確執行和數據一致性。
所以,你的總結非常精辟:
- XA: “人家(資源管理器)給我做了這個功能,我直接用。”
- TCC: “人家沒做,我自己想辦法實現這個功能。”
這種“自己實現”的靈活性使得 TCC 可以應用于 XA 無法很好解決的場景,例如跨異構數據源、不直接支持 XA 協議的服務調用等。但同時,這也帶來了更高的開發復雜度和維護成本。