分布式事务详解,并带有lcn源码解析。

1):为什么需要分布式事务?

假设用户购买商品时,会生成一个订单,修改库存,修改用户积分。

倘若单库的话,可通过开启事务方式,一起失败,或者一起成功。

但是当体量达到一个级别,不得不采用分库分表的方式,将其垂直拆分成多个库。比如用户中心库,库存中心库,订单模块库。

而服务也对应的进行了拆分,用户中心,库存中心,订单中心三个服务。修改数据,不得不跨服务访问库,这个时候,假设订单模块库成功了,库存中心库执行失败了,那么就要通知订单中心进行回滚操作。这就添加了复杂性。

随之而来出现了很多针对此场景的解决方案。

2):常见的解决方案如下?

分布式事务实现方案从类型上去分刚性事务、柔型事务。

刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。一般出现在金融领域。

柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。只追求数据最终结果的可采用。

刚性事务:XA 协议(2PC、JTA、JTS)、3PC

柔型事务:TCC/FMT、Saga(状态机模式、Aop模式)、本地事务消息、消息事务(半消息)

2)1):二阶段提交(2PC)

引入一个协调者,统一掌控所有参与者,并指示他们是否操作结果提交还是回滚
分为2个阶段
	投票阶段:参与则通知协调者,协调者反馈结果
	提交阶段:收到参与者反馈后,协调者再向参与者发出通知,根据反馈情况决定每个参与者到底还是提交还是回滚
例子:老大BOSS说要吃饭,统计下面员工意见,有员工ABC同意去,但D没回复,则BOSS依旧处于接受信息状态,ABC则阻塞.当统计了ABCD员工意见后,如果有一个员工不同意,则通知ABCD不去(回滚),然后ABCD收到后ACK BOSS,这个阶段如果BOSS没收到会一直阻塞
缺点
	执行过程中,所有节点处于阻塞,所有节点持有资源锁定状态,当有一个节点出现问题无法回复,则会一直阻塞,就需要更复杂的超时机制处理.

2)2):TXC逆向SQL

1. 拦截,并解析SQL
2. 查询受影响的行数,并记录值
   TXM通知事务提交,则删除记录
   若回滚则,将记录的值创建逆向SQL,然后执行

2)3):TCC(Try、Confirm、Cancel)

//尝试方法
function try(){
    //记录日志
    todo save A 转出了 100 元 
    todo save B 转入了 100 元 
    //执行转账
    update amount set balacne = balacne-100 where id = 1
    update amount set balacne = balacne+100 where id = 2
}
//确认方法
function confirm(){
    //清理日志
    clean save A 转出了 100 元 
    clean save B 转出了 100 元 
}

//取消方法
function cancle(){
    //加载日志
    load log A
    load log B
//退钱
update amount set balacne = balacne+100 where id = 1
update amount set balacne = balacne-100 where id = 2    
}

每个服务需要实现三个方法,开发较重。但是针对db和redis的混合开发,有不错的支持。

2)4):增量日志

确保最终一致性,延迟取决于日志对比时间
模仿mysql主从复制,binLog,涉及修改添加到日志,然后间隔多长时间进行正向反向表数据比对.

2)5):补偿事务

在业务端执行业务逆向操作事务
	flag1=执行事务一返回结果
	if(flag1){
		flag2=执行事务二返回结果
		if(flag2){
			...执行事务...
		}else{
		回滚事务二
		}
	}else{
		回滚事务一
	}
缺点
	嵌套过多
	不同业务需要写不同的补偿事务
	不具备通用性
	没有考虑补偿事务失败

2)6):后置提交优化

一个事务,分别为执行和提交2阶段
执行比提交耗时长

改变事务执行与提交时序,变成事务先执行然后一起提交

执行事务一,提交.执行事务耗时200ms,提交2ms
执行事务二,提交.执行事务耗时200ms,提交2ms
执行事务三,提交.执行事务耗时200ms,提交2ms
当执行事务二~三之间出现异常,数据就不一致,时间范围为202+202ms

通过改变时序后:
执行事务一,执行事务耗时200ms
执行事务二,执行事务耗时200ms
执行事务三,执行事务耗时200ms
提交事务一,提交2ms
提交事务二,提交2ms
提交事务三,提交2ms
后置优化后,在提交事务二~三阶段才会出现不一致情况,时间耗时4ms

虽然不能根本解决数据一致问题,但通过缩小时间概率,降低不一致概率

缺点:
	串行执行事务提交后,连接就释放了,但后置提交优化后,所有库的连接,需要等待全部事务执行完才能释放,数据库连接占用时间加长,吞吐降低了

3):txlcn源码解析

执行时序图
在这里插入图片描述

3)1):事务发起方执行分布式事务流程

仔细思考,作为发起方在整个分布式事务中出现情况如下:

  1. 发起方执行成功(在这种情况下,所有参与方均执行成功,发起方才能成功。)
    1. 参与方第二阶段成功,提交。
    2. 参与方第二阶段失败,回滚。通知txm加入补偿日志。
    3. 自身提交or回滚失败,通知txm加入补偿日志。
  2. 发起方失败,自身回滚。通知tx-m执行失败,通知其他服务回滚。

当我们在方法上添加TxTransaction时,表示开启分布式事务。注意作为服务发起方需要标记isStart=true

在这里插入图片描述

这个时候会被aop环绕注解拦截。com.codingapi.tx.springcloud.interceptor.TransactionAspect#transactionRunning

@Around("@annotation(com.codingapi.tx.annotation.TxTransaction)")
public Object transactionRunning(ProceedingJoinPoint point) throws Throwable
{
    logger.debug("annotation-TransactionRunning-start---->");
    Object obj = txManagerInterceptor.around(point);
    logger.debug("annotation-TransactionRunning-end---->");
    return obj;
}

从请求头中获取事务组id和事务模式。

com.codingapi.tx.springcloud.interceptor.TxManagerInterceptor#around

public Object around(ProceedingJoinPoint point) throws Throwable {
    String groupId = null;
    String mode = null;
    try {
        //如果是发起方,这里都是null。
        //如果是调用方,则这里会有发起方通过feign发送过来的事务组id和事务模式。
        //事务组id:事务唯一标识。
        //事务模式:包括lcn,或者tcc等。
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = requestAttributes == null ? null : ((ServletRequestAttributes) requestAttributes).getRequest();
        groupId = request == null ? null : request.getHeader("tx-group");
        mode = request == null ? null : request.getHeader("tx-mode");
    }catch (Exception e){}
    //继续调用切面方法
    return aspectBeforeService.around(groupId, point, mode);
}

封装事务信息,调用核心类执行。

com.codingapi.tx.aop.service.impl.AspectBeforeServiceImpl#around

