TX-LCN分佈式事務框架源碼解析(基於lcn模式下的正常流程源碼分析)

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

所以如果用LCN模式的分佈式事務,必須有本地連接,即要操作本地數據庫。(當然也可以多種模式混合使用)

代理連接如下,state爲事務狀態,1爲分佈式事務成功本地鏈接提交,其他爲失敗進行本地鏈接回滾

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;
        }
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(false);
    }

代理連接的獲取是用Aop實現的

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;
    }

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

    private final TxLcnBeanHelper txLcnBeanHelper;

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

    public Object getConnection(ConnectionCallback connectionCallback) throws Throwable {
        //事務本地上下文
        DTXLocalContext dtxLocalContext = DTXLocalContext.cur();
        //如果設置爲代理模式
        if (Objects.nonNull(dtxLocalContext) && dtxLocalContext.isProxy()) {
            String transactionType = dtxLocalContext.getTransactionType();
            //根據事物類型獲取代理器
            TransactionResourceProxy resourceProxy = txLcnBeanHelper.loadTransactionResourceProxy(transactionType);
            //構造代理連接對象
            Connection connection = resourceProxy.proxyConnection(connectionCallback);
            log.debug("proxy a sql connection: {}.", connection);
            return connection;
        }
        //如果不是代理直接返回原始連接
        return connectionCallback.call();
    }
}

以上是一些鋪墊

下面介紹整體流程,例子以TX-LCN分佈式事務框架應用與解析-2例子爲標準進行講解

在例子中我們就在調用接口的地方加上了一個註解@LcnTransaction,這是一個入口整個流程的開始就在這裏。

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

public class TransactionAspect implements Ordered {

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

    @Around("lcnTransactionPointcut() && !txcTransactionPointcut()" +
            "&& !tccTransactionPointcut() && !txTransactionPointcut()")
    public Object runWithLcnTransaction(ProceedingJoinPoint point) throws Throwable {
        //根據ProceedingJoinPoint 從緩存中獲取事務信息
        DTXInfo dtxInfo = DTXInfo.getFromCache(point);
        //獲取註解
        LcnTransaction lcnTransaction = dtxInfo.getBusinessMethod().getAnnotation(LcnTransaction.class);
        //設置事務類型爲LCN
        dtxInfo.setTransactionType(Transactions.LCN);
        //設置事務傳播行爲,發起方是REQUIRED,參與方式是SUPPORTS
        dtxInfo.setTransactionPropagation(lcnTransaction.propagation());
        //開始執行事務
        return dtxLogicWeaver.runTransaction(dtxInfo, point::proceed);
    }

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

這裏做了個緩存,當再次執行這個方法時則直接取得事務信息。這個方法的作用就是根據當前要執行的類名、方法名和方法參數構造事務信息對象,存入緩存。

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

2、事務執行runTransaction方法

public Object runTransaction(DTXInfo dtxInfo, BusinessCallback business) throws Throwable {

        if (Objects.isNull(DTXLocalContext.cur())) {
            DTXLocalContext.getOrNew();
        } else {
            return business.call();
        }

        log.debug("<---- TxLcn start ---->");
        DTXLocalContext dtxLocalContext = DTXLocalContext.getOrNew();
        TxContext txContext;
        // ---------- 保證每個模塊在一個DTX下只會有一個TxContext ---------- //
        if (globalContext.hasTxContext()) {
            // 有事務上下文的獲取父上下文
            txContext = globalContext.txContext();
            dtxLocalContext.setInGroup(true);
            log.debug("Unit[{}] used parent's TxContext[{}].", dtxInfo.getUnitId(), txContext.getGroupId());
        } else {
            // 沒有的開啓本地事務上下文
            txContext = globalContext.startTx();
        }

        // 本地事務調用
        if (Objects.nonNull(dtxLocalContext.getGroupId())) {
            dtxLocalContext.setDestroy(false);
        }

        dtxLocalContext.setUnitId(dtxInfo.getUnitId());
        dtxLocalContext.setGroupId(txContext.getGroupId());
        dtxLocalContext.setTransactionType(dtxInfo.getTransactionType());

        // 事務參數
        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 {
            return transactionServiceExecutor.transactionRunning(info);
        } 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 ---->");
        }
    }

