分佈式事務:TCC

開源源碼地址:https://github.com/changmingxie/tcc-transaction.git

項目demo地址:https://github.com/liuchaoOvO/tcc-transaction-demo.git

源碼修改成springboot 源碼地址:https://github.com/liuchaoOvO/tcc-transaction.git

主要涉及如下三個 Maven 項目:

  • tcc-transaction-core :tcc-transaction 底層實現。

  • tcc-transaction-api :tcc-transaction 使用 API。

  • tcc-transaction-spring :tcc-transaction Spring 支持。

目錄

TCC事務簡介

架構模型圖

與 2PC協議 比較

源碼

添加tcc-transaction的使用方法


TCC事務簡介

爲了解決在事務運行過程中,大顆粒度資源鎖定的問題,提出一種新的事務模型,它是基於業務層面的事務定義。鎖粒度完全由業務自己控制。它本質是一種補償的思路。它把事務運行過程分成 Try、Confirm / Cancel 兩個階段。在每個階段的邏輯由業務代碼控制。這樣就事務的鎖粒度可以完全自由控制。業務可以在犧牲隔離性的情況下,獲取更高的性能。本質上,TCC 通過多個參與者的 try / confirm / cancel 方法,實現事務的最終一致性

架構模型圖

在這裏插入圖片描述

  • Try 階段

    • 完成所有業務檢查( 一致性 )

    • 預留必須業務資源( 準隔離性 )

    • Try :嘗試執行業務

  • Confirm / Cancel 階段:

    • 釋放 Try 階段預留的業務資源

    • Cancel 操作滿足冪等性(先getXXXID()得到對應Try 嘗試保存的數據,再補償取消)

    • 真正執行業務

    • 不做任務業務檢查

    • Confirm 操作滿足冪等性

    • Confirm :確認執行業務

    • Cancel :取消執行業務

    • Confirm 與 Cancel 互斥

與 2PC協議 比較

  • 沒有單獨的準備( Prepare )階段,Try 操作兼備自願操作與準備能力

  • Try 操作可以靈活選擇業務資源的鎖定粒度

  • 較高開發成本

 在這裏插入圖片描述

 

TCC柔性補償性事務:嘗試/ 確定/ 取消  三個接口都要我們自己去實現

在 TCC 裏,一個業務活動可以有多個事務,每個業務操作歸屬於不同的事務,即一個事務可以包含多個業務操作。TCC-Transaction 將每個業務操作抽象成事務參與者,每個事務可以包含多個參與者

TCC-Transaction 有兩個攔截器,通過對 @Compensable AOP 切面( 參與者 try 方法 )進行攔截,透明化對參與者 confirm / cancel 方法調用,從而實現 TCC 。

兩種攔截器:

可補償事務攔截器:CompensableTransactionInterceptor

資源協調者攔截器:ResourceCoordinatorInterceptor

第一個攔截器,可補償事務攔截器,實現如下功能:

  • 在 Try 階段,對事務的發起、傳播。

  • 在 Confirm / Cancel 階段,對事務提交或回滾。

  • 爲什麼會有對事務的傳播呢?  在遠程調用服務的參與者時,會通過"參數"( 需要序列化 )的形式傳遞事務給遠程參與者。

第二個攔截器,資源協調者攔截器,實現如下功能:

  • 在 Try 階段,添加參與者到事務中。當事務上下文不存在時,進行創建。

源碼

1、參與者對象 Participant :

public class Participant implements Serializable {

   /**
     * 事務編號
     */
    private TransactionXid xid;
    /**
     * 確認執行業務方法調用上下文
     */
    private InvocationContext confirmInvocationContext;
    /**
     * 取消執行業務方法
     */
    private InvocationContext cancelInvocationContext;
    /**
     * 執行器
     */
    private Terminator terminator = new Terminator();
    /**
     * 事務上下文編輯
     */
    Class<? extends TransactionContextEditor> transactionContextEditorClass;

    /**
     * 提交事務
     */
    public void commit() {
        terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
    }

    /**
     * 回滾事務
     */
    public void rollback() {
        terminator.invoke(new TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass);
    }

    ...
}

2、InvocationContext。

執行方法調用上下文,記錄類、方法名、參數類型數組、參數數組。通過這些屬性,可以執行提交 / 回滾事務。在 org.mengyun.tcctransaction.Terminator 會看到具體的代碼實現。

3、Terminator 執行器

public class Terminator implements Serializable {
private static final long serialVersionUID = -164958655471605778L;

public Object invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class&lt;? extends TransactionContextEditor&gt; transactionContextEditorClass) {
    if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {
        try {
            // 獲得 參與者對象
            Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance();
            // 獲得 方法
            Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
            // 設置 事務上下文 到方法參數
            FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs());
            // 執行方法
            return method.invoke(target, invocationContext.getArgs());
        } catch (Exception e) {
            throw new SystemException(e);
        }
    }
    return null;
}

4、事務管理器 TransactionManager

事務管理器不關心接口是怎麼實現的,只會根據情況去調用。1、啓動事務。2、調用嘗試的接口,3、提交或者失敗調用取消接口。

TransactionManager

發起根事務

transactionManager 提供 begin() 方法,發起根事務。該方法在調用方法類型爲 MethodType.ROOT 並且 事務處於 Try 階段被調用。

TCC-Transaction 支持多個的事務獨立存在,後創建的事務先提交(原因:ThreadLocal<Deque<Transaction>>),類似 Spring 的org.springframework.transaction.annotation.Propagation.REQUIRES_NEW 

傳播發起分支事務