@Override
public Object around(String groupId, ProceedingJoinPoint point, String mode) throws Throwable {

    MethodSignature signature = (MethodSignature) point.getSignature();
    //获取接口执行方法
    Method method = signature.getMethod();
    Class<?> clazz = point.getTarget().getClass();
    //获取方法参数
    Object[] args = point.getArgs();
    //获取具体执行的子类方法
    Method thisMethod = clazz.getMethod(method.getName(), method.getParameterTypes());

    //获取分布式事务注解
    TxTransaction transaction = thisMethod.getAnnotation(TxTransaction.class);

    TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();

    logger.debug("around--> groupId-> " +groupId+",txTransactionLocal->"+txTransactionLocal);
    //封装事务方法
    TransactionInvocation invocation = new TransactionInvocation(clazz, thisMethod.getName(), thisMethod.toString(), args, method.getParameterTypes());

    //封装事务信息,包含注解信息,方法信息,事务组id。
    TxTransactionInfo info = new TxTransactionInfo(transaction,txTransactionLocal,invocation,groupId);
    try {
        //设置事务类型
        info.setMode(TxTransactionMode.valueOf(mode));
    } catch (Exception e) {
        info.setMode(TxTransactionMode.TX_MODE_LCN);
    }
    //创建事务服务
    TransactionServer server = transactionServerFactoryService.createTransactionServer(info);
            
    //执行核心事务方法
    return server.execute(point, info);
}

3)1)1):创建事务服务

com.codingapi.tx.aop.service.impl.TransactionServerFactoryServiceImpl

public TransactionServer createTransactionServer(TxTransactionInfo info) throws Throwable {
    if (!SocketManager.getInstance().isNetState()) {
        //检查socket通讯是否正常 (第一次执行时启动txRunningTransactionServer的业务处理控制,然后嵌套调用其他事务的业务方法时都并到txInServiceTransactionServer业务处理下)
        logger.warn("tx-manager not connected.");
        return txDefaultTransactionServer;
    }

    /*********分布式事务处理逻辑***********/
    logger.info("分布式事务处理逻辑...开始");

    /** 事务发起方:仅当TxTransaction注解不为空,其他都为空时。表示分布式事务开始启动 **/
    //注解不为空,且注解声明了为发起方,且事务组id为空,这就说明为事务发起方。
    if (info.getTxTransaction() != null && info.getTxTransaction().isStart() && info.getTxTransactionLocal() == null && StringUtils.isEmpty(info.getTxGroupId())) {
        //检查socket通讯是否正常 (当启动事务的主业务方法执行完以后,再执行其他业务方法时将进入txInServiceTransactionServer业务处理)
        if (SocketManager.getInstance().isNetState()) {
            return txStartTransactionServer;
        } else {
            logger.warn("tx-manager not connected.");
            return txDefaultTransactionServer;
        }
    }

    /** 事务参与方:分布式事务已经开启,业务进行中 **/
    logger.debug("事务参与方:分布式事务已经开启,业务进行中");
    if (info.getTxTransactionLocal() != null || StringUtils.isNotEmpty(info.getTxGroupId())) {
        //检查socket通讯是否正常 (第一次执行时启动txRunningTransactionServer的业务处理控制,然后嵌套调用其他事务的业务方法时都并到txInServiceTransactionServer业务处理下)
        if (SocketManager.getInstance().isNetState()) {
            if (info.getTxTransactionLocal() != null) {
                return txDefaultTransactionServer;
            } else {
                /** 表示整个应用没有获取过DB连接 || 无事务业务的操作 **/
                if (transactionControl.isNoTransactionOperation() || info.getTxTransaction().readOnly()) {
                    return txRunningNoTransactionServer;
                } else {
                    return txRunningTransactionServer;
                }
            }
        } else {
            logger.warn("tx-manager not connected.");
            return txDefaultTransactionServer;
        }
    }
    /*********分布式事务处理逻辑*结束***********/
    logger.debug("分布式事务处理逻辑*结束");
    return txDefaultTransactionServer;
}

当出现网络不通时,会选择com.codingapi.tx.aop.service.impl.TxDefaultTransactionServerImpl,就是一个简单的调用。

@Override
public Object execute(ProceedingJoinPoint point, TxTransactionInfo info) throws Throwable {
    logger.info("默认事务管理器...");
    return point.proceed();
}

com.codingapi.tx.aop.service.impl.TxDefaultTransactionServerImpl类继承TransactionServer,该类下有4个实现,分别针对发起方和调用方,默认以及无事务4种实现。
在这里插入图片描述

3)1)2):执行分布式事务

作为调用方,这里只看com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute

  1. 构建事务组id

  2. 向tx-mange发起事务创建,对应图中这步。

在这里插入图片描述

  1. 封装事务上下文

  2. 执行业务方法,失败或成功通过state记录状态。

  3. 通知tx-m关闭事务组,进入事务提交第一阶段,tx-m会通知所有参与者提交。获取所有参与者执行后的最终结果。

    1. 对应如图

在这里插入图片描述

  1. 若task不为空,则唤醒阻塞任务,这里会执行本地db连接池commit,或者回滚。然后归还db连接。

  2. 发起方执行失败但是参与方成功,或者发起方成功,参与方失败。则通知tx-M记录补偿

public Object execute(ProceedingJoinPoint point,final TxTransactionInfo info) throws Throwable {
    //分布式事务开始执行

    logger.info("事务发起方...");

    logger.debug("--->分布式事务开始执行 begin start transaction");

    final long start = System.currentTimeMillis();

    //标记执行状态,0失败,1成功。
    int state = 0;

    //1:构建事务组id
    final String groupId = TxCompensateLocal.current()==null?KidUtils.generateShortUuid():TxCompensateLocal.current().getGroupId();

    //2:向tx-mange发起事务创建
    logger.debug("创建事务组并发送消息");
    txManagerService.createTransactionGroup(groupId);

    //3:封装事务上下文(封装组id,事务超时时间,事务模式,是否发起方),并设置当前线程中。这里贯穿整个生命周期。
    TxTransactionLocal txTransactionLocal = new TxTransactionLocal();
    txTransactionLocal.setGroupId(groupId);
    txTransactionLocal.setHasStart(true);
    txTransactionLocal.setMaxTimeOut(Constants.txServer.getCompensateMaxWaitTime());
    txTransactionLocal.setMode(info.getTxTransaction().mode());
    txTransactionLocal.setReadOnly(info.getTxTransaction().readOnly());
    TxTransactionLocal.setCurrent(txTransactionLocal);

    try {
        //4:执行业务方法
        Object obj = point.proceed();
        //修改执行结果状态
        state = 1;
        return obj;
    } catch (Throwable e) {
        //5:回滚事务
        state = rollbackException(info,e);
        throw e;
    } finally {

        final String type = txTransactionLocal.getType();
        //6:通知tx-m关闭事务组,进入事务提交第一阶段,tx-m会通知所有参与者提交。获取所有参与者执行后的最终结果。
        int rs = txManagerService.closeTransactionGroup(groupId, state);

        int lastState = rs==-1?0:state;

        int executeConnectionError = 0;

        //获取task,走到这里的时候,发起方若执行了回滚,这里为null。
        final TxTask waitTask = TaskGroupManager.getInstance().getTask(groupId, type);
        if(waitTask!=null){
            //设置最终执行结果
            waitTask.setState(lastState);
            //唤醒阻塞任务,这里会执行本地db连接池commit,或者回滚。然后归还db连接。
            waitTask.signalTask();
            //自旋等待线程池删除任务。
            while (!waitTask.isRemove()){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if(waitTask.getState()== TaskState.connectionError.getCode()){
                //本地执行失败.
                executeConnectionError = 1;

                lastState = 0;
            }
        }

        final TxCompensateLocal compensateLocal =  TxCompensateLocal.current();

        if (compensateLocal == null) {
            long end = System.currentTimeMillis();
            long time = end - start;
            if ((executeConnectionError == 1&&rs == 1)||(lastState == 1 && rs == 0)) {
                //发起方执行失败但是参与方成功,或者发起方成功,参与方失败。则通知tx-M记录补偿
                txManagerService.sendCompensateMsg(groupId, time, info,executeConnectionError);
            }
        }else{
            if(rs==1){
                lastState = 1;
            }else{
                lastState = 0;
            }
        }
        //清除事务上下文信息
        TxTransactionLocal.setCurrent(null);
        logger.debug("<---分布式事务 end start transaction");
        logger.debug("start transaction over, res -> groupId:" + groupId + ", now state:" + (lastState == 1 ? "commit" : "rollback"));

    }
}

3)1)3):获取线程连接