方法比較長,但是邏輯比較簡單

1、獲取或新建一個線程變量DTXLocalContext

2、如果已經存在事務上下文則獲取,否則構建事務上下文

3、爲DTXLocalContext設置參數

4、構造TxTransactionInfo 並設置參數

5、執行事務

6、finally做一些通知與銷燬操作。

其他還好,主要說下第二點什麼情況下要獲取已有的TxContext ,什麼時候構造新的

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

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

鑑於本例中都是單獨的模塊所以對於每一個模塊來說都是新創建TxContext

public TxContext startTx() {
        TxContext txContext = new TxContext();
        // 事務發起方判斷
        txContext.setDtxStart(!TracingContext.tracing().hasGroup());
        //是否是事務發起方
        if (txContext.isDtxStart()) {
            //是的話開始事務組,主要是構造事務id
            TracingContext.tracing().beginTransactionGroup();
        }
        //設置事務組id
        txContext.setGroupId(TracingContext.tracing().groupId());
        String txContextKey = txContext.getGroupId() + ".dtx";
        //key與txContext緩存起來
        attachmentCache.attach(txContextKey, txContext);
        log.debug("Start TxContext[{}]", txContext.getGroupId());
        return txContext;
    }

對於本例中a是事務發起方,其他的兩個是事務參與方

第6點雖然東西不多但是有必要講下

            // 線程執行業務完畢清理本地數據
            if (dtxLocalContext.isDestroy()) {
                // 通知事務執行完畢
                synchronized (txContext.getLock()) {
                    txContext.getLock().notifyAll();
                }

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

                DTXLocalContext.makeNeverAppeared();
                TracingContext.tracing().destroy();
            }
public void destroyTx() {
        if (!hasTxContext()) {
            throw new IllegalStateException("non TxContext.");
        }
        destroyTx(txContext().getGroupId());
    }
public void destroyTx(String groupId) {
        attachmentCache.remove(groupId + ".dtx");
        log.debug("Destroy TxContext[{}]", groupId);
    }

業務執行完會把attachmentCache的key移除掉,後面在參與方clean事務的時候纔不會await。

3、transactionRunning方法執行事務

public Object transactionRunning(TxTransactionInfo info) throws Throwable {

        // 1. 獲取事務類型,LCN
        String transactionType = info.getTransactionType();

        // 2. 獲取事務傳播狀態
        DTXPropagationState propagationState = propagationResolver.resolvePropagationState(info);

        // 2.1 如果不參與分佈式事務立即終止
        if (propagationState.isIgnored()) {
            return info.getBusinessCallback().call();
        }

        // 3. 獲取本地分佈式事務控制器
        DTXLocalControl dtxLocalControl = txLcnBeanHelper.loadDTXLocalControl(transactionType, propagationState);

        // 4. 織入事務操作
        try {
            // 4.1 記錄事務類型到事務上下文
            Set<String> transactionTypeSet = globalContext.txContext(info.getGroupId()).getTransactionTypes();
            transactionTypeSet.add(transactionType);
            //執行前
            dtxLocalControl.preBusinessCode(info);

            // 4.2 業務執行前
            txLogger.txTrace(
                    info.getGroupId(), info.getUnitId(), "pre business code, unit type: {}", transactionType);

            // 4.3 執行業務
            Object result = dtxLocalControl.doBusinessCode(info);

            // 4.4 業務執行成功
            txLogger.txTrace(info.getGroupId(), info.getUnitId(), "business success");
            dtxLocalControl.onBusinessCodeSuccess(info, result);
            return result;
        } catch (TransactionException e) {
            txLogger.error(info.getGroupId(), info.getUnitId(), "before business code error");
            throw e;
        } catch (Throwable e) {
            // 4.5 業務執行失敗
            txLogger.error(info.getGroupId(), info.getUnitId(), Transactions.TAG_TRANSACTION,
                    "business code error");
            dtxLocalControl.onBusinessCodeError(info, e);
            throw e;
        } finally {
            // 4.6 業務執行完畢
            dtxLocalControl.postBusinessCode(info);
        }
    }

