TCC真沒這麼簡單,一文講透|分散式事務系列(三)

張哥說技術發表於2023-03-30

來源:後端開發技術


本文從兩個場景說起,詳細描述了TCC的詳細過程,以及對比2PC有什麼區別,適用什麼樣的場景。

在面試前複習 TCC 的時候你是不是這樣做的:百度TCC關鍵詞,隨便找了篇文章,查詢到他有try、confirm、Cancel 三個階段,業務侵入度高,和兩階段差不多。複習完畢。

如果你是這樣去理解和複習的,只能說對 TCC 的理解太不到位了,真的有必要耐心看完這篇文章。

TCC

有些人可能覺得 TCC 和兩階段提交非常相似,無法區分。首先強調,TCC是一種最終一致性方案,他並不是強一致性。如果你不瞭解兩階段提交,請先看這篇。

TCC真沒這麼簡單,一文講透|分散式事務系列(三)

分散式事務,強一致性方案有哪些?|分散式事務系列(二)


這篇文章我們來探討下 TCC 到底是什麼,他用來解決什麼問題?在繼續閱讀之前,我先丟擲兩個問題。

一個面試中的問題

在某大廠面試的時候,面試官問了我一個問題:由於我介紹的是一個支付專案,業務系統呼叫支付系統扣款成功後採用本地事務狀態表(最終一致性)的方案通知業務系統。由於我們的系統QPS並不高,所以我自認為這樣的方案是沒問題的。

但是面試官問我,如果扣款成功後訂單系統資料庫寫入不可用怎麼辦?雖然使用者支付成功,但是即使有短暫幾分鐘的寫入不可用也會導致客訴暴漲,你怎麼解決這個問題?

這讓我意識到,如果在高併發場景下這個方案是有問題的。本地事務狀態表的缺點就是及時性不夠,當然這個可以透過再本地事務結束後及時發起對業務的通知解決。但是有個問題無法解決,本地事務狀態表都是基於本地業務執行成功後,下游依賴業務也會成功。如果下游系統發生不可用問題,我們的本地事務狀態表中狀態將阻塞在這裡,出現上述支付扣款後一直無法通知到訂單的情況。

電商超賣問題

除了上述面試問題,還有一個電商中很常見的超賣問題。

還是以電商場景下的餘額支付、扣庫存為例。如果我們採用可靠訊息佇列或者本地事務表的方案,使用者購買了一瓶可樂,餘額扣款成功後傳送訊息到庫存系統,庫存系統對可樂的庫存減1。如果是隻有一個使用者買並且庫存充足的情況下這是沒有問題的,但是如果此時有3個使用者在購買可樂,但是庫存僅剩 1 瓶,支付前使用介面檢查剩餘庫存的時候是透過的。但是等到支付成功,扣減庫存的訊息同步到庫存服務的時候系統就會出現超賣 2 瓶的情況。

TCC真沒這麼簡單,一文講透|分散式事務系列(三)

之所以出現上述問題,是因為建立訂單在業務上是使用者之間隔離的,業務上不會有資源的競爭,但是庫存資料是一種共享資源,多個使用者同時購買同樣的商品就會出現併發問題,所以需要不同的執行緒之間事務隔離

實現隔離性可以使用我們之前提到的強一致性方案 兩階段提交,但是在電商場景高併發下,兩階段提交持有資源時間過長並不合適。強一致性效能太低,訊息佇列方案由無法資源隔離,怎麼辦呢?TCC 方案就是為這種場景而生的。

什麼是 TCC

TCC 是“Try-Confirm-Cancel”三個單詞的縮寫,是由資料庫專家 Pat Helland 在 2007 年撰寫的論文《Life beyond Distributed Transactions: An Apostate’s Opinion》中提出。一提到 TCC 我們都知道它是一種業務侵入較強的分散式事務方案,要求業務處理過程必須拆分為“try ”和“ confirm/cancel”兩個階段,並且需要分別提供這兩個階段所涉及三個步驟的介面。

  • Try :嘗試執行階段,完成所有業務可執行性的檢查(保障一致性),並且預留好全部需用到的業務資源(保障隔離性)。
  • Confirm :確認執行階段,不進行任何業務檢查,直接使用 Try 階段準備的資源來完成業務處理(也就是說業務上理論一定可以執行成功)。由於分散式環境下的不可靠性,Confirm 階段可能會重複執行,因此本階段所執行的操作需要具備冪等性。
  • Cancel:取消執行階段,釋放 Try 階段預留的業務資源。由於分散式環境下的不可靠性,Cancel 階段可能會重複執行,也需要滿足冪等性。

具體時序圖如下:

TCC真沒這麼簡單,一文講透|分散式事務系列(三)

這三個階段的特點如下:

