Tx-lcn 源碼解析:基於lcn模式下的正常流程源碼(3)

一、代理Connect的獲取

TX-LCN中的LCN模式是通過代理數據庫連接,進而對事務進行控制的。通過靜態代理的方式包裝的原本來的Connection,並設置爲手動提交,根據事務狀態控制提交與回滾。

代理連接代碼如下,state爲事務狀態,state=1表示分佈式事務成功,本地連接提交,其它爲失敗進行本地連接回滾。



package com.codingapi.txlcn.tc.core.transaction.lcn.resource;

import com.codingapi.txlcn.txmsg.dto.RpcResponseState;
import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * @author CoderTnT
 */
@Slf4j
public class LcnConnectionProxy implements Connection {

    private Connection connection;

    public LcnConnectionProxy(Connection connection) {
        this.connection = connection;
    }

    /**
     * notify connection
     *
     * @param state transactionState
     * @return RpcResponseState RpcResponseState
     */
    public RpcResponseState notify(int state) {
        try {
            if (state == 1) {
                log.debug("commit transaction type[lcn] proxy connection:{}.", this);
                connection.commit();
            } else {
                log.debug("rollback transaction type[lcn] proxy connection:{}.", this);
                connection.rollback();
            }
            connection.close();
            log.debug("transaction type[lcn] proxy connection:{} closed.", this);
            return RpcResponseState.success;
        } catch (Exception e) {
            log.error(e.getLocalizedMessage(), e);
            return RpcResponseState.fail;
        }
    }
    
    
    // autoCommit設置false,表示 連接關閉自動提交,採用手動提交模式
    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(false);
    }

    @Override
    public void commit() throws SQLException {
        connection.commit();
    }

    @Override
    public void rollback() throws SQLException {
        connection.rollback();
    }

    @Override
    public void close() throws SQLException {
        connection.close();
    }


    @Override
    public boolean getAutoCommit() throws SQLException {
        return connection.getAutoCommit();
    }
    /*
    * 忽略其他無關代碼。。。
    */

}
LcnConnectionProxy代理連接的獲取是通過AOP實現的
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered {

    private final TxClientConfig txClientConfig;

    private final DTXResourceWeaver dtxResourceWeaver;

    public DataSourceAspect(TxClientConfig txClientConfig, DTXResourceWeaver dtxResourceWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxResourceWeaver = dtxResourceWeaver;
    }

    
    //環繞通知獲取代理connection連接
    @Around("execution(* javax.sql.DataSource.getConnection(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        return dtxResourceWeaver.getConnection(() -> (Connection) point.proceed());
    }


    @Override
    public int getOrder() {
        return txClientConfig.getResourceOrder();
    }


}
@Component
@Slf4j
public class DTXResourceWeaver {

    private final TxLcnBeanHelper txLcnBeanHelper;

    public DTXResourceWeaver(TxLcnBeanHelper txLcnBeanHelper) {
        this.txLcnBeanHelper = txLcnBeanHelper;
    }

    public Object getConnection(ConnectionCallback connectionCallback) throws Throwable {
        //獲取事務本地上下文 dtxLocalContext
        DTXLocalContext dtxLocalContext = DTXLocalContext.cur();
        if (Objects.nonNull(dtxLocalContext) && dtxLocalContext.isProxy()) {
            //獲取事務本地上下文的事務類型  dtxLocalContext的事務類型
            String transactionType = dtxLocalContext.getTransactionType();
            //根據事務類型獲取事務代理器:LcnTransactionResourceProxy或TccTransactionResourceProxy或TxcTransactionResourceProxy
            TransactionResourceProxy resourceProxy = txLcnBeanHelper.loadTransactionResourceProxy(transactionType);
            //由事務代理器根據connectionCallback Connection提供者 構造生成代理連接connection對象
            Connection connection = resourceProxy.proxyConnection(connectionCallback);
            log.debug("proxy a sql connection: {}.", connection);
            return connection;
        }
        return connectionCallback.call();
    }
}