事務控制器根據事物類型與傳播狀態執行的也不同

獲取傳播類型,發起方是create,參與方是join

public DTXPropagationState resolvePropagationState(TxTransactionInfo txTransactionInfo) throws TransactionException {

        // 本地已在DTX,根據事務傳播,靜默加入
        if (DTXLocalContext.cur().isInGroup()) {
            log.info("SILENT_JOIN group!");
            return DTXPropagationState.SILENT_JOIN;
        }

        // 發起方之前沒有事務
        if (txTransactionInfo.isTransactionStart()) {
            // 根據事務傳播,對於 SUPPORTS 不參與事務
            if (DTXPropagation.SUPPORTS.equals(txTransactionInfo.getPropagation())) {
                return DTXPropagationState.NON;
            }
            // 根據事務傳播,創建事務
            return DTXPropagationState.CREATE;
        }

        // 已經存在DTX,根據事務傳播,加入
        return DTXPropagationState.JOIN;
    }

事務發起方控制器是LcnStartingTransaction事務參與方控制器是LcnRunningTransaction

這裏分爲四步執行業務前、執行業務,執行業務後、最後finally。

1、執行業務前preBusinessCode方法

1)發起方。

通過netty調用服務端創建事務組

設置DTXLocalContext的proxy爲true。

public void preBusinessCode(TxTransactionInfo info) throws TransactionException {
        // create DTX group
        transactionControlTemplate.createGroup(
                info.getGroupId(), info.getUnitId(), info.getTransactionInfo(), info.getTransactionType());

        // lcn type need connection proxy
        DTXLocalContext.makeProxy();
    }
public void createGroup(String groupId, String unitId, TransactionInfo transactionInfo, String transactionType)
            throws TransactionException {
        //創建事務組
        try {
            // 日誌
            txLogger.txTrace(groupId, unitId,
                    "create group > transaction type: {}", transactionType);
            // 創建事務組消息
            reliableMessenger.createGroup(groupId);
            // 緩存發起方切面信息
            aspectLogger.trace(groupId, unitId, transactionInfo);
        } catch (RpcException e) {
            // 通訊異常
            dtxExceptionHandler.handleCreateGroupMessageException(groupId, e);
        } catch (LcnBusinessException e) {
            // 創建事務組業務失敗
            dtxExceptionHandler.handleCreateGroupBusinessException(groupId, e.getCause());
        }
        txLogger.txTrace(groupId, unitId, "create group over");
    }
public void createGroup(String groupId) throws RpcException, LcnBusinessException {
        // TxManager創建事務組
        MessageDto messageDto = request(MessageCreator.createGroup(groupId));
        if (!MessageUtils.statusOk(messageDto)) {
            throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
        }
    }

服務端接收到創建事務組信息後會用CreateGroupExecuteService的execute方法進行處理

public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
        try {
            transactionManager.begin(transactionCmd.getGroupId());
        } catch (TransactionException e) {
            throw new TxManagerException(e);
        }
        txLogger.txTrace(transactionCmd.getGroupId(), null, "created group");
        return null;
    }
public void begin(String groupId) throws TransactionException {
        try {
            dtxContextRegistry.create(groupId);
        } catch (TransactionException e) {
            throw new TransactionException(e);
        }
    }
 public DTXContext create(String groupId) throws TransactionException {
        try {
            fastStorage.initGroup(groupId);
        } catch (FastStorageException e) {
            // idempotent processing
            if (e.getCode() != FastStorageException.EX_CODE_REPEAT_GROUP) {
                throw new TransactionException(e);
            }
        }
        return get(groupId);
    }
public void initGroup(String groupId) {
        redisTemplate.opsForHash().put(REDIS_GROUP_PREFIX + groupId, "root", "");
        redisTemplate.expire(REDIS_GROUP_PREFIX + groupId, managerConfig.getDtxTime() + 10000, TimeUnit.MILLISECONDS);
    }

服務端會以hash結構在redis創建一個key。hashkey爲“tm:group:”+groupid,key爲root,value爲空。超時時間爲分佈式事務超時時間8s加上10s一共18秒。

2)參與方