transactionManager 調用 #propagationNewBegin(...) 方法,傳播發起分支事務。該方法在調用方法類型爲 MethodType.PROVIDER 並且 事務處於 Try 階段被調用。

傳播獲取分支事務

transactionManager 調用 #propagationExistBegin(...) 方法,傳播發起分支事務。該方法在調用方法類型爲 MethodType.PROVIDER 並且 事務處於 Confirm / Cancel 階段被調用。

提交事務

transactionManager 調用 #commit(...) 方法,提交事務。該方法在事務處於 Confirm / Cancel 階段被調用。

回滾事務

transactionManager 調用 #rollback(...) 方法,取消事務,和 #commit() 方法基本類似。該方法在事務處於 Confirm / Cancel 階段被調用。

添加參與者到事務

調用 #enlistParticipant(...) 方法,添加參與者到事務。該方法在事務處於 Try 階段被調用。

5、事務攔截器方法

package org.mengyun.tcctransaction.interceptor;
public class CompensableTransactionInterceptor {

    private TransactionManager transactionManager;

    private Set<Class<? extends Exception>> delayCancelExceptions;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }	
    public void setDelayCancelExceptions(Set<Class<? extends Exception>> delayCancelExceptions) {
        this.delayCancelExceptions = delayCancelExceptions;
    }

	//事務攔截器通過AOP環繞到加了註解@Compensable的方法,環繞執行下面的方法
    public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
		//獲取到加了註解的方法本身
        Method method = CompensableMethodUtils.getCompensableMethod(pjp);
		//獲取到註解@Compensable自己本身 --> 獲取註解的屬性:取消的方法,確認的方法
        Compensable compensable = method.getAnnotation(Compensable.class);
        Propagation propagation = compensable.propagation();
        //獲取事務上下文,RPC隱式參數中包含這個上下文
        TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs());

        boolean asyncConfirm = compensable.asyncConfirm();

        boolean asyncCancel = compensable.asyncCancel();
	    //判斷是否有存在的事務隊列
        boolean isTransactionActive = transactionManager.isTransactionActive();
		
        if (!TransactionUtils.isLegalTransactionContext(isTransactionActive, propagation, transactionContext)) {
            throw new SystemException("no active compensable transaction while propagation is mandatory for method " + method.getName());
        }

        MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);
		/*
		*這個方法判斷:(!isTransactionActive && transactionContext == null)是否已經有事務 
        了,事務上下文是否爲空
		都滿足說明式主事務,否則是分支事務
		*/
        switch (methodType) {
            case ROOT:
                return rootMethodProceed(pjp, asyncConfirm, asyncCancel);
            case PROVIDER:
                return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel);
            default:
                return pjp.proceed();
        }
    }

	/**這裏是主事務的操作
	開啓事務
	註冊,持久化事務
	然後判斷是cancel,confirm
	清除事務
	*/
    private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable {

        Object returnValue = null;

        Transaction transaction = null;

        try {
			事務爲空那麼就開啓一個全新的事務
            transaction = transactionManager.begin();

            try {
            //繼續後續操作
                returnValue = pjp.proceed();
            } catch (Throwable tryingException) {

                if (!isDelayCancelException(tryingException)) {
                   
                    logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);

                    transactionManager.rollback(asyncCancel);
                }

                throw tryingException;
            }

            transactionManager.commit(asyncConfirm);

        } finally {
            transactionManager.cleanAfterCompletion(transaction);
        }

        return returnValue;
    }

    private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext, boolean asyncConfirm, boolean asyncCancel) throws Throwable {

        Transaction transaction = null;
        try {

            switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
                case TRYING:
                    transaction = transactionManager.propagationNewBegin(transactionContext);
                    return pjp.proceed();
                case CONFIRMING:
                    try {
                        transaction = transactionManager.propagationExistBegin(transactionContext);
                        transactionManager.commit(asyncConfirm);
                    } catch (NoExistedTransactionException excepton) {
                        //the transaction has been commit,ignore it.
                    }
                    break;
                case CANCELLING:

                    try {
                        transaction = transactionManager.propagationExistBegin(transactionContext);
                        transactionManager.rollback(asyncCancel);
                    } catch (NoExistedTransactionException exception) {
                        //the transaction has been rollback,ignore it.
                    }
                    break;
            }

        } finally {
            transactionManager.cleanAfterCompletion(transaction);
        }

        Method method = ((MethodSignature) (pjp.getSignature())).getMethod();

        return ReflectionUtils.getNullValue(method.getReturnType());
    }

    private boolean isDelayCancelException(Throwable throwable) {

        if (delayCancelExceptions != null) {
            for (Class delayCancelException : delayCancelExceptions) {

                Throwable rootCause = ExceptionUtils.getRootCause(throwable);

                if (delayCancelException.isAssignableFrom(throwable.getClass())
                        || (rootCause != null && delayCancelException.isAssignableFrom(rootCause.getClass()))) {
                    return true;
                }
            }
        }

        return false;
    }

}

添加tcc-transaction的使用方法

1、需要提供分佈式事務支持的接口上添加@Compensable

2、在對應的接口實現上添加@Compensable

3、在接口實現上添加confirmMethod、cancelMethod、transactionContextEditor

4、實現對應的confirmMethod、cancelMethod   注意:confirm方法和cancel方法必須與try方法在同一個類中.。(根據類的反射找到方法invoke)

注意:

1、分佈式事務裏,不要輕易在業務層捕獲所有異常,拋出異常才能被TCC感知到,然後回調。只捕獲必要捕獲的異常。

2、使用TCC-Transaction時,confirm和cancel的冪等性需要自己編寫代碼支持

 

 

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