当执行业务方法的时,会通过封装db连接,达到控制commit或者rollback操作。

拦截获取数据库连接方法com.codingapi.tx.datasource.aspect.DataSourceAspect#around

@Around("execution(* javax.sql.DataSource.getConnection(..))")
public Connection around(ProceedingJoinPoint point)throws Throwable{

    logger.debug("getConnection-start---->");

    //获取封装之后的线程池对象。
    Connection connection = lcnConnection.getConnection(point);
    logger.debug("connection-->"+connection);

    logger.debug("getConnection-end---->");

    return connection;
}

com.codingapi.tx.datasource.relational.LCNTransactionDataSource#getConnection

@Override
public Connection getConnection(ProceedingJoinPoint point) throws Throwable {
    //说明有db操作.
    hasTransaction = true;

    //设置db类型
    initDbType();

    //从缓存中获取db资源
    Connection connection = (Connection)loadConnection();
    //获取不到则新建
    if(connection==null) {
        //没获取到利用spring生成db连接,并进行封装,加入缓存中。
        connection = initLCNConnection((Connection) point.proceed());
        if(connection==null){
            throw new SQLException("connection was overload");
        }
        return connection;
    }else {
        return connection;
    }
}

从缓存中获取db资源

protected ILCNResource loadConnection(){
    //从当前线程获取事务远程调用控制对象
    TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();

    if(txTransactionLocal==null){
        logger.debug("loadConnection -> null !");
        return null;
    }
    if (txTransactionLocal.isReadOnly()) {
        logger.debug("readonly tx don't reuse connection.");
        return null;
    }

    //这里每次获取新的连接的时候会将,事务组id作为key,db连接作为value存储pool中。这样同一个事务组获取的始终是同一个线程,便于后面回滚操作。
    //是否获取旧连接的条件:同一个模块下被多次调用时第一次的事务操作
    ILCNResource old = pools.get(txTransactionLocal.getGroupId());
    if (old != null) {
        //已经有连接了则返回null,后面新建一个连接
        if(txTransactionLocal.isHasConnection()){
            logger.debug("connection is had , transaction get a new connection .");
            return null;
        }

        logger.debug("loadConnection -> old !");
        //标记有连接
        txTransactionLocal.setHasConnection(true);
        return old;
    }
    return null;
}

若没获取到db连接,则利用spring生成db连接,并进行封装,加入缓存中。

    protected C initLCNConnection(C connection) {
        logger.debug("initLCNConnection");
        C lcnConnection = connection;
        TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();

        //没有从缓存中获取到db连接,且事务注解没有设置本服务为只读,则封装db连接。
        if (txTransactionLocal != null&&!txTransactionLocal.isHasConnection()
            && !txTransactionLocal.isReadOnly()) {

            logger.debug("lcn datasource transaction control ");

            //补偿的情况的
//            if (TxCompensateLocal.current() != null) {
//                logger.info("rollback transaction ");
//                return getCompensateConnection(connection,TxCompensateLocal.current());
//            }

            if(StringUtils.isNotEmpty(txTransactionLocal.getGroupId())){

                logger.debug("lcn transaction ");
                //封装连接
                return createConnection(txTransactionLocal, connection);
            }
        }
        logger.debug("load default connection !");
        return lcnConnection;
    }
  1. 当事务模式是TCC,则直接创建。
  2. 不是TCC,则说明事务模式是Lcn,当db连接个数若等于最大创建个数,则等待一秒再次获取连接,超过30s则返回源db。
  3. 低于创建个数,直接创建
  4. 大于最大创建个数,说明资源紧张,返回null。
private C createConnection(TxTransactionLocal txTransactionLocal, C connection){
    if (txTransactionLocal.getMode() != null
        && txTransactionLocal.getMode() == TxTransactionMode.TX_MODE_TXC) {
        // 1:txc 模式下没有maxCount的限制 直接创建
        return createTxcConnection(connection, txTransactionLocal);
    }
    //2:若等于最大创建个数,则等待一秒再次获取连接,超过30s则返回源db。
    if (nowCount == maxCount) {
        for (int i = 0; i < maxWaitTime; i++) {
            for(int j=0;j<100;j++){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (nowCount < maxCount) {
                    //封装连接
                    return createLcnConnection(connection, txTransactionLocal);
                }
            }
        }
    } else if (nowCount < maxCount) {
        //3:低于创建个数,直接创建
        return createLcnConnection(connection, txTransactionLocal);
    } else {
        //4:超过创建个数,说明资源紧张,则返回null。
        logger.info("connection was overload");
        return null;
    }
    return connection;
}

针对发起方和调用方分别不同封装db连接

@Override
protected Connection createLcnConnection(Connection connection, TxTransactionLocal txTransactionLocal) {
    //累加当前获取db连接数。
    nowCount++;
    //如果是发起方
    if(txTransactionLocal.isHasStart()){
        //注册回调函数,用于清除缓存中连接资源。
        LCNStartConnection lcnStartConnection = new LCNStartConnection(connection,subNowCount);
        logger.debug("get new start connection - > "+txTransactionLocal.getGroupId());
        //添加到缓存
        pools.put(txTransactionLocal.getGroupId(), lcnStartConnection);
        //标记已经获取到db连接
        txTransactionLocal.setHasConnection(true);
        return lcnStartConnection;
    }else {
        //调用方
        LCNDBConnection lcn = new LCNDBConnection(connection, dataSourceService, subNowCount);
        logger.debug("get new connection ->" + txTransactionLocal.getGroupId());
        pools.put(txTransactionLocal.getGroupId(), lcn);
        txTransactionLocal.setHasConnection(true);
        return lcn;
    }
}

创建连接时会新建一个task,该task主要用于,在等待tx-m告知事务所有参与方最终执行结果之前,发起方一直阻塞在commit或rollback处。获取tx-m告知的结果后,在执行commit或rollback。

com.codingapi.tx.datasource.relational.LCNStartConnection#LCNStartConnection

public LCNStartConnection(Connection connection, ICallClose<ILCNResource> subNowCount) {
    this.connection = connection;
    this.subNowCount = subNowCount;

    if(TxCompensateLocal.current()!=null){
        isCompensate = true;
        logger.info("transaction is compensate-connection.");

        TxCompensateLocal txCompensateLocal = TxCompensateLocal.current();
        groupId =  txCompensateLocal.getGroupId();

        TaskGroup taskGroup = TaskGroupManager.getInstance().createTask(groupId,txCompensateLocal.getType());
        waitTask = taskGroup.getCurrent();

        startState = txCompensateLocal.getStartState();
    }else{
        isCompensate = false;
        logger.info("transaction is start-connection.");
        TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
        groupId = txTransactionLocal.getGroupId();

        //新建任务
        TaskGroup taskGroup = TaskGroupManager.getInstance().createTask(groupId, txTransactionLocal.getType());
        //将新建的任务捞出赋给waitTask
        waitTask = taskGroup.getCurrent();
    }

    logger.info("lcn start connection init ok .");
}