設置DTXLocalContext的proxy爲true。

 public void preBusinessCode(TxTransactionInfo info) {
        // lcn type need connection proxy
        DTXLocalContext.makeProxy();
    }

2、執行業務方法doBusinessCode

對於執行業務方法,就是執行我們的方法的內容。主要是調用其他服務,而其他服務又會有@LcnTransaction註解,又會走同樣的流程,只是處理類不一樣了,這裏都會寫出不同處理類的處理方式

3、業務執行成功方法onBusinessCodeSuccess

1)發起方

設置系統分佈式事務狀態爲1成功

public void onBusinessCodeSuccess(TxTransactionInfo info, Object result) {
        DTXLocalContext.cur().setSysTransactionState(1);
    }

2)參與方

加入事務組

 public void onBusinessCodeSuccess(TxTransactionInfo info, Object result) throws TransactionException {
        log.debug("join group: [GroupId: {},Method: {}]" , info.getGroupId(),
                info.getTransactionInfo().getMethodStr());
        
        // join DTX group
        transactionControlTemplate.joinGroup(info.getGroupId(), info.getUnitId(), info.getTransactionType(),
                info.getTransactionInfo());
    }
public void joinGroup(String groupId, String unitId, String transactionType, TransactionInfo transactionInfo)
            throws TransactionException {
        try {
            txLogger.txTrace(groupId, unitId, "join group > transaction type: {}", transactionType);
            //加入事務組
            reliableMessenger.joinGroup(groupId, unitId, transactionType, DTXLocalContext.transactionState(globalContext.dtxState(groupId)));

            txLogger.txTrace(groupId, unitId, "join group message over.");

            // 異步檢測,用定時任務去查看是否有異常事務
            dtxChecking.startDelayCheckingAsync(groupId, unitId, transactionType);

            // 緩存參與方切面信息
            aspectLogger.trace(groupId, unitId, transactionInfo);
        } catch (RpcException e) {
            dtxExceptionHandler.handleJoinGroupMessageException(Arrays.asList(groupId, unitId, transactionType), e);
        } catch (LcnBusinessException e) {
            dtxExceptionHandler.handleJoinGroupBusinessException(Arrays.asList(groupId, unitId, transactionType), e);
        }
        txLogger.txTrace(groupId, unitId, "join group logic over");
    }
public void joinGroup(String groupId, String unitId, String unitType, int transactionState) throws RpcException, LcnBusinessException {
        JoinGroupParams joinGroupParams = new JoinGroupParams();
        joinGroupParams.setGroupId(groupId);
        joinGroupParams.setUnitId(unitId);
        joinGroupParams.setUnitType(unitType);
        joinGroupParams.setTransactionState(transactionState);
        MessageDto messageDto = request(MessageCreator.joinGroup(joinGroupParams));
        if (!MessageUtils.statusOk(messageDto)) {
            throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
        }
    }

客戶端會調用服務端去把當前操作加入到事務組中。

服務端收到後會調用JoinGroupExecuteService類的execute進行處理

public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
        try {
            //根據事務組id獲取事務上下文
            DTXContext dtxContext = dtxContextRegistry.get(transactionCmd.getGroupId());
            //從消息中獲取參數
            JoinGroupParams joinGroupParams = transactionCmd.getMsg().loadBean(JoinGroupParams.class);
            txLogger.txTrace(transactionCmd.getGroupId(), joinGroupParams.getUnitId(), "unit:{} try join group:{}",
                    joinGroupParams.getUnitId(), transactionCmd.getGroupId());
            //join
            transactionManager.join(dtxContext, joinGroupParams.getUnitId(), joinGroupParams.getUnitType(),
                    rpcClient.getAppName(transactionCmd.getRemoteKey()), joinGroupParams.getTransactionState());
            txLogger.txTrace(transactionCmd.getGroupId(), joinGroupParams.getUnitId(), "unit:{} joined.",
                    joinGroupParams.getUnitId());
        } catch (TransactionException e) {
            txLogger.error(this.getClass().getSimpleName(), e.getMessage());
            throw new TxManagerException(e.getLocalizedMessage());
        }
        // non response
        return null;
    }
