用Java輕鬆完成一個分散式事務TCC,保姆級教程

葉東富發表於2021-09-26

什麼是TCC,TCC是Try、Confirm、Cancel三個詞語的縮寫,最早是由 Pat Helland 於 2007 年發表的一篇名為《Life beyond Distributed Transactions:an Apostate’s Opinion》的論文提出。

TCC組成

TCC分為3個階段

  • Try 階段:嘗試執行,完成所有業務檢查(一致性), 預留必須業務資源(準隔離性)
  • Confirm 階段:如果所有分支的Try都成功了,則走到Confirm階段。Confirm真正執行業務,不作任何業務檢查,只使用 Try 階段預留的業務資源
  • Cancel 階段:如果所有分支的Try有一個失敗了,則走到Cancel階段。Cancel釋放 Try 階段預留的業務資源。

TCC分散式事務裡,有3個角色,與經典的XA分散式事務一樣:

  • AP/應用程式,發起全域性事務,定義全域性事務包含哪些事務分支
  • RM/資源管理器,負責分支事務各項資源的管理
  • TM/事務管理器,負責協調全域性事務的正確執行,包括Confirm,Cancel的執行,並處理網路異常

如果我們要進行一個類似於銀行跨行轉賬的業務,轉出(TransOut)和轉入(TransIn)分別在不同的微服務裡,一個成功完成的TCC事務典型的時序圖如下:

image.png

TCC實踐

下面我們進行一個TCC事務的具體開發

我們的例子使用的分散式事務框架為dtm,它對分散式事務的支援非常優雅。下面來詳細講解TCC的組成

下面我們來編寫具體的Try/Confirm/Cancel的處理函式

@RequestMapping("TransOutTry")
    public Map<String, String> TransOutTry() {
        logger.info("TransOutTry");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

    @RequestMapping("TransOutConfirm")
    public Map<String, String> TransOutConfirm(HttpServerResponse response) {
        logger.info("TransOutConfirm");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

    @RequestMapping("TransOutCancel")
    public Map<String, String> TransOutCancel() {
        logger.info("TransOutCancel");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

    @RequestMapping("TransInTry")
    public Map<String, String> TransInTry() {
        logger.info("TransInTry");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

    @RequestMapping("TransInConfirm")
    public Map<String, String> TransInConfirm() {
        logger.info("TransInConfirm");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

    @RequestMapping("TransInCancel")
    public Map<String, String> TransInCancel() {
        logger.info("TransInCancel");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "SUCCESS");
        return result;
    }

到此各個子事務的處理函式已經OK了,然後是開啟TCC事務,進行分支呼叫

       @RequestMapping("fireTcc")
    public String fireTcc() {
        Function<Tcc, Boolean> function = TccController::tccTrans;
        return tcc.tccGlobalTransaction(function);
    }

    public static Boolean tccTrans(Tcc tcc) {
        try {
            boolean a = tcc.callBranch("", svc + "/TransOutTry", svc + "/TransOutConfirm", svc + "/TransOutCancel");
            boolean b = tcc.callBranch("", svc + "/TransInTry", svc + "/TransInConfirm", svc + "/TransInCancel");
            return a && b;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

至此,一個完整的TCC分散式事務編寫完成。

如果您想要完整執行一個成功的示例,那麼參考這個例子yedf/dtmcli-java-sample,將它執行起來非常簡單

# 部署啟動dtm
# 需要docker版本18以上
git clone https://github.com/yedf/dtm
cd dtm
docker-compose up

# 另起一個命令列
git clone https://github.com/yedf/dtmcli-java-sample.git
cd dtmcli-java-sample
# 編譯執行例子 main/src/main/java/com/github/viticis/dtmclijavaexamples/DtmcliJavaSampleApplication

TCC的回滾

假如銀行將金額準備轉入使用者2時,發現使用者2的賬戶異常,返回失敗,會怎麼樣?我們可以讓TransIn返回失敗來模擬這種情況

    @RequestMapping("TransInTry")
    public Map<String, String> TransInTry() {
        logger.info("TransInTry");
        Map<String, String> result = new HashMap<>();
        result.put("dtm_result", "FAILURE");
        return result;
    }

我們給出事務失敗互動的時序圖

image.png

這個跟成功的TCC差別就在於,當某個子事務返回失敗後,後續就回滾全域性事務,呼叫各個子事務的Cancel操作,保證全域性事務全部回滾。

在TCC事務模式上,有不少的讀者會問,如果Confirm/Cancel失敗會怎麼樣?這是一個好問題,代表您正在深入思考TCC事務模式。第一種情況是臨時失敗,例如網路故障、應用或資料庫當機,這類錯誤進行重試,最後會返回成功;另一種情況為業務失敗,按照TCC的協議,第一階段鎖定資源,保證足夠的資源能夠讓Confirm/Cancel執行,也就是說,程式邏輯上,Confirm/Cancel是不允許返回業務失敗的,如果出現業務失敗,那麼是bug,需要開發人員手動修復bug。

小結

在這篇文章裡,我們介紹了TCC的理論知識,也通過一個例子,完整給出了編寫一個TCC事務的過程,涵蓋了正常成功完成,以及成功回滾的情況。相信讀者通過這邊文章,對TCC已經有了深入的理解。

關於分散式事務更多更全面的知識,請參考《分散式事務最經典的七種解決方案》

文中使用的例子節選自yedf/dtm,它支援多種事務模式:TCC、SAGA、XA、事務訊息 跨語言支援,已支援 golang、python、Java、PHP、nodejs等語言的客戶端,參考各語言SDK。提供子事務屏障功能,優雅解決冪等、懸掛、空補償等問題。

閱讀完此篇乾貨,歡迎大家訪問https://github.com/yedf/dtm專案,給顆星星支援!

相關文章