3)1)4):执行业务方法

@Override
@TxTransaction(isStart = true)
@Transactional
public int save(String name) {

    testMapper.save("demo1");

    demo2Client.save( "demo2");

    demo3Client.save();

    return 2;
}

3)1)4)1):调用远程接口传递groupId

注意:当执行demo2Client.save( “demo2”)时,会在请求头传递事务组id给demo2服务。

com.codingapi.tx.springcloud.feign.TransactionRestTemplateInterceptor

@Override
public void apply(RequestTemplate requestTemplate) {

    TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
    String groupId = txTransactionLocal == null ? null : txTransactionLocal.getGroupId();

    logger.info("LCN-SpringCloud TxGroup info -> groupId:"+groupId);

    if (txTransactionLocal != null) {
        requestTemplate.header("tx-group", groupId);
    }
}

3)1)5):提交

业务方法执行成功结束后,会走到这里。

com.codingapi.tx.datasource.relational.LCNStartConnection#commit

public void commit() throws SQLException {

    logger.info("commit label");
    //标记正确执行
    state=1;
    //异步线程执行commit或者rollback
    close();
    //设置已经执行过close
    isClose.set(true);
}

异步线程执行commit或者rollback,方法有如下作用:

  1. 对rollback处理,业务方法执行过程中有任何报错,包含参与方的报错,走到这里回滚业务方访问db的操作。
  2. 对commit的处理,都到这说明业务方法顺利执行。
    1. 3:异步线程执行,等待txm返回的最终结果,决定是否commit或者rollback。
public void close() throws SQLException {

    if(isClose.get()!=null&& isClose.get()){
        return;
    }

    if(connection==null||connection.isClosed()){
        return;
    }
    //1:回滚的处理
    if(state==0){
        //数据库回滚
        rollbackConnection();
        //关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
        closeConnection();
        return;
    }
    //2:正确执行的处理
    if(state == 1) {
        TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
        boolean isGroup = (txTransactionLocal != null) ? txTransactionLocal.isHasIsGroup() : false;
        //不是同一个db连接调用多次
        if (isGroup) {
            //加入队列的连接,仅操作连接对象,不处理事务
            logger.info("start connection hasGroup -> " + isGroup);
            return;
        }
        //3:异步线程执行,等待txm返回的最终结果,决定是否commit或者rollback。
        startRunnable();
    }
}
protected void startRunnable(){
    if(hasStartTransaction){
        logger.debug("start connection is wait ! ");
        return;
    }
    //标记已执行过该方法
    hasStartTransaction = true;
    Runnable runnable = new HookRunnable() {
        @Override
        public void run0() {
            //清空上下文对象
            TxTransactionLocal.setCurrent(null);
            try {
                //执行提交或回滚。
                transaction();
            } catch (Exception e) {
                logger.error(e.getMessage());
                try {
                    rollbackConnection();
                } catch (SQLException e1) {
                    logger.error(e1.getMessage());
                }
            } finally {
                try {
                    //关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
                    closeConnection();
                } catch (SQLException e) {
                    logger.error(e.getMessage());
                }
            }
        }
    };
    Thread thread = new Thread(runnable);
    thread.start();
}

执行提交或回滚操作。

com.codingapi.tx.datasource.relational.LCNStartConnection#transaction

public void transaction()throws SQLException{
        if (waitTask == null) {
            rollbackConnection();
            System.out.println(" start waitTask is null");
            return;
        }

        logger.info(" start transaction is wait for TxManager notify, groupId : " + getGroupId());
        //1:这里会阻塞等待,直到tx-m通知所有参与者提交,获取到所有参与者执行后的最终结果后,才会被唤醒。
        waitTask.awaitTask();
        //获取最终执行结果。
        int rs = waitTask.getState();

        try {
            //说明tx-m返回成功
            if (rs == 1) {
                if(isCompensate) {
                    //2:补偿时需要根据补偿数据决定提交还是回滚.
                    rs = startState;
                    if(rs==1) {
                        connection.commit();
                    }else{
                        connection.rollback();
                    }
                }else{
                    //3:提交
                    connection.commit();
                }
            } else {
                //tx-m
                //4:回滚事务
                rollbackConnection();
            }
            logger.info(" lcn start transaction over, res -> groupId:"+getGroupId()+" and  state is "+(rs==1?"commit":"rollback"));

        }catch (SQLException e){
            //回滚事务
            rollbackConnection();
            //记录发起方提交失败
            waitTask.setState(TaskState.connectionError.getCode());

            System.out.println(" lcn start transaction over,but connection is closed,  res -> groupId:"+getGroupId());
        }
        //删除任务
        waitTask.remove();

    }

这里由一个阻塞等待操作,该操作主要在com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute被唤醒,后决定提交还是回滚。

在这里插入图片描述

这里执行失败后,会设置结果为:TaskState.connectionError.getCode()。在在com.codingapi.tx.aop.service.impl.TxStartTransactionServerImpl#execute类中会针对此通知tx-M记录补偿。

在这里插入图片描述

3)1)6):回滚

业务方法执行失败结束后,会走到这里。

com.codingapi.tx.datasource.relational.LCNStartConnection#rollback()

@Override
public void rollback() throws SQLException {

    logger.info("rollback label");
    //标记执行失败。
    state=0;
    //执行rollback,归还线程资源。
    close();
    //设置已经执行过close
    isClose.set(true);
}

在这里插入图片描述

@Override
protected void rollbackConnection() throws SQLException {
    connection.rollback();
}
@Override
protected void closeConnection() throws SQLException{
    //关闭task,从缓存中清除db连接
    subNowCount.close(this);

    //spring封装的连接池归还
    connection.close();
}

调用回调函数,关闭task,从缓存中清除db连接。这里回调函数是第一次构造connection时传递过来的。

protected ICallClose<ILCNResource> subNowCount = new ICallClose<ILCNResource>() {

    @Override
    public void close(ILCNResource connection) {
        //删除回调任务
        Task waitTask = connection.getWaitTask();
        if (waitTask != null) {
            if (!waitTask.isRemove()) {
                waitTask.remove();
            }
        }
        //从连接池中剔除事务组对应的db连接池
        pools.remove(connection.getGroupId());
        nowCount--;//当前连接池数量-1
    }
};

至此发起方流程结束。

3)2):事务参与方执行分布式事务流程

仔细思考,作为参与者在整个分布式事务中出现情况如下:

  1. 参与方成功
    1. 发起方成功,提交。
    2. 发起方失败,回滚。
    3. 自身提交or回滚失败。
  2. 参与方失败,自身会回滚,首先不会影响自身,然后对上层服务会抛异常,上层服务会根据异常回滚操作。

针对上述四种情况均需要汇报给tx-m,让tx-m通知其他节点做出决策。

接下来查看执行流程,当发起方调用feign后,会在请求头注入事务组id,然后访问参与者接口。

在这里插入图片描述

参与者service层方法实现如下,可以看到该方法也需要添加事务注解,表明加入分布式事务交由tx-m进行管理。

在这里插入图片描述