流程特點
Try預留業務資源(不鎖定資源,獨立事務)
Confirm確認提交資源(需要重試,最終一致性)
Cancel釋放資源(需要重試,最終一致性)

如何解決前文兩個問題

訂單狀態不更新問題

第一個問題,如何解決扣款成功訂單狀態遲遲不更新:

  1. 如果採用TCC方案,首先本地建立事務,生成事務 ID,記錄在活動日誌中,此時狀態為Try。
  2. 訂單在扣款和通知訂單狀態變更會在Try階段進行檢查,首先在使用者的餘額系統鎖定訂單金額,然後通知訂單狀態變為支付待確認。
  3. 如果訂單庫此時寫入不可用,那麼Try階段訂單服務會失敗,活動日誌狀態變更為 Cancel。
  4. 進入Cancel階段後,餘額服務將鎖定的金額釋放,通知訂單支付不成功,如果任意環節失敗,可以用最終一致性方案不斷嘗試。

這樣,上述問題透過餘額回退的方式得到化解。

超賣問題

第二個問題,如何解決電商超賣的問題:

  1. 使用者發起支付請求,購買一瓶可樂。

  2. 建立事務,生成事務 ID,記錄在活動日誌中,進入 Try 階段:

    餘額服務嘗試鎖定金額,庫存服務嘗試鎖定庫存資源。兩者有任意失敗或者介面超時活動日誌的狀態記錄為 Cancel,將進入 Cancel階段;全部成功則進入 Confirm 階段,活動日誌的狀態記錄為 Confirm。

  3. Confirm 階段,說明Try全部成功:餘額服務執行扣減金額,庫存執行扣減庫存。

    Confirm 階段如果全部完成,事務正常結束。如果任何一個環節出現異常,不論是業務異常或者網路異常,都將根據活動日誌中的記錄,重複執行該服務的 Confirm 操作,實現最終一致。

  4. Cancel 階段,說明Try 階段有服務超時或者執行失敗:撤銷使用者被鎖定的金額,解鎖被佔用的庫存。

    Cancel 階段如果全部完成,事務以執行回滾結束。如果在 Cancel 時任意服務超時或者失敗,都將根據活動日誌中的記錄,重複執行該服務的 Cancel 操作,實現最終一致性。

假設可樂只剩 1 瓶,因為我們在第 Try 階段首先執行餘額鎖定和庫存扣減,如果使用者A扣款成功,並且鎖定庫存成功,此時可樂的可購買庫存為0。當使用者B併發購買,即使餘額鎖定成功,但是檢查庫存時發現已經庫存不足,將通知餘額解鎖金額,超賣問題由此解決。

TCC真沒這麼簡單,一文講透|分散式事務系列(三)

TCC與2PC對比

正因為 TCC 也分為了 Try 和 Confirm/Cancel 兩個階段,所以很多人對此和兩階段提交產生了混淆,這裡用兩個表格列出他們的異同。

相同:

TCC兩階段提交
可分為 Try 和 Confirm/Cancel 兩個階段可以分為投票階段和提交階段 兩個階段
在Try階段佔用資源在第一階段佔用資源

不同:

TCC兩階段提交
實現了最終一致性剛性事務,實現了強一致性
服務的程式碼邏輯層面實現,需要對三種操作分別編碼,有業務侵入,開發成本更高底層資料庫支援兩階段協議,無業務侵入
第一階段,Try 階段會直接提交事務,只會短暫持有資源鎖,效能較高第一階段會持續鎖定資源並持有鎖,事務不提交,造成服務阻塞,效能低
第二階段的 Confirm 和 Cancel 如果出現失敗,會利用最終一致性方案不斷重試第二階段如果有失敗,會造成系統之間的資料不一致
TCC 的每個步驟在資料庫層面都是一個獨立事務兩階段的兩個步驟和起來是一個完整的事務

適用場景

說了這麼多,我們應該在什麼場景下使用 TCC 呢?我做了些總結。

  1. 當常規最終一致性方案無法滿足,需要更強的一致性
  2. 當業務對實時性要求高。
  3. 當業務涉及到資源爭奪,需要更高的隔離性
  4. 當業務在滿足一致性和隔離性的時候,還需要更好的效能表現

當你的業務遇到這些問題的時候,那就可以考慮TCC了,比如涉及到資金資料,銀行業務,金融業務,涉及到交易、支付、賬務都可以考慮。

但是上述優點都是有代價的,TCC 對於業務的侵入性非常強,業務邏輯的每個分支都需要實現try、confirm、cancel三個操作。難度也比較大,需要根據不同的失敗原因實現不同的回滾策略,有更高的開發成本和更換事務實現方案的替換成本。我們可以基於某些分散式事務中介軟體(譬如阿里開源的Seata)去完成,儘量減輕一些編碼工作量。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2942604/,如需轉載,請註明出處,否則將追究法律責任。

相關文章