分布式技術(shù)原理與實戰(zhàn)45講--第09講:如何在業(yè)務(wù)中體現(xiàn) TCC 事務(wù)模型?
在分布式系統(tǒng)設(shè)計中,隨著微服務(wù)的流行,通常一個業(yè)務(wù)操作被拆分為多個子任務(wù),比如電商系統(tǒng)的下單和支付操作,就涉及到了創(chuàng)建和更新訂單、扣減賬戶余額、扣減庫存、發(fā)送物流消息等,那么在復(fù)雜業(yè)務(wù)開發(fā)中,如何保證最終數(shù)據(jù)一致性呢?
TCC 事務(wù)模型是什么
TCC(Try-Confirm-Cancel)的概念來源于 Pat Helland 發(fā)表的一篇名為“Life beyond Distributed Transactions:an Apostate’s Opinion”的論文。
TCC 提出了一種新的事務(wù)模型,基于業(yè)務(wù)層面的事務(wù)定義,鎖粒度完全由業(yè)務(wù)自己控制,目的是解決復(fù)雜業(yè)務(wù)中,跨表跨庫等大顆粒度資源鎖定的問題。TCC 把事務(wù)運行過程分成 Try、Confirm / Cancel 兩個階段,每個階段的邏輯由業(yè)務(wù)代碼控制,避免了長事務(wù),可以獲取更高的性能。
TCC 的各個階段
TCC 的具體流程如下圖所示:

Try 階段:調(diào)用 Try 接口,嘗試執(zhí)行業(yè)務(wù),完成所有業(yè)務(wù)檢查,預(yù)留業(yè)務(wù)資源。
Confirm 或 Cancel 階段:兩者是互斥的,只能進(jìn)入其中一個,并且都滿足冪等性,允許失敗重試。
Confirm 操作:對業(yè)務(wù)系統(tǒng)做確認(rèn)提交,確認(rèn)執(zhí)行業(yè)務(wù)操作,不做其他業(yè)務(wù)檢查,只使用 Try 階段預(yù)留的業(yè)務(wù)資源。
Cancel 操作:在業(yè)務(wù)執(zhí)行錯誤,需要回滾的狀態(tài)下執(zhí)行業(yè)務(wù)取消,釋放預(yù)留資源。
Try 階段失敗可以 Cancel,如果 Confirm 和 Cancel 階段失敗了怎么辦?
TCC 中會添加事務(wù)日志,如果 Confirm 或者 Cancel 階段出錯,則會進(jìn)行重試,所以這兩個階段需要支持冪等;如果重試失敗,則需要人工介入進(jìn)行恢復(fù)和處理等。
應(yīng)用 TCC 的優(yōu)缺點
實際開發(fā)中,TCC 的本質(zhì)是把數(shù)據(jù)庫的二階段提交上升到微服務(wù)來實現(xiàn),從而避免數(shù)據(jù)庫二階段中長事務(wù)引起的低性能風(fēng)險。
所以說,TCC 解決了跨服務(wù)的業(yè)務(wù)操作原子性問題,比如下訂單減庫存,多渠道組合支付等場景,通過 TCC 對業(yè)務(wù)進(jìn)行拆解,可以讓應(yīng)用自己定義數(shù)據(jù)庫操作的粒度,可以降低鎖沖突,提高系統(tǒng)的業(yè)務(wù)吞吐量。
TCC 的不足主要體現(xiàn)在對微服務(wù)的侵入性強,TCC 需要對業(yè)務(wù)系統(tǒng)進(jìn)行改造,業(yè)務(wù)邏輯的每個分支都需要實現(xiàn) try、Confirm、Cancel 三個操作,并且 Confirm、Cancel 必須保證冪等。
另外 TCC 的事務(wù)管理器要記錄事務(wù)日志,也會損耗一定的性能。
從真實業(yè)務(wù)場景分析 TCC
下面以一個電商中的支付業(yè)務(wù)來演示,用戶在支付以后,需要進(jìn)行更新訂單狀態(tài)、扣減賬戶余額、增加賬戶積分和扣減商品操作。
在實際業(yè)務(wù)中為了防止超賣,有下單減庫存和付款減庫存的區(qū)別,支付除了賬戶余額,還有各種第三方支付等,這里我們?yōu)榱嗣枋龇奖悖y(tǒng)一使用扣款減庫存,扣款來源是用戶賬戶余額。