调用save前,会进过aop环绕切面,走到这个方法,获取groupId。

在这里插入图片描述

3)2)1):创建事务服务

和事务发起方不同的就是获取的事务服务执行器不一样,其余流程都一样。当事务组id不为空时,会进入该方法。groupId是由发起方通过feign访问时设置到请求头里面的。

com.codingapi.tx.aop.service.impl.TransactionServerFactoryServiceImpl#createTransactionServer

在这里插入图片描述

3)2)2):执行分布式事务

  1. 构建任务id
  2. 构建上下文对象。
  3. 执行业务方法
  4. 针对有db操作的处理。

com.codingapi.tx.aop.service.impl.TxRunningTransactionServerImpl#execute

@Override
public Object execute(final ProceedingJoinPoint point, final TxTransactionInfo info) throws Throwable {

    logger.info("事务参与方...");
    //1:构建任务id
    String kid = KidUtils.generateShortUuid();
    String txGroupId = info.getTxGroupId();
    logger.debug("--->begin running transaction,groupId:" + txGroupId);
    long t1 = System.currentTimeMillis();
    //是否是同一个事务下,第一次进入默认不在。
    boolean isHasIsGroup =  transactionControl.hasGroup(txGroupId);

    //2:构建上下文对象。
    TxTransactionLocal txTransactionLocal = new TxTransactionLocal();
    txTransactionLocal.setGroupId(txGroupId);
    txTransactionLocal.setHasStart(false);
    txTransactionLocal.setKid(kid);
    txTransactionLocal.setHasIsGroup(isHasIsGroup);
    txTransactionLocal.setMaxTimeOut(Constants.txServer.getCompensateMaxWaitTime());
    txTransactionLocal.setMode(info.getMode());
    TxTransactionLocal.setCurrent(txTransactionLocal);

    try {
        //3:执行业务方法
        Object res = point.proceed();

        //4:针对有db操作的处理。
        if(!txTransactionLocal.isReadOnly()) {
            //获取该注解的业务方法名
            String methodStr = info.getInvocation().getMethodStr();
            //添加事务组子对象
            TxGroup resTxGroup = txManagerService.addTransactionGroup(txGroupId, kid, isHasIsGroup, methodStr);

            //已经进入过该模块的,不再执行此方法
            if(!isHasIsGroup) {
                String type = txTransactionLocal.getType();
                //获取任务,该任务在创建db连接时创建。
                TxTask waitTask = TaskGroupManager.getInstance().getTask(kid, type);

                //lcn 连接已经开始等待时.自旋等待任务被删除
                while (waitTask != null && !waitTask.isAwait()) {
                    TimeUnit.MILLISECONDS.sleep(1);
                }

                //tx-m没有发现该事务组,则回滚。
                if (resTxGroup == null) {
                    //通知业务回滚事务
                    if (waitTask != null) {
                        //修改事务组状态异常
                        waitTask.setState(-1);
                        //唤醒db连接进行回滚。
                        waitTask.signalTask();
                        throw new ServiceException("update TxGroup error, groupId:" + txGroupId);
                    }
                }
            }
        }

        return res;
    } catch (Throwable e) {
        // 这里处理以下情况:当 point.proceed() 业务代码中 db事务正常提交,开始等待,后续处理发生异常。
        // 由于没有加入事务组,不会收到通知。这里唤醒并回滚
        if(!isHasIsGroup) {
            String type = txTransactionLocal.getType();
            TxTask waitTask = TaskGroupManager.getInstance().getTask(kid, type);
            // 有一定几率不能唤醒: wait的代码是在另一个线程,有可能线程还没执行到wait,先执行到了这里
            // TODO 要不要 sleep 1毫秒
            logger.warn("wake the waitTask: {}", (waitTask != null && waitTask.isAwait()));
            if (waitTask != null && waitTask.isAwait()) {
                waitTask.setState(-1);
                waitTask.signalTask();
            }
        }
        throw e;
    } finally {
        //清空上下文信息
        TxTransactionLocal.setCurrent(null);
        long t2 = System.currentTimeMillis();
        logger.debug("<---end running transaction,groupId:" + txGroupId+",execute time:"+(t2-t1));

    }
}

3)2)3):获取线程连接

com.codingapi.tx.datasource.relational.LCNTransactionDataSource#createLcnConnection

这里需要注意的是,实现的连接是LCNDBConnection,比发起方多了一个传参dataSourceService。

@Override
protected Connection createLcnConnection(Connection connection, TxTransactionLocal txTransactionLocal) {
    //累加当前获取db连接数。
    nowCount++;
    //如果是发起方
    if(txTransactionLocal.isHasStart()){
        //注册回调函数,用于清除缓存中连接资源。
        LCNStartConnection lcnStartConnection = new LCNStartConnection(connection,subNowCount);
        logger.debug("get new start connection - > "+txTransactionLocal.getGroupId());
        //添加到缓存
        pools.put(txTransactionLocal.getGroupId(), lcnStartConnection);
        //标记已经获取到db连接
        txTransactionLocal.setHasConnection(true);
        return lcnStartConnection;
    }else {
        //调用方
        LCNDBConnection lcn = new LCNDBConnection(connection, dataSourceService, subNowCount);
        logger.debug("get new connection ->" + txTransactionLocal.getGroupId());
        pools.put(txTransactionLocal.getGroupId(), lcn);
        txTransactionLocal.setHasConnection(true);
        return lcn;
    }
}

3)2)4):执行业务方法

@Override
@TxTransaction
@Transactional
public int save(String name) {
    return testMapper.save(name);
}

3)2)5):提交

com.codingapi.tx.datasource.relational.LCNDBConnection#commit

下述方法同事务发起方的代码一致,可直接看com.codingapi.tx.datasource.relational.LCNDBConnection#transaction

@Override
public void commit() throws SQLException {

    logger.debug("commit label");

    //标记成功执行。
    state = 1;

    close();

    //设置该db已关闭
    isClose.set(true);

}
@Override
public void close() throws SQLException {

    //这里会调用二次close,一次是commit时调用。另外一次是spring关闭时调用。
    //若已经close则返回。
    if(isClose.get()!=null&& isClose.get()){
        return;
    }

    //db连接关闭则返回
    if(connection==null||connection.isClosed()){
        return;
    }

    //针对连接只读的处理。
    if(readOnly){
        closeConnection();
        logger.debug("now transaction is readOnly , groupId:" + groupId);
        return;
    }

    logger.debug("now transaction state is " + state + ", (1:commit,0:rollback) groupId:" + groupId);

    //执行失败,回滚的处理。
    if (state==0) {

        rollbackConnection();
        closeConnection();

        logger.debug("rollback transaction ,groupId:" + groupId);
    }
    //执行成功
    if (state==1) {
        TxTransactionLocal txTransactionLocal = TxTransactionLocal.current();
        //同一个模块被多次请求则直接返回。
        boolean hasGroup = (txTransactionLocal!=null)?txTransactionLocal.isHasIsGroup():false;
        if (hasGroup) {
            //加入队列的连接,仅操作连接对象,不处理事务
            logger.debug("connection hasGroup -> "+hasGroup);
            return;
        }
        startRunnable();
    }

}
protected void startRunnable(){
    if(hasStartTransaction){
        logger.debug("start connection is wait ! ");
        return;
    }
    //标记已执行过该方法
    hasStartTransaction = true;
    Runnable runnable = new HookRunnable() {
        @Override
        public void run0() {
            //清空上下文对象
            TxTransactionLocal.setCurrent(null);
            try {
                //执行提交或回滚。
                transaction();
            } catch (Exception e) {
                logger.error(e.getMessage());
                try {
                    rollbackConnection();
                } catch (SQLException e1) {
                    logger.error(e1.getMessage());
                }
            } finally {
                try {
                    //关闭回调函数,关闭task,从缓存中清除db连接,归还db连接。
                    closeConnection();
                } catch (SQLException e) {
                    logger.error(e.getMessage());
                }
            }
        }
    };
    Thread thread = new Thread(runnable);
    thread.start();
}