public void join(DTXContext dtxContext, String unitId, String unitType, String modId, int userState) throws TransactionException {
        //手動回滾時設置狀態爲回滾狀態 0,這個狀態只有在用戶自己設置時可用
        if (userState == 0) {
            dtxContext.resetTransactionState(0);
        }
        //構造事務單元
        TransactionUnit transactionUnit = new TransactionUnit();
        transactionUnit.setModId(modId);
        transactionUnit.setUnitId(unitId);
        transactionUnit.setUnitType(unitType);
        dtxContext.join(transactionUnit);
    }
public void join(TransactionUnit transactionUnit) throws TransactionException {
        try {
            fastStorage.saveTransactionUnitToGroup(groupId, transactionUnit);
        } catch (FastStorageException e) {
            throw new TransactionException("attempts to join the non-existent transaction group. ["
                    + transactionUnit.getUnitId() + '@' + transactionUnit.getModId() + ']');
        }
    }
public void saveTransactionUnitToGroup(String groupId, TransactionUnit transactionUnit) throws FastStorageException {
        if (Optional.ofNullable(redisTemplate.hasKey(REDIS_GROUP_PREFIX + groupId)).orElse(false)) {
            redisTemplate.opsForHash().put(REDIS_GROUP_PREFIX + groupId, transactionUnit.getUnitId(), transactionUnit);
            return;
        }
        throw new FastStorageException("attempts to the non-existent transaction group " + groupId,
                FastStorageException.EX_CODE_NON_GROUP);
    }

服務端收到信息後會在redis與creategroup的hashkey相同數據下保存事務單元信息,key爲事務單元id,key爲事務單元信息

4、業務執行成功後finally中的postBusinessCode

1)發起方

關閉事務組

public void postBusinessCode(TxTransactionInfo info) {
        // RPC close DTX group
        transactionControlTemplate.notifyGroup(
                info.getGroupId(), info.getUnitId(), info.getTransactionType(),
                DTXLocalContext.transactionState(globalContext.dtxState(info.getGroupId())));
    }
public void notifyGroup(String groupId, String unitId, String transactionType, int state) {
        try {
            txLogger.txTrace(
                    groupId, unitId, "notify group > transaction type: {}, state: {}.", transactionType, state);
            if (globalContext.isDTXTimeout()) {
                throw new LcnBusinessException("dtx timeout.");
            }
            //通知事務組,進行事務提交
            state = reliableMessenger.notifyGroup(groupId, state);
            //成功後去提交本地事務
            transactionCleanTemplate.clean(groupId, unitId, transactionType, state);
        } catch (TransactionClearException e) {
            txLogger.trace(groupId, unitId, Transactions.TE, "clean transaction fail.");
        } catch (RpcException e) {
            dtxExceptionHandler.handleNotifyGroupMessageException(Arrays.asList(groupId, state, unitId, transactionType), e);
        } catch (LcnBusinessException e) {
            // 關閉事務組失敗
            dtxExceptionHandler.handleNotifyGroupBusinessException(Arrays.asList(groupId, state, unitId, transactionType), e.getCause());
        }
        txLogger.txTrace(groupId, unitId, "notify group exception state {}.", state);
    }

通知事務組進行提交事務這邊比較繞

public int notifyGroup(String groupId, int transactionState) throws RpcException, LcnBusinessException {
        NotifyGroupParams notifyGroupParams = new NotifyGroupParams();
        notifyGroupParams.setGroupId(groupId);
        notifyGroupParams.setState(transactionState);
        MessageDto messageDto = request0(MessageCreator.notifyGroup(notifyGroupParams),
                clientConfig.getTmRpcTimeout() * clientConfig.getChainLevel());
        // 成功清理髮起方事務
        if (!MessageUtils.statusOk(messageDto)) {
            throw new LcnBusinessException(messageDto.loadBean(Throwable.class));
        }
        return messageDto.loadBean(Integer.class);
    }

同樣是向服務端發送請求,服務端接收到請求後用NotifyGroupExecuteService類的execute方法進行處理