二、整體正常執行的代碼流程

實現分佈式事務需要在特定執行的service方法上增加一個註解@LcnTransaction,這也是一個入口,整個流程的開始就在這裏。

TransactionAspect類是一個切面類,對@LcnTransaction註解設置了環繞通知,在碰到註解@LcnTransaction開始了整個流程。

2.1 分佈式事務執行方法的環繞

@Aspect    //切面類註解
@Component
@Slf4j
public class TransactionAspect implements Ordered {

    private final TxClientConfig txClientConfig;

    private final DTXLogicWeaver dtxLogicWeaver;

    public TransactionAspect(TxClientConfig txClientConfig, DTXLogicWeaver dtxLogicWeaver) {
        this.txClientConfig = txClientConfig;
        this.dtxLogicWeaver = dtxLogicWeaver;
    }

    /**
     * DTC Aspect Common type() can lcn,tcc,txc and custom DTX type
     */
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TxTransaction)")
    public void txTransactionPointcut() {
    }

    /**
     * DTC Aspect (Type of LCN)
     */
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.LcnTransaction)")
    public void lcnTransactionPointcut() {
    }

    /**
     * DTC Aspect (Type of TXC)
     */
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TxcTransaction)")
    public void txcTransactionPointcut() {
    }

    /**
     * DTC Aspect (Type of TCC)
     */
    @Pointcut("@annotation(com.codingapi.txlcn.tc.annotation.TccTransaction)")
    public void tccTransactionPointcut() {
    }


    @Around("txTransactionPointcut()")
    public Object transactionRunning(ProceedingJoinPoint point) throws Throwable {
        //見2.1.1
        DTXInfo dtxInfo = DTXInfo.getFromCache(point);
        TxTransaction txTransaction = dtxInfo.getBusinessMethod().getAnnotation(TxTransaction.class);
        dtxInfo.setTransactionType(txTransaction.type());
        dtxInfo.setTransactionPropagation(txTransaction.propagation());
        return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
    }

    @Around("lcnTransactionPointcut() && !txcTransactionPointcut()" +
            "&& !tccTransactionPointcut() && !txTransactionPointcut()")
    public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
        //根據ProceedingJoinPoint  從緩存中獲取事務信息dtxInfo
        DTXInfo dtxInfo = DTXInfo.getFromCache(point);
        //獲取註解LcnTransaction,因爲註解LcnTransaction上配置不同的傳播行爲propagation,如REQUIRED,SUPPORTS等
        LcnTransaction lcnTransaction = dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
        //設置dtxInfo的事務類型爲LCN
        dtxInfo.setTransactionType(Transactions.LCN);
        //設置事務傳播行爲,發起方是REQUIRED,參與方式是SUPPORTS
        dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
        //織入後 開始執行事務
        //見2.1.2
        return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
    }
   /*
   *  忽略其他代碼。。。
   */
}

2.1.1 在註解的開始從緩存中獲取了事務信息dtxInfo

 public static DTXInfo getFromCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 根據切入點 獲取方法簽名信息signature
        String signature = proceedingJoinPoint.getSignature().toString();
        //根據方法簽名生成事務單元ID
        String unitId = Transactions.unitId(signature);
        //根據事務單元ID獲取事務信息dtxInfo
        DTXInfo dtxInfo = dtxInfoCache.get(unitId);
        if (Objects.isNull(dtxInfo)) {
            // 根據切入點 獲取方法簽名信息methodSignature
            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            //獲取要執行的方法,這是接口的方法並不是我們要的方法
            Method method = methodSignature.getMethod();
            //獲取目標類
            Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
            //獲取真實的實現方法
            Method thisMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
            //構造dtxInfo 對象
            dtxInfo = new DTXInfo(thisMethod, proceedingJoinPoint.getArgs(), targetClass);
            //緩存dtxInfo 對象 鍵:事務單元ID,值:dtxInfo
            dtxInfoCache.put(unitId, dtxInfo);
        }
        dtxInfo.reanalyseMethodArgs(proceedingJoinPoint.getArgs());
        return dtxInfo;
    }