同事务发起方不同实现在此com.codingapi.tx.datasource.relational.LCNDBConnection#transaction

3)2)5)1):最终提交

该方法主要等待tx-m返回最终结果,决定是否提交还是回滚。倘若tx-m超过5秒没返回任务,则执行超时任务。超时任务会主动向txm发起询问,针对结果处理。

  1. 等待5秒,倘若没收到tx-m返回的事务执行结果,主动询问

  2. 阻塞等待,等待tx-m返回的结果。对应步骤如图。
    在这里插入图片描述

  3. 获取tx-m返回的结果

  4. 说明发起方调用方执行成功,则提交事务。否则回滚。

  5. 删除任务

@Override
public void transaction() throws SQLException {
    if (waitTask == null) {
        rollbackConnection();
        System.out.println(" waitTask is null");
        return;
    }


    //start 结束就是全部事务的结束表示,考虑start挂掉的情况
    Timer timer = new Timer();
    System.out.println(" maxOutTime : "+maxOutTime);
    //1:等待5秒,倘若没收到tx-m返回的事务执行结果,主动询问
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            logger.info("auto execute ,groupId:" + getGroupId());
            dataSourceService.schedule(getGroupId(), waitTask);
        }
    }, maxOutTime);

    logger.info("transaction is wait for TxManager notify, groupId {}", getGroupId());

    //2:阻塞等待,等待tx-m返回的结果
    waitTask.awaitTask();
    //说明获取到结果,定时任务取消。
    timer.cancel();

    //3:获取返回的结果
    int rs = waitTask.getState();

    try {
        //4:说明发起方调用方执行成功,则提交事务。
        if (rs == 1) {
            connection.commit();
        } else {
            //5:说明其他方执行失败,则回滚事务
            rollbackConnection();
        }

        logger.info("lcn transaction over, res -> groupId:"+getGroupId()+" and  state is "+(rs==1?"commit":"rollback"));

    }catch (SQLException e){
        logger.info("lcn transaction over,but connection is closed, res -> groupId:"+getGroupId());
        //记录连接错误。
        waitTask.setState(TaskState.connectionError.getCode());
    }

    //6:删除任务
    waitTask.remove();

}

倘若没收到tx-m返回的事务执行结果,主动发起询问

com.codingapi.tx.datasource.service.impl.DataSourceServiceImpl#schedule

@Override
public void schedule(String groupId, Task waitTask) {


    String waitTaskId = waitTask.getKey();
    //1:主动询问tx-m结果状态。
    int rs = txManagerService.cleanNotifyTransaction(groupId, waitTaskId);
    //执行成功或失败则存储结果唤醒。
    if (rs == 1 || rs == 0) {
        //将tx-m返回的结果存储。
        waitTask.setState(rs);
        //唤醒任务
        waitTask.signalTask();

        return;
    }
    //2:走到这里说明网络或连接错误,通过http请求询问tx-m结果状态,同通知txm清理事务数据。
    rs = txManagerService.cleanNotifyTransactionHttp(groupId, waitTaskId);
    if (rs == 1 || rs == 0) {
        waitTask.setState(rs);
        waitTask.signalTask();

        return;
    }

    //3:走到这里说明tx-m异常,则添加到补偿队列
    waitTask.setState(-100);
    waitTask.signalTask();

}

3)2)5)2):通知tx-m最终执行结果。

com.codingapi.tx.control.service.impl.ActionTServiceImpl#notifyWaitTask对应这步。该方法通知参与方提交还是回滚,并通过自旋方式等待参与方最终结果,上报给tx-m。

在这里插入图片描述

private String notifyWaitTask(TaskGroup task, int state) {
    String res;
    //传递tx-m返回结果
    task.setState(state);
    //唤醒db连接,根据state提交或回滚
    task.signalTask();
    int count = 0;

    //自旋等待db连接最终执行结果
    while (true) {
        if (task.isRemove()) {

            if (task.getState() == TaskState.rollback.getCode()
                || task.getState() == TaskState.commit.getCode()) {

                res = "1";
            } else {
                res = "0";
            }
            break;
        }
        if (count > 1000) {
            //已经通知了,有可能失败.
            res = "2";
            break;
        }

        count++;
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //返回结果给tx-m
    return res;
}

3)2)5)3):超时处理 todo

dataSourceService.schedule(getGroupId(), waitTask);

3)2)6):回滚

同事务发起方一样。

3)3):tx-m

3)3)1):启动流程

向spring注入ServerListener对象,该对象继承com.codingapi.tm.listener.ServerListener,在servlet容器启动时触发。

就是简单的启动netty服务。

@Component
    public class ServerListener implements ServletContextListener {

    private WebApplicationContext springContext;


    private InitService initService;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        springContext = WebApplicationContextUtils
                .getWebApplicationContext(event.getServletContext());
        initService = springContext.getBean(InitService.class);
        //启动netty内嵌服务
        initService.start();
    }


    @Override
    public void contextDestroyed(ServletContextEvent event) {
        initService.close();
    }

}

com.codingapi.tm.listener.service.impl.InitServiceImpl

@Override
public void start() {
    /**加载本地服务信息**/

    Constants.socketPort = configReader.getSocketPort();
    Constants.maxConnection = configReader.getSocketMaxConnection();
    nettyServerService.start();
}

com.codingapi.tm.netty.service.impl.NettyServerServiceImpl#start

public void start() {
    final int heartTime = configReader.getTransactionNettyHeartTime()+10;
    txCoreServerHandler = new TxCoreServerHandler(threadPool,nettyService);
    bossGroup = new NioEventLoopGroup(50); // (1)
    workerGroup = new NioEventLoopGroup();
    try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast("timeout", new IdleStateHandler(heartTime, heartTime, heartTime, TimeUnit.SECONDS));

                        ch.pipeline().addLast(new LengthFieldPrepender(4, false));
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));

                        ch.pipeline().addLast(txCoreServerHandler);
                    }
                });

        // Start the server.
        b.bind(Constants.socketPort);
        logger.info("Socket started on port(s): " + Constants.socketPort + " (socket)");

    } catch (Exception e) {
        // Shut down all event loops to terminate all threads.
        e.printStackTrace();
    }
}

com.codingapi.tm.netty.handler.TxCoreServerHandler

该方法处理请求。

@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
    final String json = SocketUtils.getJson(msg);
    logger.debug("request->"+json);
    //接收请求后,线程池异步处理,提高吞吐。
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            service(json,ctx);
        }
    });
}

通过命令类型寻找对应类处理。