public Serializable execute(TransactionCmd transactionCmd) throws TxManagerException {
        try {
            DTXContext dtxContext = dtxContextRegistry.get(transactionCmd.getGroupId());
            // 解析參數
            NotifyGroupParams notifyGroupParams = transactionCmd.getMsg().loadBean(NotifyGroupParams.class);
            int commitState = notifyGroupParams.getState();
            // 獲取事務狀態(當手動回滾時會先設置狀態)
            int transactionState = transactionManager.transactionStateFromFastStorage(transactionCmd.getGroupId());
            if (transactionState == 0) {
                commitState = 0;
            }

            // 系統日誌
            txLogger.txTrace(
                    transactionCmd.getGroupId(), "", "notify group state: {}", notifyGroupParams.getState());
            //如果狀態爲1則事務成功,通知參與模塊進行提交
            if (commitState == 1) {
                transactionManager.commit(dtxContext);
            } else if (commitState == 0) {//如果狀態爲0則事務失敗,通知參與模塊進行回滾
                transactionManager.rollback(dtxContext);
            }
            if (transactionState == 0) {
                txLogger.txTrace(transactionCmd.getGroupId(), "", "mandatory rollback for user.");
            }
            return commitState;
        } catch (TransactionException e) {
            throw new TxManagerException(e);
        } finally {
            transactionManager.close(transactionCmd.getGroupId());
            // 系統日誌
            txLogger.txTrace(transactionCmd.getGroupId(), "", "notify group successfully.");
        }
    }
public void commit(DTXContext dtxContext) throws TransactionException {
        notifyTransaction(dtxContext, 1);
    }
//根據groupid從redis獲取所有的事務單元,通知其進行提交
private void notifyTransaction(DTXContext dtxContext, int transactionState) throws TransactionException {
        
        List<TransactionUnit> transactionUnits = dtxContext.transactionUnits();
        log.debug("group[{}]'s transaction units: {}", dtxContext.getGroupId(), transactionUnits);
        for (TransactionUnit transUnit : transactionUnits) {
            NotifyUnitParams notifyUnitParams = new NotifyUnitParams();
            notifyUnitParams.setGroupId(dtxContext.getGroupId());
            notifyUnitParams.setUnitId(transUnit.getUnitId());
            notifyUnitParams.setUnitType(transUnit.getUnitType());
            notifyUnitParams.setState(transactionState);
            txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify {}'s unit: {}",
                    transUnit.getModId(), transUnit.getUnitId());
            try {
                List<String> modChannelKeys = rpcClient.remoteKeys(transUnit.getModId());
                if (modChannelKeys.isEmpty()) {
                    // record exception
                    throw new RpcException("offline mod.");
                }
                MessageDto respMsg =
                        rpcClient.request(modChannelKeys.get(0), MessageCreator.notifyUnit(notifyUnitParams));
                if (!MessageUtils.statusOk(respMsg)) {
                    // 提交/回滾失敗的消息處理
                    List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
                    rpcExceptionHandler.handleNotifyUnitBusinessException(params, respMsg.loadBean(Throwable.class));
                }
            } catch (RpcException e) {
                // 提交/回滾通訊失敗
                List<Object> params = Arrays.asList(notifyUnitParams, transUnit.getModId());
                rpcExceptionHandler.handleNotifyUnitMessageException(params, e);
            } finally {
                txLogger.txTrace(dtxContext.getGroupId(), notifyUnitParams.getUnitId(), "notify unit over");
            }
        }

notifyTransaction方法又會調用客戶端,客戶端用DefaultNotifiedUnitService的execute方法進行處理

public Serializable execute(TransactionCmd transactionCmd) throws TxClientException {
        try {
            NotifyUnitParams notifyUnitParams = transactionCmd.getMsg().loadBean(NotifyUnitParams.class);
            // 保證業務線程執行完畢後執行事務清理操作
            //這個參數在參與方會一直是空,在finally會釋放
            TxContext txContext = globalContext.txContext(transactionCmd.getGroupId());
            if (Objects.nonNull(txContext)) {
                synchronized (txContext.getLock()) {
                    txLogger.txTrace(transactionCmd.getGroupId(), notifyUnitParams.getUnitId(),
                            "clean transaction cmd waiting for business code finish.");
                    txContext.getLock().wait();
                }
            }
            // 事務清理操作,主要是提交本地事務
            transactionCleanTemplate.clean(
                    notifyUnitParams.getGroupId(),
                    notifyUnitParams.getUnitId(),
                    notifyUnitParams.getUnitType(),
                    notifyUnitParams.getState());
            return true;
        } catch (TransactionClearException | InterruptedException e) {
            throw new TxClientException(e);
        }

畫一張圖來表示流程

2)參與方

