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 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 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 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 params = Arrays.asList(notifyUnitParams, transUnit.getModId());
                    rpcExceptionHandler.handleNotifyUnitBusinessException(params, respMsg.loadBean(Throwable.class));
                }
            } catch (RpcException e) {
                // 提交/回滚通讯失败
                List 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);
        }

画一张图来表示流程

TX-LCN分布式事务框架源码解析(基于lcn模式下的正常流程源码分析)_第1张图片

2)参与方

空操作,什么也不干

default void postBusinessCode(TxTransactionInfo info) {

    }

整体流程图如下,比较粗糙,其中清理事务的已经在上图画出。

Finally(runTransaction)的作用就是释放资源,保证在清理事务时不至于阻塞线程。

TX-LCN分布式事务框架源码解析(基于lcn模式下的正常流程源码分析)_第2张图片

一些其他点:

关于参与方如何知道已经存在事务的?

一般我们都是一接口的方式去调用服务,基本就是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 restTemplates) {
        if (Objects.nonNull(restTemplates)) {
            restTemplates.forEach(restTemplate -> {
                List 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);
        }
    }

 

你可能感兴趣的:(分布式事物,TX-LCN)