分佈式事務之TCC模式實現 TCCJ 變種模式代碼

一、前言

嚴格遵守ACID的分佈式事務我們稱爲剛性事務,而遵循BASE理論(基本可用:在故障出現時保證核心功能可用,軟狀態:允許中間狀態出現,最終一致性:不要求分佈式事務打成中時間點數據都是一致性的,但是保證達到某個時間點後,數據就處於了一致性了)的事務我們稱爲柔性事務,其中TCC編程模式就屬於柔性事務,本文我們來闡述其理論。

二、TCC編程模式

TCC編程模式本質上也是一種二階段協議,不同在於TCC編程模式需要與具體業務耦合,下面首先看下TCC編程模式步驟:

  • 所有事務參與方都需要實現try,confirm,cancle接口。
  • 事務發起方向事務協調器發起事務請求,事務協調器調用所有事務參與者的try方法完成資源的預留,這時候並沒有真正執行業務,而是爲後面具體要執行的業務預留資源,這裏完成了一階段。(狀態機加入)
  • 如果事務協調器發現有參與者的try方法預留資源時候發現資源不夠,則調用參與方的cancle方法回滾預留的資源,需要注意cancle方法需要實現業務冪等,因爲有可能調用失敗(比如網絡原因參與者接受到了請求,但是由於網絡原因事務協調器沒有接受到回執)會重試。(補償機制)

  • 如果事務協調器發現所有參與者的try方法返回都OK,則事務協調器調用所有參與者的confirm方法,不做資源檢查,直接進行具體的業務操作。

  • 如果協調器發現所有參與者的confirm方法都OK了,則分佈式事務結束。
  • 如果協調器發現有些參與者的confirm方法失敗了,或者由於網絡原因沒有收到回執,則協調器會進行重試。這裏如果重試一定次數後還是失敗,會怎麼樣那?常見的是做事務補償。

螞蟻金服基於TCC實現了XTS(雲上叫DTS),目前在螞蟻金服雲上有對外輸出,這裏我們來結合其提供的一個例子來具體理解TCC的含義,以下引入螞蟻金服雲實例:

“首先我們假想這樣一種場景:轉賬服務,從銀行 A 某個賬戶轉 100 元錢到銀行 B 的某個賬戶,銀行 A 和銀行 B 可以認爲是兩個單獨的系統,也就是兩套單獨的數據庫。

我們將賬戶系統簡化成只有賬戶和餘額 2 個字段,並且爲了適應 DTS 的兩階段設計要求,業務上又增加了一個凍結金額(凍結金額是指在一筆轉賬期間,在一階段的時候使用該字段臨時存儲轉賬金額,該轉賬額度不能被使用,只有等這筆分佈式事務全部提交成功時,纔會真正的計入可用餘額)。按這樣的設計,用戶的可用餘額等於賬戶餘額減去凍結金額。這點是理解參與者設計的關鍵,也是 DTS 保證最終一致的業務約束。”

在try階段並沒有對銀行A和B數據庫中的餘額字段做操作,而是對凍結金額做的操作,對應A銀行預留資源操作是對凍結金額加上100元,這時候A銀行賬號上可用錢爲餘額字段-凍結金額;對應B銀行的操作是對凍結金額上減去100,這時候B銀行賬號上可用的錢爲餘額字段-凍結金額。

如果事務協調器調用銀行A和銀行B的try方法有一個失敗了(比如銀行A的賬戶餘額不夠了),則調用cancle進行回滾操作(具體是對凍結金額做反向操作)。如果調用try方法都OK了,則進入confirm階段,confirm階段則不做資源檢查,直接做業務操作,對應銀行A要在賬戶餘額減去100,然後凍金額減去100;對應銀行B要對賬戶餘額字段加上100,然後凍結金額加上100。

最關心的,如果confirm階段如果有一個參與者失敗了,該如何處理,其實上面操作都是xts-client做的,還有一個xts-server專門做事務補償的。

三、總結

TCC是對二階段的一個改進,try階段通過預留資源的方式避免了同步阻塞資源的情況,但是TCC編程需要業務自己實現try,confirm,cancle方法,對業務入侵太大,實現起來也比較複雜。

 

四,實戰代碼

最後我寫了一個TCC變種分佈式事務模板=> TCCJ可供參考:  我這裏,我這裏的judge模塊是對try模塊產生結果的審覈, 審覈不通過執行回滾操作.



/**
 * Description: 分佈式事務執行模板
 * <p>
 *     1. 先執行各個服務塊業務
 *     2. 執行結束通過confirm判定執行結果, 如果失敗則進行取消(回滾操作)
 *     3. 如果執行服務模塊發生異常,則判定後進行
 * </p>
 * User: zhouzhou
 * Date: 2018-08-27
 * Time: 10:27
 */
public class TccTemplate {

    private static final Logger logger = LoggerFactory.getLogger(TccCallBack.class);

    /**
     *  分佈式事務模板
     * @param tccCallBack 分佈式事務執行回調
     * @param method 當前方法名(封裝參數, 可方便撈取數據)
     */
    public static <T> TccResult process(TccCallBack tccCallBack, String method, T t) {
        // 返回一個消息用於
        TccResult tccResult = new TccResult();
        String msg = "";
        try {
            // 執行主業務
            tccCallBack.tryExecute();
            // 進行確認執行結果,如果結果是false,則執行回滾操作
            boolean judge = tccCallBack.judge();
            if (judge) {
                tccResult.setStatus(true);
                msg = String.format("分佈式事務{%s}執行成功", method);
                logger.info(msg);
                // 執行確認操作
                tccCallBack.confirm();
            } else {
                tccResult.setStatus(false);
                msg = String.format("分佈式事務{%s}執行失敗,進行回滾操作", method);
                logger.warn(msg);
                tccCallBack.cancel();
            }
        } catch (Exception e) {
            // 主流程發生異常, 則直接執行回滾操作
            tccResult.setStatus(false);
            msg = String.format("分佈式事務{%s}執行發生異常{%s},進行回滾操作", method,e.getMessage());
            logger.warn(String.format("分佈式事務{%s}執行發生異常,進行回滾操作", method), e);
            tccCallBack.cancel();
        }finally {
            // 返回結果Result
            tccResult.setMsg(msg);
            return tccResult;
        }


    }
}

當然你需要編寫回調接口:



/**
 * Description: 基於TCC變成模式的分佈式事務回調
 * User: zhouzhou
 * Date: 2018-08-27
 * Time: 10:20
 */
public interface TccCallBack {

    /**
     * 執行主要分佈式業務操作
     */
    void tryExecute();

    /**
     * 確認分佈式業務操作最終結果,
     * 如果返回true,則不執行cancel,返回false則執行cancel
     */
    boolean judge();

    /**
     * 取消操作
     */
    void cancel();
    
    /**
     * 確認操作
     */
    void confirm();
   
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章