業(yè)務(wù)邏輯拆解
我們把訂單業(yè)務(wù)拆解為以下幾個步驟:
訂單更新為支付完成狀態(tài)
扣減用戶賬戶余額
增加用戶賬戶積分
扣減當(dāng)前商品的庫存
如果不使用事務(wù),上面的幾個步驟都可能出現(xiàn)失敗,最終會造成大量的數(shù)據(jù)不一致,比如訂單狀態(tài)更新失敗,扣款卻成功了;或者扣款失敗,庫存卻扣減了等情況,這個在業(yè)務(wù)上是不能接受的,會出現(xiàn)大量的客訴。
如果直接應(yīng)用事務(wù),不使用分布式事務(wù),比如在代碼中添加 Spring 的聲明式事務(wù) @Transactional 注解,這樣做實際上是在事務(wù)中嵌套了遠(yuǎn)程服務(wù)調(diào)用,一旦服務(wù)調(diào)用出現(xiàn)超時,事務(wù)無法提交,就會導(dǎo)致數(shù)據(jù)庫連接被占用,出現(xiàn)大量的阻塞和失敗,會導(dǎo)致服務(wù)宕機。另一方面,如果沒有定義額外的回滾操作,比如遇到異常,非 DB 的服務(wù)調(diào)用失敗時,則無法正確執(zhí)行回滾。
業(yè)務(wù)系統(tǒng)改造
下面應(yīng)用 TCC 事務(wù),需要對業(yè)務(wù)代碼改造,抽象 Try、Confirm 和 Cancel 階段。
Try 操作
Try 操作一般都是鎖定某個資源,設(shè)置一個預(yù)備的狀態(tài),凍結(jié)部分?jǐn)?shù)據(jù)。比如,訂單服務(wù)添加一個預(yù)備狀態(tài),修改為 UPDATING,也就是更新中的意思,凍結(jié)當(dāng)前訂單的操作,而不是直接修改為支付成功。
庫存服務(wù)設(shè)置凍結(jié)庫存,可以擴展字段,也可以額外添加新的庫存凍結(jié)表。積分服務(wù)和庫存一樣,添加一個預(yù)增加積分,比如本次訂單積分是 100,添加一個額外的存儲表示等待增加的積分,賬戶余額服務(wù)等也是一樣的操作。
Confirm 操作
Confirm 操作就是把前邊的 Try 操作鎖定的資源提交,類比數(shù)據(jù)庫事務(wù)中的 Commit 操作。在支付的場景中,包括訂單狀態(tài)從準(zhǔn)備中更新為支付成功;庫存數(shù)據(jù)扣減凍結(jié)庫存,積分?jǐn)?shù)據(jù)增加預(yù)增加積分。
Cancel 操作
Cancel 操作執(zhí)行的是業(yè)務(wù)上的回滾處理,類比數(shù)據(jù)庫事務(wù)中的 Rollback 操作。首先訂單服務(wù),撤銷預(yù)備狀態(tài),還原為待支付狀態(tài)或者已取消狀態(tài),庫存服務(wù)刪除凍結(jié)庫存,添加到可銷售庫存中,積分服務(wù)也是一樣,將預(yù)增加積分扣減掉。
執(zhí)行業(yè)務(wù)操作
下面來分析業(yè)務(wù)的實際執(zhí)行操作,首先業(yè)務(wù)請求過來,開始執(zhí)行 Try 操作,如果 TCC 分布式事務(wù)框架感知到各個服務(wù)的 Try 階段都成功了以后,就會執(zhí)行各個服務(wù)的 Confirm 邏輯。
如果 Try 階段有操作不能正確執(zhí)行,比如訂單失效、庫存不足等,就會執(zhí)行 Cancel 的邏輯,取消事務(wù)提交。
TCC 對比 2PC 兩階段提交
TCC 事務(wù)模型的思想類似 2PC 提交,下面對比 TCC 和基于 2PC 事務(wù) XA 規(guī)范對比。
對比 2PC 提交

第一階段
在 XA 事務(wù)中,各個 RM 準(zhǔn)備提交各自的事務(wù)分支,事實上就是準(zhǔn)備提交資源的更新操作(insert、delete、update 等);而在 TCC 中,是主業(yè)務(wù)操作請求各個子業(yè)務(wù)服務(wù)預(yù)留資源。
第二階段
XA 事務(wù)根據(jù)第一階段每個 RM 是否都 prepare 成功,判斷是要提交還是回滾。如果都 prepare 成功,那么就 commit 每個事務(wù)分支,反之則 rollback 每個事務(wù)分支。
在 TCC 中,如果在第一階段所有業(yè)務(wù)資源都預(yù)留成功,那么進(jìn)入 Confirm 步驟,提交各個子業(yè)務(wù)服務(wù),完成實際的業(yè)務(wù)處理,否則進(jìn)入 Cancel 步驟,取消資源預(yù)留請求。
與 2PC/XA 兩階段提交的區(qū)別
2PC/XA 是數(shù)據(jù)庫或者存儲資源層面的事務(wù),實現(xiàn)的是強一致性,在兩階段提交的整個過程中,一直會持有數(shù)據(jù)庫的鎖。
TCC 關(guān)注業(yè)務(wù)層的正確提交和回滾,在 Try 階段不涉及加鎖,是業(yè)務(wù)層的分布式事務(wù),關(guān)注最終一致性,不會一直持有各個業(yè)務(wù)資源的鎖。
TCC 的核心思想是針對每個業(yè)務(wù)操作,都要添加一個與其對應(yīng)的確認(rèn)和補償操作,同時把相關(guān)的處理,從數(shù)據(jù)庫轉(zhuǎn)移到業(yè)務(wù)中,以此實現(xiàn)跨數(shù)據(jù)庫的事務(wù)。
TCC 分布式服務(wù)組件
在業(yè)務(wù)中引入 TCC 一般是依賴單獨的 TCC 事務(wù)框架,可以選擇自研或者應(yīng)用開源組件。TCC 框架扮演了資源管理器的角色,常用的 TCC 開源組件有 Tcc-transaction、ByteTCC、Spring-cloud-rest-tcc 等。
前面介紹過的 Seata,可以選擇 TCC 事務(wù)模式,也支持了 AT 模式及 Saga 模式。
以 Tcc-transaction 為例,源碼托管在 Github-tcc-transaction,提供了對 Spring 和 Dubbo 的適配,感興趣的話可以查看 tcc-transaction-tutorial-sample 學(xué)習(xí)。

總結(jié)
這一課時介紹了 TCC 分布式事務(wù)模型的應(yīng)用,通過一個實際例子分析了如何應(yīng)用 TCC 對業(yè)務(wù)系統(tǒng)進(jìn)行改造,并且對比了 TCC 和 2PC 兩階段提交,以及 TCC 相關(guān)的開源組件。