2.1.2 事務執行runTransaction方法

public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {
        // 1、獲取或新建一個線程變量DTXLocalContext
        if (Objects.isNull(DTXLocalContext.cur())) {
            DTXLocalContext.getOrNew();
        } else {
            return business.call();
        }

        log.debug("<---- TxLcn start ---->");
        DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
        TxContext txContext;
        // ---------- 保證每個模塊在一個DTX下只會有一個TxContext ---------- //
        //2、如果已經存在事務上下文則獲取,否則構建事務上下文TxContext 
        if (globalContext.hasTxContext()) {
            // 有全局TC事務上下文的獲取上下文
            txContext = globalContext.txContext();
            dtxLocalContext.setInGroup(true);
            log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
        } else {
            // 沒有的開啓本地事務上下文
            // 見2.1.4
            txContext = globalContext.startTx();
        }

        // 本地事務調用
        if (Objects.nonNull(dtxLocalContext.getGroupId())) {
            dtxLocalContext.setDestroy(false);
        }
        //3、爲DTXLocalContext設置參數
        dtxLocalContext.setUnitId(dtxInfo.getUnitId());
        dtxLocalContext.setGroupId(txContext.getGroupId());
        dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());

        // 事務參數
        //4、構造TxTransactionInfo 並設置參數
        TxTransactionInfo info = new TxTransactionInfo();
        info.setBusinessCallback(business);
        info.setGroupId(txContext.getGroupId());
        info.setUnitId(dtxInfo.getUnitId());
        info.setPointMethod(dtxInfo.getBusinessMethod());
        info.setPropagation(dtxInfo.getTransactionPropagation());
        info.setTransactionInfo(dtxInfo.getTransactionInfo());
        info.setTransactionType(dtxInfo.getTransactionType());
        info.setTransactionStart(txContext.isDtxStart());

        //LCN事務處理器
        try {
            //執行5、執行事務 事務業務執行
            return transactionServiceExecutor.transactionRunning(info);
        } 
        //6、finally做一些通知與銷燬操作。
        //見2.1.5
        finally {
            // 線程執行業務完畢清理本地數據
            if (dtxLocalContext.isDestroy()) {
                // 通知事務執行完畢
                synchronized (txContext.getLock()) {
                    txContext.getLock().notifyAll();
                }

                // TxContext生命週期是和事務組一樣(不與具體模塊相關的)
                if (!dtxLocalContext.isInGroup()) {
                    globalContext.destroyTx();
                }

                DTXLocalContext.makeNeverAppeared();
                TracingContext.tracing().destroy();
            }
            log.debug("<---- TxLcn end ---->");
        }
    }

2.1.3 主要說下2.1.2代碼註釋中的第2點什麼情況下要獲取已有的TxContext ,什麼時候構造新的

如果一個調用鏈是這樣的A->B->C 三個都是不同的模塊則會爲每個模塊都構造新的TxContext,保證一個模塊一個。

如果三個是兩個模塊,如A是一個模塊,B和C是一個模塊根據調用鏈的情況肯定是C的用B的TxContext。

2.1.4 構建新的TxContext

public TxContext startTx() {
        TxContext txContext = new TxContext();
        // 事務發起方判斷
        txContext.setDtxStart(!TracingContext.tracing().hasGroup());
        // 是否爲事務發起方
        if (txContext.isDtxStart()) {
            //是的話開始 初始化事務組操作,主要是構造事務組groupId,並放入以groupId和appMap爲鍵的Map 到tracingContext對象緩存中
            TracingContext.tracing().beginTransactionGroup();
        }
        //設置txContext事務組groupId
        txContext.setGroupId(TracingContext.tracing().groupId());
        String txContextKey = txContext.getGroupId() + ".dtx";
        //key與txContext緩存起來
        attachmentCache.attach(txContextKey, txContext);
        log.debug("Start TxContext[{}]", txContext.getGroupId());
        return txContext;
    }

2.1.5

 

 

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