private void service(String json,ChannelHandlerContext ctx){
    if (StringUtils.isNotEmpty(json)) {
        JSONObject jsonObject = JSONObject.parseObject(json);
        //获取命令类型
        String action = jsonObject.getString("a");
        String key = jsonObject.getString("k");
        JSONObject params = JSONObject.parseObject(jsonObject.getString("p"));//获取请求参数
        //获取客户端地址
        String channelAddress = ctx.channel().remoteAddress().toString();
        //通过命令类型,从spring容器中寻找对应的类处理
        IActionService actionService =  nettyService.getActionService(action);

        String res = actionService.execute(channelAddress,key,params);

        //封装响应结果,并刷出站
        JSONObject resObj = new JSONObject();
        resObj.put("k", key);
        resObj.put("d", res);
        SocketUtils.sendMsg(ctx,resObj.toString());

    }
}

com.codingapi.tm.netty.service.impl.NettyServiceImpl#getActionService

@Override
public IActionService getActionService(String action) {
    return spring.getBean(action,IActionService.class);
}

3)3)2):创建事务组

发起者向tx-m发起请求,命令类型为cg

com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#createTransactionGroup

@Override
public void createTransactionGroup(String groupId) {
    JSONObject jsonObject = new JSONObject();
    //事务组
    jsonObject.put("g", groupId);
    Request request = new Request("cg", jsonObject.toString());
    SocketManager.getInstance().sendMsg(request);
}

直接看tx-m cg对应的处理类实现。

com.codingapi.tm.netty.service.impl.ActionCGServiceImpl#execute

@Override
public String execute(String channelAddress, String key, JSONObject params ) {
    String res = "";
    //获取事务组id
    String groupId = params.getString("g");
    //创建事务组
    TxGroup txGroup = txManagerService.createTransactionGroup(groupId);
    if(txGroup!=null) {
        //设置当前时间到事务对象中。
        txGroup.setNowTime(System.currentTimeMillis());
        res = txGroup.toJsonString(false);
    }else {
        res = "";
    }
    return res;
}

简单的将事务对象存储redis中。

com.codingapi.tm.manager.service.impl.TxManagerServiceImpl#createTransactionGroup

@Override
public TxGroup createTransactionGroup(String groupId) {
    logger.info("创建事物组");
    //新建事务对象
    TxGroup txGroup = new TxGroup();
    //从redis缓存中获取是否有补偿信息
    if (compensateService.getCompensateByGroupId(groupId)!=null) {
        txGroup.setIsCompensate(1);
    }

    txGroup.setStartTime(System.currentTimeMillis());
    txGroup.setGroupId(groupId);
    //将事务信息存储redis中
    String key = configReader.getKeyPrefix() + groupId;
    redisServerService.saveTransaction(key, txGroup.toJsonString());
    return txGroup;
}

3)3)3):加入事务组

参与者向tx-m发送加入事务组请求,数据如下:

    @Override
    public TxGroup addTransactionGroup(String groupId, String taskId, boolean isGroup, String methodStr) {
        JSONObject jsonObject = new JSONObject();
        //groupId
        jsonObject.put("g", groupId);
        //参与者方taskId
        jsonObject.put("t", taskId);
        //参与者业务方法名
        jsonObject.put("ms", methodStr);
        //是否同一个事务组下
        jsonObject.put("s", isGroup ? 1 : 0);
        Request request = new Request("atg", jsonObject.toString());
        String json =  SocketManager.getInstance().sendMsg(request);
        return TxGroup.parser(json);
    }

直接看tx-m atg对应的处理类实现。

com.codingapi.tm.netty.service.impl.ActionATGServiceImpl#execute

@Override
public String execute(String channelAddress,String key,JSONObject params ) {
    String res = "";
    //groupId
    String groupId = params.getString("g");
    //参与者方taskId
    String taskId = params.getString("t");
    //参与者业务方法名
    String methodStr = params.getString("ms");
    //获取参与者执行结果
    int isGroup = params.getInteger("s");
    //添加事务组
    TxGroup txGroup = txManagerService.addTransactionGroup(groupId, taskId, isGroup, channelAddress, methodStr);

    if(txGroup!=null) {
        txGroup.setNowTime(System.currentTimeMillis());
        res = txGroup.toJsonString(false);
    }else {
         res = "";
    }
    return res;
}

com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#addTransactionGroup

@Override
public TxGroup addTransactionGroup(String groupId, String taskId, int isGroup, String channelAddress, String methodStr) {
    logger.info("添加事务组子对象...");
    //构建key,并从redis获取事务信息
    String key = getTxGroupKey(groupId);
    TxGroup txGroup = getTxGroup(groupId);
    //说明事务在redis中不存在,则直接返回。
    if (txGroup==null) {
        return null;
    }
    //封装参与方事务信息
    TxInfo txInfo = new TxInfo();
    txInfo.setChannelAddress(channelAddress);
    txInfo.setKid(taskId);//参与方内部的任务,主要用来接受当所有参与方执行后,发起者告知txm是否通知大伙儿一起提交还是回滚。
    txInfo.setAddress(Constants.address);
    txInfo.setIsGroup(isGroup);
    txInfo.setMethodStr(methodStr);
    //通过发起方地址,从缓存中寻找到注册信息。
    ModelInfo modelInfo =  ModelInfoManager.getInstance().getModelByChannelName(channelAddress);
    if(modelInfo!=null) {//将发起方地址信息,存储事务信息中
        txInfo.setUniqueKey(modelInfo.getUniqueKey());
        txInfo.setModelIpAddress(modelInfo.getIpAddress());
        txInfo.setModel(modelInfo.getModel());
    }
    //
    txGroup.addTransactionInfo(txInfo);
    //添加redis缓存中
    redisServerService.saveTransaction(key, txGroup.toJsonString());

    return txGroup;
}

3)3)4):关闭事务组

com.codingapi.tx.netty.service.impl.MQTxManagerServiceImpl#closeTransactionGroup

    @Override
    public int closeTransactionGroup(final String groupId, final int state) {
        JSONObject jsonObject = new JSONObject();
        //groupId
        jsonObject.put("g", groupId);
        //发起者业务方法执行结果,通过该结果,tx-m决定大伙儿一起提交还是回滚。
        jsonObject.put("s", state);
        Request request = new Request("ctg", jsonObject.toString());
        String json =  SocketManager.getInstance().sendMsg(request);
        try {
            return Integer.parseInt(json);
        }catch (Exception e){
            return 0;
        }
    }

直接看tx-m ctg对应的处理类实现,该方法主要流程如下:

  1. 当接收到发起方的通知后,根据发起方的执行结果,异步通知每个参与者提交或回滚,然后阻塞等待结果。
  2. 参与者收到tx-m通知后,执行提交或回滚。执行结束,将执行结果通知tx-m。
  3. tx-m此时都在阻塞等待参与者最终结果,然后响应给发起方。当有一个参与者没执行成功,则发起方会回滚,并且记录补偿日志。

在这里插入图片描述

com.codingapi.tm.netty.service.impl.ActionCTGServiceImpl#execute#execute

@Override
public String execute(String channelAddress, String key, JSONObject params ) {
    //获取事务组
    String groupId = params.getString("g");
    //获取参与者执行结果
    int state = params.getInteger("s");
    String res = String.valueOf(txManagerService.closeTransactionGroup(groupId,state));
    return res;
}

com.codingapi.tm.manager.service.impl.TxManagerServiceImpl#closeTransactionGroup