空操作,什麼也不幹

default void postBusinessCode(TxTransactionInfo info) {

    }

整體流程圖如下,比較粗糙,其中清理事務的已經在上圖畫出。

Finally(runTransaction)的作用就是釋放資源,保證在清理事務時不至於阻塞線程。

一些其他點:

關於參與方如何知道已經存在事務的?

一般我們都是一接口的方式去調用服務,基本就是restTemplate如果用到spring Cloud還會用到Fegin。框架在進行接口調用時都會通過把事務消息放在header中,後邊的服務從header中就會得到當前的事務信息。

ClientHttpRequestInterceptor可以對請求進行攔截,並在其被髮送至服務端之前修改請求或是增強相應的信息。

添加事務組信息到請求頭

RestTemplate調用實現攔截

RestTemplateTracingTransmitter實現了ClientHttpRequestInterceptor接口。並把此攔截器加入到restTemplate的攔截器鏈中,當restTemplate去請求別的資源時,會被RestTemplateTracingTransmitter攔截到,在requestheader中加入groupid信息。

public class RestTemplateTracingTransmitter implements ClientHttpRequestInterceptor {

    @Autowired
    public RestTemplateTracingTransmitter(@Autowired(required = false) List<RestTemplate> restTemplates) {
        if (Objects.nonNull(restTemplates)) {
            restTemplates.forEach(restTemplate -> {
                List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
                //加入攔截器鏈
                interceptors.add(interceptors.size(), RestTemplateTracingTransmitter.this);
            });
        }
    }

    @Override
    @NonNull
    public ClientHttpResponse intercept(
            @NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
            @NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
        Tracings.transmit(httpRequest.getHeaders()::add);
        return clientHttpRequestExecution.execute(httpRequest, bytes);
    }
}
public static void transmit(TracingSetter tracingSetter) {
        //把信息存儲到header中
        if (TracingContext.tracing().hasGroup()) {
            log.debug("tracing transmit group:{}", TracingContext.tracing().groupId());
            tracingSetter.set(TracingConstants.HEADER_KEY_GROUP_ID, TracingContext.tracing().groupId());
            tracingSetter.set(TracingConstants.HEADER_KEY_APP_MAP,
                    Base64Utils.encodeToString(TracingContext.tracing().appMapString().getBytes(StandardCharsets.UTF_8)));
        }
    }

fegin實現請求攔截

RequestInterceptor可以實現對請求的攔截

FeignTracingTransmitter實現RequestInterceptor接口在請求資源時在header中加入groupid信息。

public class FeignTracingTransmitter implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        Tracings.transmit(requestTemplate::header);
    }
}

從請求頭中獲取事務信息

springboot中要實現攔截請求要用HandlerInterceptor,並且需要在WebMvcConfigurer把此攔截器註冊到InterceptorRegistry

public class SpringTracingApplier implements com.codingapi.txlcn.tracing.http.spring.HandlerInterceptor, WebMvcConfigurer {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //從header中獲取事務組信息
        Tracings.apply(request::getHeader);
        return true;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this);
    }
}
 public static void apply(TracingGetter tracingGetter) {
        String groupId = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_GROUP_ID)).orElse("");
        String appList = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_APP_MAP)).orElse("");
        //事務組信息加載到本地上下文
        TracingContext.init(Maps.newHashMap(TracingConstants.GROUP_ID, groupId, TracingConstants.APP_MAP,
                StringUtils.isEmpty(appList) ? appList : new String(Base64Utils.decodeFromString(appList), StandardCharsets.UTF_8)));
        if (TracingContext.tracing().hasGroup()) {
            log.debug("tracing apply group:{}, app map:{}", groupId, appList);
        }
    }

 

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