1. 實戰(zhàn)!阿里神器 Seata 實現(xiàn) TCC 模式解決分布式事務(wù)

        共 6791字,需瀏覽 14分鐘

         ·

        2022-05-01 03:20

        今天這篇文章介紹一下Seata如何實現(xiàn)TCC事務(wù)模式,文章目錄如下:

        83fa08aaf35bde35e0e7e7cb091425d7.webp目錄

        什么是TCC模式?

        TCC(Try Confirm Cancel)方案是一種應(yīng)用層面侵入業(yè)務(wù)的兩階段提交。是目前最火的一種柔性事務(wù)方案,其核心思想是:針對每個操作,都要注冊一個與其對應(yīng)的確認和補償(撤銷)操作。

        TCC分為兩個階段,分別如下:

        • 第一階段:Try(嘗試),主要是對業(yè)務(wù)系統(tǒng)做檢測及資源預(yù)留 (加鎖,鎖住資源)
        • 第二階段:本階段根據(jù)第一階段的結(jié)果,決定是執(zhí)行confirm還是cancel
        1. Confirm(確認):執(zhí)行真正的業(yè)務(wù)(執(zhí)行業(yè)務(wù),釋放鎖)
        2. Cancle(取消):是預(yù)留資源的取消(出問題,釋放鎖)
        fa405bd7b56c81a71ca4317737c8795d.webpTCC

        為了方便理解,下面以電商下單為例進行方案解析,這里把整個過程簡單分為扣減庫存,訂單創(chuàng)建 2 個步驟,庫存服務(wù)和訂單服務(wù)分別在不同的服務(wù)器節(jié)點上。

        假設(shè)商品庫存為 100,購買數(shù)量為 2,這里檢查和更新庫存的同時,凍結(jié)用戶購買數(shù)量的庫存,同時創(chuàng)建訂單,訂單狀態(tài)為待確認。

        ①Try 階段

        TCC 機制中的 Try 僅是一個初步操作,它和后續(xù)的確認一起才能真正構(gòu)成一個完整的業(yè)務(wù)邏輯,這個階段主要完成:

        • 完成所有業(yè)務(wù)檢查( 一致性 ) 。
        • 預(yù)留必須業(yè)務(wù)資源( 準(zhǔn)隔離性 ) 。
        • Try 嘗試執(zhí)行業(yè)務(wù)。
        88b0c572d4c9e3e888131d2cc7b71d94.webpTry階段

        ②Confirm / Cancel 階段

        根據(jù) Try 階段服務(wù)是否全部正常執(zhí)行,繼續(xù)執(zhí)行確認操作(Confirm)或取消操作(Cancel)。

        Confirm 和 Cancel 操作滿足冪等性,如果 Confirm 或 Cancel 操作執(zhí)行失敗,將會不斷重試直到執(zhí)行完成。

        Confirm:當(dāng) Try 階段服務(wù)全部正常執(zhí)行, 執(zhí)行確認業(yè)務(wù)邏輯操作,業(yè)務(wù)如下圖:

        6225591fad1f5b3ecad5ec4422dc1b27.webpTry->Confirm

        這里使用的資源一定是 Try 階段預(yù)留的業(yè)務(wù)資源。在 TCC 事務(wù)機制中認為,如果在 Try 階段能正常的預(yù)留資源,那 Confirm 一定能完整正確的提交。

        Confirm 階段也可以看成是對 Try 階段的一個補充,Try+Confirm 一起組成了一個完整的業(yè)務(wù)邏輯。

        Cancel:當(dāng) Try 階段存在服務(wù)執(zhí)行失敗, 進入 Cancel 階段,業(yè)務(wù)如下圖:

        08ba6b95615ef6d3de543d37b41e9acc.webpTry-Cancel

        Cancel 取消執(zhí)行,釋放 Try 階段預(yù)留的業(yè)務(wù)資源,上面的例子中,Cancel 操作會把凍結(jié)的庫存釋放,并更新訂單狀態(tài)為取消。

        TCC模式的三種類型?

        業(yè)內(nèi)實際生產(chǎn)中對TCC模式進行了擴展,總結(jié)出了如下三種類型,其實從官方的定義中無此說法,不過是企業(yè)生產(chǎn)中根據(jù)實際的需求衍生出來的三種方案。

        1、通用型 TCC 解決方案

        通用型TCC解決方案是最經(jīng)典的TCC事務(wù)模型的實現(xiàn),正如第一節(jié)介紹的模型,所有的從業(yè)務(wù)都參與到主業(yè)務(wù)的決策中。

        fecc01366eae10c381597b9752936626.webp通用型TCC

        適用場景

        由于從業(yè)務(wù)服務(wù)是同步調(diào)用,其結(jié)果會影響到主業(yè)務(wù)服務(wù)的決策,因此通用型 TCC 分布式事務(wù)解決方案適用于執(zhí)行時間確定且較短的業(yè)務(wù),比如電商系統(tǒng)的三個核心服務(wù):訂單服務(wù)、賬戶服務(wù)、庫存服務(wù)。

        這個三個服務(wù)要么同時成功,要么同時失敗。

        4b4c079a0611ec49b29f51365073948d.webp

        當(dāng)庫存服務(wù)、賬戶服務(wù)的第二階段調(diào)用完成后,整個分布式事務(wù)完成。

        2、異步確保型 TCC 解決方案

        異步確保型 TCC 解決方案的直接從業(yè)務(wù)服務(wù)是可靠消息服務(wù),而真正的從業(yè)務(wù)服務(wù)則通過消息服務(wù)解耦,作為消息服務(wù)的消費端,異步地執(zhí)行。

        093b7a3fd778908f8aab5d697b1942bb.webp異步確保型

        可靠消息服務(wù)需要提供 Try,Confirm,Cancel 三個接口。Try 接口預(yù)發(fā)送,只負責(zé)持久化存儲消息數(shù)據(jù);Confirm 接口確認發(fā)送,這時才開始真正的投遞消息;Cancel 接口取消發(fā)送,刪除消息數(shù)據(jù)。

        消息服務(wù)的消息數(shù)據(jù)獨立存儲,獨立伸縮,降低從業(yè)務(wù)服務(wù)與消息系統(tǒng)間的耦合,在消息服務(wù)可靠的前提下,實現(xiàn)分布式事務(wù)的最終一致性。

        此解決方案雖然增加了消息服務(wù)的維護成本,但由于消息服務(wù)代替從業(yè)務(wù)服務(wù)實現(xiàn)了 TCC 接口,從業(yè)務(wù)服務(wù)不需要任何改造,接入成本非常低。

        適用場景:

        由于從業(yè)務(wù)服務(wù)消費消息是一個異步的過程,執(zhí)行時間不確定,可能會導(dǎo)致不一致時間窗口增加。因此,異步確保性 TCC 分布式事務(wù)解決方案只適用于對最終一致性時間敏感度較低的一些被動型業(yè)務(wù)(從業(yè)務(wù)服務(wù)的處理結(jié)果不影響主業(yè)務(wù)服務(wù)的決策,只被動的接收主業(yè)務(wù)服務(wù)的決策結(jié)果)。比如會員注冊服務(wù)和郵件發(fā)送服務(wù):

        83c334458eb441c4db9342f7caf77efb.webp

        3、補償型 TCC 解決方案

        補償型 TCC 解決方案與通用型 TCC 解決方案的結(jié)構(gòu)相似,其從業(yè)務(wù)服務(wù)也需要參與到主業(yè)務(wù)服務(wù)的活動決策當(dāng)中。但不一樣的是,前者的從業(yè)務(wù)服務(wù)只需要提供 Do 和 Compensate 兩個接口,而后者需要提供三個接口。

        c30acfe4a021f165d6c89bc1c5b049be.webp

        Do 接口直接執(zhí)行真正的完整業(yè)務(wù)邏輯,完成業(yè)務(wù)處理,業(yè)務(wù)執(zhí)行結(jié)果外部可見;Compensate 操作用于業(yè)務(wù)補償,抵消或部分抵消正向業(yè)務(wù)操作的業(yè)務(wù)結(jié)果,Compensate操作需滿足冪等性。

        與通用型解決方案相比,補償型解決方案的從業(yè)務(wù)服務(wù)不需要改造原有業(yè)務(wù)邏輯,只需要額外增加一個補償回滾邏輯即可,業(yè)務(wù)改造量較小。但要注意的是,業(yè)務(wù)在一階段就執(zhí)行完整個業(yè)務(wù)邏輯,無法做到有效的事務(wù)隔離,當(dāng)需要回滾時,可能存在補償失敗的情況,還需要額外的異常處理機制,比如人工介入。

        適用場景

        由于存在回滾補償失敗的情況,補償型 TCC 分布式事務(wù)解決方案只適用于一些并發(fā)沖突較少或者需要與外部交互的業(yè)務(wù),這些外部業(yè)務(wù)不屬于被動型業(yè)務(wù),其執(zhí)行結(jié)果會影響主業(yè)務(wù)服務(wù)的決策。

        以上部分內(nèi)容參考自:https://seata.io/zh-cn/blog/tcc-mode-applicable-scenario-analysis.html?utm_source=gold_browser_extension

        TCC事務(wù)模式的落地實現(xiàn)

        在前面文章中介紹了Seata的AT模式,當(dāng)Seata支持的事務(wù)模式不局限于AT模式,還有TCC模式、SAGA模式、XA模式,下面整合一下TCC模式。

        1、演示場景

        就以電商系統(tǒng)中下訂單為例,為了演示,直接去掉賬戶服務(wù),以訂單服務(wù)、庫存服務(wù)為例介紹。

        具體的邏輯如下:

        1. 客戶端調(diào)用下訂單接口
        2. 扣庫存
        3. 創(chuàng)建訂單
        4. 請求完成

        根據(jù)上面的邏輯可知,訂單服務(wù)肯定是主業(yè)務(wù)服務(wù),事務(wù)的發(fā)起方,庫存服務(wù)是從業(yè)務(wù)服務(wù),參與事務(wù)的決策。

        Seata的AT模式解決方案偽代碼如下:

        @GlobalTransactional
        public?Result?createOrder(Long?productId,Long?num,.....){
        ????//1、扣庫存
        ????reduceStorage();
        ????//2、創(chuàng)建訂單
        ????saveOrder();
        }

        @GlobalTransactional這個注解用于發(fā)起一個全局事務(wù)。

        但是AT模式有局限性,如下:

        • 性能低,鎖定資源時間太長
        • 無法解決跨應(yīng)用的事務(wù)

        因此對于要求性能的下單接口,可以考慮使用TCC模式進行拆分成兩階段執(zhí)行,這樣整個流程鎖定資源的時間將會變短,性能也能提高。

        此時的TCC模式的拆分如下:

        1、一階段的Try操作

        TCC模式中的Try階段其實就是預(yù)留資源,在這個過程中可以將需要的商品數(shù)量的庫存凍結(jié),這樣就要在庫存表中維護一個凍結(jié)的庫存這個字段。

        偽代碼如下:

        @Transactional
        public?boolean?try(){
        ??//凍結(jié)庫存
        ??frozenStorage();
        ??//生成訂單,狀態(tài)為待確認
        ??saveOrder();
        }

        注意:@Transactional開啟了本地事務(wù),只要出現(xiàn)了異常,本地事務(wù)將會回滾,同時執(zhí)行第二階段的cancel操作。

        2、二階段的confirm操作

        confirm操作在一階段try操作成功之后提交事務(wù),涉及到的操作如下:

        1. 釋放try操作凍結(jié)的庫存(凍結(jié)庫存-購買數(shù)量)
        2. 生成訂單

        偽代碼如下:

        @Transactional
        public?boolean?confirm(){
        ????//釋放掉try操作預(yù)留的庫存
        ????cleanFrozen();
        ????//修改訂單,狀態(tài)為已完成
        ????updateOrder();
        ????return?true;
        }

        注意:這里如果返回false,遵循TCC規(guī)范,應(yīng)該要不斷重試,直到confirm完成。

        3、二階段的cancel操作

        cancel操作在一階段try操作出現(xiàn)異常之后執(zhí)行,用于回滾資源,涉及到的操作如下:

        1. 恢復(fù)凍結(jié)的庫存(凍結(jié)庫存-購買數(shù)量、庫存+購買數(shù)量)
        2. 刪除訂單

        偽代碼如下:

        @Transactional
        public?boolean?cancel(){
        ????//釋放掉try操作預(yù)留的庫存
        ????rollbackFrozen();
        ????//修改訂單,狀態(tài)為已完成
        ????delOrder();
        ????return?true;
        }

        注意:這里如果返回false,遵循TCC規(guī)范,應(yīng)該要不斷重試,直到cancel完成。

        2、TCC事務(wù)模型的三個異常

        實現(xiàn)TCC事務(wù)模型涉及到的三個異常是不可避免的,實際生產(chǎn)中必須要規(guī)避這三大異常。

        1、空回滾

        定義:在未調(diào)用try方法或try方法未執(zhí)行成功的情況下,就執(zhí)行了cancel方法進行了回滾。

        怎么理解呢?未調(diào)用try方法就執(zhí)行了cancel方法,這個很容易理解,既然沒有預(yù)留資源,那么肯定是不能回滾。

        try方法未執(zhí)行成功是什么意思?

        可以看上節(jié)中的第一階段try方法的偽代碼,由于try方法開啟了本地事務(wù),一旦try方法執(zhí)行過程中出現(xiàn)了異常,將會導(dǎo)致try方法的本地事務(wù)回滾(注意這里不是cancel方法回滾,而是try方法的本地事務(wù)回滾),這樣其實try方法中的所有操作都將會回滾,也就沒有必要調(diào)用cancel方法。

        但是實際上一旦try方法拋出了異常,那么必定是要調(diào)用cancel方法進行回滾,這樣就導(dǎo)致了空回滾。

        解決方案

        解決邏輯很簡單:在cancel方法執(zhí)行操作之前,必須要知道try方法是否執(zhí)行成功。

        2、冪等性

        TCC模式定義中提到:如果confirm或者cancel方法執(zhí)行失敗,要一直重試直到成功。

        這里就涉及了冪等性,confirm和cancel方法必須保證同一個全局事務(wù)中的冪等性。

        解決方案

        解決邏輯很簡單:對付冪等,自然是要利用冪等標(biāo)識進行防重操作。

        3、懸掛

        事務(wù)協(xié)調(diào)器在調(diào)用 TCC 服務(wù)的一階段 Try 操作時,可能會出現(xiàn)因網(wǎng)絡(luò)擁堵而導(dǎo)致的超時,此時事務(wù)管理器會觸發(fā)二階段回滾,調(diào)用 TCC 服務(wù)的 Cancel 操作,Cancel 調(diào)用未超時;

        在此之后,擁堵在網(wǎng)絡(luò)上的一階段 Try 數(shù)據(jù)包被 TCC 服務(wù)收到,出現(xiàn)了二階段 Cancel 請求比一階段 Try 請求先執(zhí)行的情況,此 TCC 服務(wù)在執(zhí)行晚到的 Try 之后,將永遠不會再收到二階段的 Confirm 或者 Cancel ,造成 TCC 服務(wù)懸掛。

        解決方案

        解決邏輯很簡單:在執(zhí)行try方法操作資源之前判斷cancel方法是否已經(jīng)執(zhí)行;同樣的在cancel方法執(zhí)行后要記錄執(zhí)行的狀態(tài)。

        4、總結(jié)

        針對以上三個異常,落地的解決方案很多,比如維護一個事務(wù)狀態(tài)表,每個事務(wù)的執(zhí)行階段全部記錄下來。

        • 冪等:在執(zhí)行confirm或者cancel之前根據(jù)事務(wù)狀態(tài)表查詢當(dāng)前全局事務(wù)是否已經(jīng)執(zhí)行過confirm或者cancel方法
        • 空回滾:在執(zhí)行cancel之前才能根據(jù)事務(wù)狀態(tài)表查詢當(dāng)前全局事務(wù)是否已經(jīng)執(zhí)行成功try方法
        • 懸掛:在執(zhí)行try方法之前,根據(jù)事務(wù)狀態(tài)表查詢當(dāng)前全局事務(wù)是否已經(jīng)執(zhí)行過cancel方法

        Seata整合TCC實現(xiàn)

        關(guān)于如何搭建項目、添加依賴這里就不再細說了,本節(jié)介紹一下關(guān)鍵代碼。

        源碼目錄如下:

        fcff75c5d45378611fa9918fd326fc66.webp源碼目錄

        項目啟動所需要的相關(guān)文件如下圖:

        bfbb202102979d45e108556b42e3bef8.webp

        nacos目錄中的SEATA_GROUP是Seata事務(wù)服務(wù)端和客戶端所需要的相關(guān)配置,直接導(dǎo)入nacos即可。

        seata目錄中的conf是1.3.0版本服務(wù)端的配置

        SQL目錄是相關(guān)的幾個數(shù)據(jù)庫。

        1、TCC接口定義

        order-boot模塊創(chuàng)建OrderTccService,代碼如下:

        fe99837fa2616b63214d01e5bb47a3a2.webp

        代碼中注釋已經(jīng)很完整了,下面挑幾個重點介紹一下:

        1. @LocalTCC:該注解開啟TCC事務(wù)
        2. @TwoPhaseBusinessAction:該注解標(biāo)注在try方法上,其中的三個屬性如下:
          1. name:TCC事務(wù)的名稱,必須是唯一的
          2. commitMethod:confirm方法的名稱,默認是commit
          3. rollbackMethod:cancel方法的名稱,,默認是rollback
        3. confirm和cancel的返回值尤為重要,返回false則會不斷的重試。

        2、TCC接口實現(xiàn)

        定義有了,總要實現(xiàn),如下:

        1、try方法

        8949fd49bf45d901ddeff07b8854579f.webptry方法

        處的代碼是為了防止懸掛異常,從事務(wù)日志表中獲取全局事務(wù)ID的狀態(tài),如果是cancel狀態(tài)則不執(zhí)行。

        處的代碼凍結(jié)庫存

        處的代碼生成訂單,狀態(tài)為待確認

        處的代碼向冪等工具類中添加一個標(biāo)記,key當(dāng)前類全局事務(wù)IDvalue為當(dāng)前時間戳。

        注意:必須要開啟本地事務(wù),如上代碼使用@Transactional開啟本地事務(wù)

        2、confirm方法

        2e8954ab8fa341843d5cdf41f3574e15.webpconfirm方法

        處的代碼從冪等工具類中根據(jù)當(dāng)前類和全局事務(wù)ID獲取值,由于try階段執(zhí)行成功會向其中添加值,confirm方法執(zhí)行成功會移出這個值,因此在confirm開頭判斷這個值是否存在就起到了冪等效果,防止重試的效果。

        處的代碼從冪等工具類中移出try方法中添加的值。

        處的代碼是從BusinessActionContext中獲取try方法中的入?yún)ⅰ?/p>

        處的代碼是釋放掉凍結(jié)的庫存

        處的代碼是修改訂單的狀態(tài)為已完成。

        注意:1. 開啟本地事務(wù) ?2. 注意返回值,返回false時將會重試

        3、cancel方法

        b7b3857adf2df709eb4bc9631f45f253.webpcancel方法

        處的代碼是向事務(wù)日志記錄表中插入一條數(shù)據(jù),標(biāo)記當(dāng)前事務(wù)進入cancel方法,用來防止懸掛,這個和try方法中的處的代碼相呼應(yīng)。

        處的代碼是為了防止冪等和空回滾,因為只有當(dāng)try方法中執(zhí)行成功冪等工具類中對應(yīng)的當(dāng)前類和全局事務(wù)ID才會存儲該值。這樣既防止了冪等,也防止了空回滾。

        處的代碼恢復(fù)凍結(jié)的庫存。

        處的代碼刪除這筆訂單

        處的代碼是移出冪等工具類當(dāng)前類和全局事務(wù)ID對應(yīng)的值。

        3、如何防止TCC模型的三個異常?

        實現(xiàn)方法有很多,有些案例是全部使用事務(wù)日志表記錄當(dāng)前的狀態(tài),這樣完美的解決了冪等、空回滾、懸掛的問題。

        陳某這里為了方便,使用了兩種方案,如下:

        1、冪等、空回滾

        使用了一個冪等工具類,其中是個Map,key為當(dāng)前類和全局事務(wù)ID,value是時間戳。

        代碼如下:

        bb6845bfb0c0548e409595ac301a7301.webp

        思路如下:

        1. 在try方法最后使用冪等工具類中的add方法添加值
        2. 在confirm、cancel方法中使用冪等工具類中的remove方法移出值
        3. 在confirm、cancel方法中使用冪等工具類中g(shù)et方法獲取值,如果為空,則表示已經(jīng)執(zhí)行過了,直接返回true,這樣既防止了冪等,也防止了空回滾。

        2、懸掛

        懸掛的實現(xiàn)依靠的是事務(wù)日志表,表結(jié)構(gòu)如下:

        CREATE?TABLE?`transactional_record`?(
        ??`id`?bigint(11)?NOT?NULL?AUTO_INCREMENT,
        ??`xid`?varchar(100)?NOT?NULL,
        ??`status`?int(1)?DEFAULT?NULL?COMMENT?'1.?try??2?commit?3?cancel?',
        ??PRIMARY?KEY?(`id`)?USING?BTREE
        )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4;

        其中的xid是全局事務(wù)ID,status是事務(wù)的狀態(tài)。

        其他的字段自己可以擴展

        解決懸掛問題的邏輯如下:

        1. cancel方法中將當(dāng)前全局事務(wù)ID記錄到事務(wù)日志表中,狀態(tài)為cancel
        2. try方法執(zhí)行資源操作前檢查事務(wù)日志表中當(dāng)前全局事務(wù)ID是否已經(jīng)是cancel狀態(tài)

        4、創(chuàng)建訂單的業(yè)務(wù)方法

        上面只是完成了TCC的三個方法,主業(yè)務(wù)事務(wù)發(fā)起方還未提供,代碼如下:

        37342b3b6c5a4e275d35bf829707ab6c.webp

        @GlobalTransactional這個注解開啟了全局事務(wù),是事務(wù)的發(fā)起方。

        內(nèi)部直接調(diào)用的TCC的try方法。

        5、其他的配置

        以上只是列出了關(guān)鍵的步驟,剩余其他的配置自己根據(jù)案例源碼完善,如下:

        1. 接口測試
        2. 整合nacos
        3. 整合feign
        4. 整合seata,TCC模式中的配置和AT模式的Seata配置相同

        注意:一定要配置Seata的事務(wù)組tx-service-group,配置方法見之前的文章。

        6、總結(jié)

        TCC事務(wù)模型相對來說比較簡單的一種,有興趣的可以下載源碼試試。

        75a65998432104eac0d1461dc5239cf6.webp

        往期推薦

        ebed0d473663bbd286bf45d4781bc6c0.webp

        synchronized和ReentrantLock的5個區(qū)別!


        f4e806b815facf0e52b0001b6275d996.webp

        oppo后端16連問


        b67b8f875bd66cef05466e566b5a8289.webp

        怎么解決MySQL死鎖問題的?


        1e1fcac98267e8bbbefed1ad52ceb674.webp

        瀏覽 56
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 久久精品秘 一区二区三区 | 裸体未来初音被到爽 | 操东北女人视频 | 美日韩黄片 | 操视频这里只有精品 |