@Override
public int closeTransactionGroup(String groupId,int state) {
    logger.info("关闭事务组");
    //构建key,从redis获取事务信息
    String key = getTxGroupKey(groupId);
    TxGroup txGroup = getTxGroup(groupId);
    //为空则说明事务不存在,返回失败。
    if(txGroup==null){
        return 0;
    }
    //设置发起方事务执行结果
    txGroup.setState(state);
    //标记事务结束
    txGroup.setHasOver(1);
    //存储redis中
    redisServerService.saveTransaction(key,txGroup.toJsonString());
    return transactionConfirmService.confirm(txGroup);
}

该方法执行流程如下:

  1. 从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。
  2. 事务不满足直接回滚事务
  3. 查看所有参与者是否执行成功。
  4. 如果所有参与者执行成功,则从redis中删除事务组
  5. 返回结果给发起者。

com.codingapi.tm.manager.service.impl.TxManagerSenderServiceImpl#confirm

@Override
public int confirm(TxGroup txGroup) {
    //1:从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。
    setChannel(txGroup.getList());

    //2:事务不满足直接回滚事务
    if (txGroup.getState()==0) {
        transaction(txGroup, 0);
        return 0;
    }

    if(txGroup.getRollback()==1){
        transaction(txGroup, 0);
        return -1;
    }

    //3:查看所有参与者是否执行成功。
    boolean hasOk =  transaction(txGroup, 1);
    //4:如果所有参与者执行成功,则从redis中删除事务组
    txManagerService.dealTxGroup(txGroup,hasOk);
    //5:返回结果给发起者。
    return hasOk?1:0;
}

从事务中获取所有参与者网络信息。然后检查网络,绑定管道对象。

private void setChannel(List<TxInfo> list) {
    for (TxInfo info : list) {
        if(Constants.address.equals(info.getAddress())){
            //参与者记录的tx-m地址和当前tx-m地址一样,则查看管道是否存活
            Channel channel = SocketManager.getInstance().getChannelByModelName(info.getChannelAddress());
            if (channel != null &&channel.isActive()) {
                //存活则绑定管道到参与者信息中
                ChannelSender sender = new ChannelSender();
                sender.setChannel(channel);
                info.setChannel(sender);
            }
        }else{
            //不存在说明tx-m触发了变更
            ChannelSender sender = new ChannelSender();
            sender.setAddress(info.getAddress());
            sender.setModelName(info.getChannelAddress());
            info.setChannel(sender);
        }
    }
}

查看所有参与者是否执行成功,就是简单的异步线程访问所有的参与者,然后汇总结果。

private boolean transaction(final TxGroup txGroup, final int checkSate) {
    if (checkSate == 1) {
        logger.info("事务提交");
        //补偿请求,加载历史数据
        if (txGroup.getIsCompensate() == 1) {
            compensateService.reloadCompensate(txGroup);
        }

        CountDownLatchHelper<Boolean> countDownLatchHelper = new CountDownLatchHelper<Boolean>();
        for (final TxInfo txInfo : txGroup.getList()) {
            if (txInfo.getIsGroup() == 0) {
                countDownLatchHelper.addExecute(new IExecute<Boolean>() {
                    @Override
                    public Boolean execute() {//channel空判断
                        if(txInfo.getChannel()==null){
                            return false;
                        }

                        final JSONObject jsonObject = new JSONObject();
                        jsonObject.put("a", "t");

                        /** 补偿请求 **/
                        if (txGroup.getIsCompensate() == 1) {
                            jsonObject.put("c", txInfo.getIsCommit());
                        } else { //正常业务
                            jsonObject.put("c", checkSate);
                        }
                        //获取参与方的任务id
                        jsonObject.put("t", txInfo.getKid());
                        final String key = KidUtils.generateShortUuid();
                        jsonObject.put("k", key);
                        //新建一个任务
                        Task task = ConditionUtils.getInstance().createTask(key);
                        //超时处理,客户端超过5秒没有返回db执行状态,则设置结果为失败
                        ScheduledFuture future = schedule(key, configReader.getTransactionNettyDelayTime());
                        //向客户端发送消息
                        threadAwaitSend(task, txInfo, jsonObject.toJSONString());
                        //阻塞等待客户端回复消息
                        task.awaitTask();
                        //超时任务还在,则取消。
                        if (!future.isDone()) {
                            future.cancel(false);
                        }

                        try {//获取参与者执行结果
                            String data = (String) task.getBack().doing();
                            // 1  成功 0 失败 -1 task为空 -2 超过
                            boolean res = "1".equals(data);

                            if (res) {
                                txInfo.setNotify(1);
                            }

                            return res;
                        } catch (Throwable throwable) {
                            throwable.printStackTrace();
                            return false;
                        } finally {//删除任务
                            task.remove();
                        }
                    }
                });
            }
        }
        //异步获取所有参与者db执行结果
        List<Boolean> hasOks = countDownLatchHelper.execute().getData();
        //更新事务组信息
        String key = configReader.getKeyPrefix() + txGroup.getGroupId();
        redisServerService.saveTransaction(key, txGroup.toJsonString());
        //标记是否所有参与者执行成功,只要有一个失败则返回失败。
        boolean hasOk = true;
        for (boolean bl : hasOks) {
            if (!bl) {
                hasOk = false;
                break;
            }
        }
        logger.info("--->" + hasOk + ",group:" + txGroup.getGroupId() + ",state:" + checkSate + ",list:" + txGroup.toJsonString());
        return hasOk;
    }

    logger.info("事务回滚");
    //回滚操作只发送通过不需要等待确认
    for (TxInfo txInfo : txGroup.getList()) {
        if(txInfo.getChannel()!=null) {
            if (txInfo.getIsGroup() == 0) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("a", "t");
                jsonObject.put("c", checkSate);
                jsonObject.put("t", txInfo.getKid());
                String key = KidUtils.generateShortUuid();
                jsonObject.put("k", key);
                txInfo.getChannel().send(jsonObject.toJSONString());
            }
        }
    }
    //删除事务组
    txManagerService.deleteTxGroup(txGroup);
    return true;

}

参与者超过5秒没返回db执行状态,则设置结果为失败

private ScheduledFuture schedule(final String key, int delayTime) {
    ScheduledFuture future = executorService.schedule(new Runnable() {
        @Override
        public void run() {
            Task task = ConditionUtils.getInstance().getTask(key);
            if(task!=null&&!task.isNotify()) {
                task.setBack(new IBack() {
                    @Override
                    public Object doing(Object... objs) throws Throwable {
                        return "-2";
                    }
                });
                task.signalTask();
            }
        }
    }, delayTime, TimeUnit.SECONDS);

    return future;
}

向客户端发送消息

private void threadAwaitSend(final Task task, final TxInfo txInfo, final String msg){
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            while (!task.isAwait() && !Thread.currentThread().interrupted()) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //判断管道是否关闭
            if(txInfo!=null&&txInfo.getChannel()!=null) {
                //发送消息给参与者,执行db操作,参与者返回状态时会唤醒tx-m
                txInfo.getChannel().send(msg,task);
            }else{
                //管道不存在,则设置失败,并唤醒。
                task.setBack(new IBack() {
                    @Override
                    public Object doing(Object... objs) throws Throwable {
                        return "-2";
                    }
                });
                task.signalTask();
            }
        }
    });

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值