分布式事务方案学习笔记(二)——TCC深入分析

TCC介绍

TCC是一种分布式事务的实现方式,其满足BASE的柔性事务

https://blog.csdn.net/qq_34996727/article/details/80628962

TCC整体步骤

TCC型整体事务其实就是两阶段提交,先Try准备数据,完成资源检查,成功之后再Confirm确认执行业务,如果Trying阶段失败,则取消执行业务
tcc三大步骤

  • Try:尝试执行业务
    完成所有业务检查(一致性)
    预留必须业务资源(准隔离性)

  • Confirm:确认执行业务
    真正执行业务
    不作任何业务检查
    只使用Try阶段预留的业务资源
    Confirm操作要满足幂等性

  • Cancel: 取消执行业务
    释放Try阶段预留的业务资源
    Cancel操作要满足幂等性

TCC框架Java源码下载

在GitHub上搜索tcc,找到第一个下载源码,导入到IDE中分析

运行demo

根据index.jsp发现该demo暴露给用户的是 http://localhost:XXX/user/2000/shop/1

  • 首先配置好tcc持久化的数据库
    数据库配置

  • 配置好Tomcat

  • 访问暴露的接口

分析项目结构

tcc项目结构

  • tcc-transaction-api
    这个模块放的是一些相关的API
  • tcc-transaction-core
    主要存放一些事务核心的类,比如两个AoP的拦截器,Repository库等
  • tcc-transaction-server
    事务的管控台,提供前台页面,暂时用不到
  • tcc-transaction-spring
    Spring框架和事务通过继承整合在这里,比如SpringJdbcTransactionRepository类
  • tcc-transaction-tutorial-sample
    业务模块
  • tcc-transaction-unit-test
    测试以及工具模块

项目依赖图

源码分析

1.流程

TCC源码流程图

2.web.xml

因为刚刚运行demo的时候,是直接访问暴露的接口,tomcat收到请求后就会找到资源路径像的web.xml,刚刚运行demo的时候,是访问tcc-transaction-dubbo-order模块的,所以先进order模块的web.xml,在这里,发现其引进了一些配置文件

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath*:config/spring/local/appcontext-*.xml,classpath:tcc-transaction.xml
    </param-value>
</context-param>

<context-param>其实就相当于全局的<init-param>,初始化servlet时可以获取,在这段代码中,appcontext-*.xml是将所有和业务相关的配置比如servler、datasource、dao、dubbo等引入,tcc-transaction.xml是将事务的相关配置引入,这个xml是放在transaction-spring模块的,也就是这个xml将一些配置类以bean的形式交给Spring管理

3.tcc-transaction.xml

<!-- 开启AoP注解解析器 -->
<aop:aspectj-autoproxy/>
<!-- 补偿事务拦截器 -->
 <bean id="compensableTransactionInterceptor"
          class="org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor">
    <property name="transactionConfigurator" ref="tccTransactionConfigurator"/>
</bean>

<!-- 资源协调员拦截器 -->
<bean id="resourceCoordinatorInterceptor"
        class="org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor">
    <property name="transactionConfigurator" ref="tccTransactionConfigurator"/>
</bean>

<bean id="tccCompensableAspect" class="org.mengyun.tcctransaction.spring.TccCompensableAspect">
    <property name="compensableTransactionInterceptor" ref="compensableTransactionInterceptor"/>
</bean>

<bean id="transactionContextAspect" class="org.mengyun.tcctransaction.spring.TccTransactionContextAspect">
    <property name="resourceCoordinatorInterceptor" ref="resourceCoordinatorInterceptor"/>
</bean>

先说其中两个最重要的bean,就是TccCompensableAspect和TccTransactionContextAspect,由名字可以得出这是两个切面

4.TccCompensableAspect和TccTransactionContextAspect切面

这两个切面都贴有@Aspect注解,且都实现了Ordered接口,改接口会调用getOrder方法获得order值,越小的优先级越高
通过阅读发现TccCompensableAspect的优先级比TccTransactionContextAspect高,因此当有方法被切入时,优先切入 TccCompensableAspect

  • TccCompensableAspect会对贴有Compensable注解的方法做增强
@Pointcut("@annotation(org.mengyun.tcctransaction.Compensable)")
public void compensableService() {}
  • TccTransactionContextAspect会对第一个参数为TransactionContext类型或者贴有Compensable注解的方法做增强
@Pointcut("execution(public * *(org.mengyun.tcctransaction.api.TransactionContext,..))||@annotation(org.mengyun.tcctransaction.Compensable)")
public void transactionContextCall() {}

5.第一个切面

因为修改数据时才有事务发生,所以可以直接从修改的请求开始,以OrerController开始,当请求过来时,执行placeOrder方法,其中执行placeOrderService的placeOrder方法,在这个方法中先获取商品的id,创建一个订单对象,接下啦就要执行makePayment方法支付,对金额进行修改

当点进去makePayment时,发现其方法上贴有@Compensable注解,并且指定了确认和取消的方法的方法名

@Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment"

因为贴有@Compensable注解,所以进入第一个切面TccCompensableAspect,进行环绕增强,调用compensableTransactionInterceptor(这个对象是在tcc-transaction.xml中被管理注入的)的interceptCompensableMethod方法,将ProceedingJoinPoint执行链传入

public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
    TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs());
    MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, true);
    switch (methodType) {
        case ROOT:
            return rootMethodProceed(pjp);
        case PROVIDER:
            return providerMethodProceed(pjp, transactionContext);
        default:
            return pjp.proceed();
    }
}

首先通过getTransactionContextFromArgs方法获取TransactionContext对象,该方法是通过遍历真实方法的参数列表获得TransactionContext类型的参数;
将null和true传入calculateMethodType方法里,很容易发现返回的methodType是ROOT,即当前方法类型处于根环境,接着往下执行rootMethodProceed方法

6.创建根环境的全局事务管理器

rootMethodProceed方法中

private Object rootMethodProceed(ProceedingJoinPoint pjp) throws Throwable {
        //创建根环境的全局事务管理器
        transactionConfigurator.getTransactionManager().begin();
        Object returnValue = null;
        try {
            returnValue = pjp.proceed();
        } catch (Throwable tryingException) {
            if (tryingException instanceof OptimisticLockException
                    || ExceptionUtils.getRootCause(tryingException) instanceof OptimisticLockException) {
                //do not rollback, waiting for recovery job
            } else {
                Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
                logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);
                transactionConfigurator.getTransactionManager().rollback();
            }
            throw tryingException;
        }
        transactionConfigurator.getTransactionManager().commit();
        return returnValue;
    }

首先看看transactionConfigurator这个字段,这个字段是在tcc-transaction.xml中通过set方法注入的,而其真实对象是TccTransactionConfigurator对象,是单例的。
接着看getTransactionManager这个方法,顾名思义这是获得事务管理器,怎么获得的,一直往下找到其实现类,其实就是根据transactionConfigurator创建了一个新的事务管理器transactionManager对象并返回(此时事务管理器的引用也存于TransactionConfigurator内)。
这时再走到其begin方法

public void begin() {
    Transaction transaction = new Transaction(TransactionType.ROOT);
    TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
    transactionRepository.create(transaction);
    threadLocalTransaction.set(transaction);
}

在该方法中,先创建一个根环境的事务对象,在创建这个事务对象时,先创建一个事务id -> TransactionXid,这个id由globalTransactionId全局事务id和branchQualifier分支id组成,这样就确定了该事务对象在分布式系统中的唯一性,接着将事务状态设置为TRYING,并设置其事务类型为ROOT

transactionRepository对象通过注入的方式获得,其实这个就是连接tcc数据库的dao,真实对象在本地项目tcc-transaction-dubbo-order中的config.spring.local中appcontext-service-dao.xml有配置,其真实类型是SpringJdbcTransactionRepository,继承JdbcTransactionRepository,因为order、capital、redpacket是3个独立的服务,所以每一个模块中都有appcontext-service-dao.xml来配置连接池

通过transactionRepository对象调用create方法将创建的事务对象保存在本地,其最终调用的是JdbcTransactionRepository类中的doCreate方法,实际上就是将序列化后的事务对象和一些相关信息保存在数据库中

StringBuilder builder = new StringBuilder();
builder.append("INSERT INTO " + getTableName() +
        "(GLOBAL_TX_ID,BRANCH_QUALIFIER,TRANSACTION_TYPE,CONTENT,STATUS,RETRIED_COUNT,CREATE_TIME,LAST_UPDATE_TIME,VERSION");
builder.append(StringUtils.isNotEmpty(domain) ? ",DOMAIN ) VALUES (?,?,?,?,?,?,?,?,?,?)" : ") VALUES (?,?,?,?,?,?,?,?,?)");

其中getTableName()方法是从当前TransactionRepository中拿到前缀拼接成表名,这个前缀是在刚刚的appcontext-service-dao.xml中配置,其配置了前缀和连接池(此时的前缀是"_ORD",连接池是tccDataSource),最终将数据保存在当前模块中的tcc库中的tcc_transaction_ord表,并且将其设置到缓存中

最后将这个事务对象也绑定到本地线程中,``threadLocalTransaction是TransactionManager的一个字段,是一个ThreadLocal 完成begin方法之后,其实也就创建完了一个根环境的全局事务管理器,这个根环境其实就是order订单环境,接着回到rootMethodProceed`方法继续往下执行

7.第二个切面

pjp.proceed()方法继续执行这条执行链,但是因为我们最开始调用的makePayment方法是贴有Compensable注解的,所以也会被TccTransactionContextAspect切入,只不过其优先级较低,作为第二个切面执行,其也是用环绕增强,调用ResourceCoordinatorInterceptor的interceptTransactionContextMethod进行方法增强

public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable {
    //获得事务管理器并取出绑定在本地线程中的事务
    Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
    //此时满足条件
    if (transaction != null && transaction.getStatus().equals(TransactionStatus.TRYING)) {
        TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs());
        Compensable compensable = getCompensable(pjp);
        //第一次进来:传入的为null和true,得到methodType为ROOT
        MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, compensable != null ? true : false);
        switch (methodType) {
            case ROOT:
                generateAndEnlistRootParticipant(pjp);
                break;
            case CONSUMER:
                generateAndEnlistConsumerParticipant(pjp);
                break;
            case PROVIDER:
                generateAndEnlistProviderParticipant(pjp);
                break;
        }
    }
    //继续往下执行真正业务
    return pjp.proceed(pjp.getArgs());
}

8.添加根环境参与者

methodType为ROOT,调用generateAndEnlistRootParticipant方法

private Participant generateAndEnlistRootParticipant(ProceedingJoinPoint pjp) {
    //通过环绕增强传入的参数(可以真正执行业务方法,可以通过这个参数获取调用目标对象、调用方法传入的参数集合)
    //获取到调用的真实方法的签名
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    //通过方法签名获取到该方法的引用对象(后续需要通过这个method反射调用)
    Method method = signature.getMethod();
    //获取真实调用方法上的Compensable注解
    Compensable compensable = getCompensable(pjp);
    //获取注解上的confirmMethod和cancelMethodName参数值(对应的是业务的确认方法的方法名字符串)
    String confirmMethodName = compensable.confirmMethod();
    String cancelMethodName = compensable.cancelMethod();
    //获取绑定在线程中的transaction对象
    Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
    //创建xid对象,关联的是全局事务的id
    TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId());
    //通过pjp参数反射获取到真实方法调用对象(其实就是PlaceOrderServiceImpl对象字节码)
    Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes());
    //封装InvocationContext,把confirmMethod和cancelMethod需要通过反射进行调用的信息存储起来,后期通过反射的方式去调用confirmMethod,实际上就可以看做是Method
    InvocationContext confirmInvocation = new InvocationContext(targetClass,
            confirmMethodName,
            method.getParameterTypes(), pjp.getArgs());
    InvocationContext cancelInvocation = new InvocationContext(targetClass,
            cancelMethodName,
            method.getParameterTypes(), pjp.getArgs());
    //定义事务参与者
    Participant participant =
            new Participant(
                    xid,
                    //定义终结者,在特定的时机执行confirmMethod或者cancelMethod
                    new Terminator(confirmInvocation, cancelInvocation));
    //在transaction添加参与者
    transaction.enlistParticipant(participant);
    TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
    //把transaction更新到数据库中.
    transactionRepository.update(transaction);
    return participant;
}

update方法中,将新的transaction更新到tcc数据库,这里的表依然还是tcc_transaction_ord,这时的transaction对象已经添加了根环境(Order)的参与者
order参与者
回到generateAndEnlistRootParticipant继续往下执行

9.创建当前主环境(Order)的其他消费参与者

此时正常执行业务逻辑

@Compensable(confirmMethod = "confirmMakePayment", cancelMethod = "cancelMakePayment")
//开启本地事务
@Transactional
public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
    System.out.println("order try make payment called");
    //修改订单状态
    order.pay(redPacketPayAmount, capitalPayAmount);
    //更新本地数据库(注意此时还在事务中,还未提交,相当于try操作)
    orderRepository.updateOrder(order);
    //capitalTradeOrderService和redPacketTradeOrderService是dubbo动态代理出来本地代理类      
    String result = capitalTradeOrderService.record(null, buildCapitalTradeOrderDto(order));
    String result2 = redPacketTradeOrderService.record(null, buildRedPacketTradeOrderDto(order));
}

在执行record方法时,因为其第一个参数是TransactionContext类型,并且没有贴*@Compensable*注解,所以会且只会被第二个切面切到

  • 注意:由于此时的capitalTradeOrderService是本地的一个代理类,所以这个record方法实际上是本地代理对象中的一个方法,在这个方法里中才通过dubbo(RPC)调用远程的record业务方法返回结果,真正的业务方法的实现是在tcc-transaction-dubbo-capital模块内,而刚刚分析到的程序的执行还是在tcc-transaction-dubbo-order模块中的代理对象上,是没有贴上*@Compensable*注解的,关于动态代理可以看我的一篇文章

https://blog.csdn.net/qq_40233503/article/details/87661593

被第二个切面切到后,执行相应的增强,首先获得绑定在本地线程中的事务对象,因为此时方法没有注解,所以calculateMethodType方法传入的第二个参数为null,此时获得的methodType为CONSUMER,执行generateAndEnlistConsumerParticipant方法

private Participant generateAndEnlistConsumerParticipant(ProceedingJoinPoint pjp) {
    //获取到真实调用方法的方法应用
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();
    //获取绑定在当前线程中的transaction对象
    Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
    //创建xid对象,还是关联全局事务的id
    TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId());
    //获取TransactionContext在参数列表中所在的位置====>0
    int position = CompensableMethodUtils.getTransactionContextParamPosition(((MethodSignature) pjp.getSignature()).getParameterTypes());
    //pjp.getArgs()调用方法的时候传的参数的集合(数组)
    //把第0个参数的值设置成TransactionContext对象关联分支的xid对象,传入transaction.getStatus().getId(),此时是TRYING状态 1
    pjp.getArgs()[position] = new TransactionContext(xid, transaction.getStatus().getId());
    //try方法的参数数组
    Object[] tryArgs = pjp.getArgs();
    //创建confirm方法的参数数组
    Object[] confirmArgs = new Object[tryArgs.length];
    //创建cancel方法的参数数组
    Object[] cancelArgs = new Object[tryArgs.length];
    //把try方法的参数数组的值拷贝到confirm方法的参数数组中
    System.arraycopy(tryArgs, 0, confirmArgs, 0, tryArgs.length);
    //把confirm方法的参数数组的第0个参数传入TransactionContext对象,传入CONFIRMING状态  2
    confirmArgs[position] = new TransactionContext(xid, TransactionStatus.CONFIRMING.getId());
    //把try方法的参数数组的值拷贝到cancel方法的参数数组中
    System.arraycopy(tryArgs, 0, cancelArgs, 0, tryArgs.length);
    //把cancel方法的参数数组的第0个参数传入TransactionContext对象,传入CANCELLING状态  3
    cancelArgs[position] = new TransactionContext(xid, TransactionStatus.CANCELLING.getId());
    //获取到目标方法所在接口类的字节码对象  -> 此时是CapitalTradeOrderService
    Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes());
    //创建InvocationContext对象,把调用confirmMethod方法所需要的信息封装起来,后期通过反射的方式进行调用
    InvocationContext confirmInvocation = new InvocationContext(targetClass, method.getName(), method.getParameterTypes(), confirmArgs);
    //创建InvocationContext对象,把调用cancelmMethod方法所需要的信息封装起来,后期通过反射的方式进行调用
    InvocationContext cancelInvocation = new InvocationContext(targetClass, method.getName(), method.getParameterTypes(), cancelArgs);
    //创建事务参与者
    Participant participant =
            new Participant(
                    xid,
                    new Terminator(confirmInvocation, cancelInvocation));
    //transaction添加该事务参与者
    transaction.enlistParticipant(participant);
    //获得连接tcc数据库的dao,真实对象是SpringJdbcTransactionRepository,jdbcUrl是连接Tcc数据库的
    TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
    //把transaction更新持久化到数据库中
    transactionRepository.update(transaction);
    return participant;
}

需要注意的是这里装进参与者的两个方法都是record方法

此时已生成了capital消费者的参与者
主环境capital参与者

接着执行往下完成执行链,此时程序便RPC远程调用实现类的record方法,接着执行相应的操作

makePayment方法中执行完==capitalTradeOrderService.record()方法后便创建完了一个capital消费参与者先不管其后续操作,那么在执行完redPacketTradeOrderService.record()==就会创建完redpacket的消费参与者
所以,左边的部分就创建完成了
order部分

10.在Capital模块创建分支Transaction对象

此时程序通过远程调用来到了Capital模块的CapitalTradeOrderServiceImpl实现类调用record方法,因为这是一个请求来到Capital模块,所以在Capital中这是一个新的线程

@Override
@Compensable(confirmMethod = "confirmRecord", cancelMethod = "cancelRecord")
@Transactional
public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
    System.out.println("capital try record called");
    TradeOrder tradeOrder = new TradeOrder(
            tradeOrderDto.getSelfUserId(),
            tradeOrderDto.getOppositeUserId(),
            tradeOrderDto.getMerchantOrderNo(),
            tradeOrderDto.getAmount()
    );
    tradeOrderRepository.insert(tradeOrder);
    CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
    transferFromAccount.transferFrom(tradeOrderDto.getAmount());
    capitalAccountRepository.save(transferFromAccount);
    return "success";
}

因为贴有==@Compensable==注解,所以在执行方法前会被第一个切面切入,由上面的流程可以得出经过第一个切面可以在本地创建一个transaction对象

public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
    //在调用方法传入的参数中是否包含TransactionContext的参数
    TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs());
    //判断当前事务参与者的角色,此时是PROVIDER
    MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, true);
    switch (methodType) {
        case ROOT:
            //执行主服务逻辑
            return rootMethodProceed(pjp);
        case PROVIDER:
            //执行服务提供者的逻辑
            return providerMethodProceed(pjp, transactionContext);
        default:
            return pjp.proceed();
    }
}

执行providerMethodProceed方法

private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext) throws Throwable {
//判断事务的状态处于哪个阶段,此时传进来的transactionContext处于TRYING阶段
switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
    case TRYING:
        //通过transactionManager(此时transactionManager是这个线程中新创建的),创建一个和根分布式事务同xid的分支事务对象,并绑定到b恩地线程中以及存储到数据库中
        transactionConfigurator.getTransactionManager().propagationNewBegin(transactionContext);
        return pjp.proceed();
    case CONFIRMING:
        try {
            transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
            transactionConfigurator.getTransactionManager().commit();
            //后面没有pjp.proceed();
        } catch (NoExistedTransactionException excepton) {
            //the transaction has been commit,ignore it.
        }
        break;
    case CANCELLING:
        try {
            transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
            transactionConfigurator.getTransactionManager().rollback();
        } catch (NoExistedTransactionException exception) {
            //the transaction has been rollback,ignore it.
        }
        break;
}

接着执行return pjp.proceed()方法,先执行pjp.proceed()往下执行完执行链

11.在Captial分支事务中添加Capital参与者

此时被第二个切面切到,此时methodType是PROVIDER,所以执行generateAndEnlistProviderParticipant方法

private Participant generateAndEnlistProviderParticipant(ProceedingJoinPoint pjp) {
    //获取到调用真实方法的方法引用
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();
    //获取到真实方法上的Compensable注解
    Compensable compensable = getCompensable(pjp);
    //获取到确认方法的名字
    String confirmMethodName = compensable.confirmMethod();
    //获取到取消方法的名字
    String cancelMethodName = compensable.cancelMethod();
    //获取当前线程中的transaction对象
    Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
    //创建TransactionXid,关联的全局事务id
    TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId());
    //获取到真实方法调用所在类的字节码对象
    Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes());
    //创建InvocationContext,封装反射调用confirmMethod方法需要的信息,后续需要使用的.
    InvocationContext confirmInvocation = new InvocationContext(targetClass, confirmMethodName,
            method.getParameterTypes(), pjp.getArgs());
    //创建InvocationContext,封装反射调用cancelMethod方法需要的信息,后续需要使用的.
    InvocationContext cancelInvocation = new InvocationContext(targetClass, cancelMethodName,
            method.getParameterTypes(), pjp.getArgs());
    //创建事务的参与者
    Participant participant =
            new Participant(
                    xid,
                    new Terminator(confirmInvocation, cancelInvocation));
    //transaction加入该事务参与者
    transaction.enlistParticipant(participant);
    //把事务对象更新到数据库中
    TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
    transactionRepository.update(transaction);
    return participant;
}

至此,已经创建完Capital的分布式事务

12.执行Capital的Try操作

程序回到interceptTransactionContextMethod方法中的最后一行,执行pjp.proceed(pjp.getArgs())方法,将参数列表传入往下执行,因为已经没有切面,所以开始执行record的真实业务方法

public String record(TransactionContext transactionContext, CapitalTradeOrderDto tradeOrderDto) {
    //tradeOrderDto是Order的数据传输对象,实际上就是存储修改数据前的数据,如果事务异常,用于后面的回滚操作
    System.out.println("capital try record called");
    TradeOrder tradeOrder = new TradeOrder(
            tradeOrderDto.getSelfUserId(),
            tradeOrderDto.getOppositeUserId(),
            tradeOrderDto.getMerchantOrderNo(),
            tradeOrderDto.getAmount()
    );
    //将之前的数据保存
    tradeOrderRepository.insert(tradeOrder);
    //修改Capital真正数据,并保存
    CapitalAccount transferFromAccount = capitalAccountRepository.findByUserId(tradeOrderDto.getSelfUserId());
    transferFromAccount.transferFrom(tradeOrderDto.getAmount());
    capitalAccountRepository.save(transferFromAccount);
    return "success";
}

因为贴有@Transactional标签,所以由本地事务管理,因为tradeOrderDto和transferFromAccount是同一个数据库的数据,也就是连接池一样,这里可以保证tradeOrderDto(之前的数据)和transferFromAccount(修改后的数据)一致的保存在数据库中

执行 return “success” ,一直返回参数,此时回到makePayment中,继续往下执行

13.执行Redpacket的Try操作

public void makePayment(Order order, BigDecimal redPacketPayAmount, BigDecimal capitalPayAmount) {
    System.out.println("order try make payment called");
    order.pay(redPacketPayAmount, capitalPayAmount);
    orderRepository.updateOrder(order);
    String result = capitalTradeOrderService.record(null, buildCapitalTradeOrderDto(order));

    /**********************继续往下执行*******************/
    String result2 = redPacketTradeOrderService.record(null, buildRedPacketTradeOrderDto(order));
}

相同的原理,RedPacket的分支事务也会被创建好,添加好参与者存在数据库中,然后也执行完Try操作,准备好数据以及保存了原始的数据在相同的数据库中

14.执行Confirm操作

执行完makePayment方法后,程序会回到之前所在点,继续执行完执行链

private Object rootMethodProceed(ProceedingJoinPoint pjp) throws Throwable {
        transactionConfigurator.getTransactionManager().begin();
        Object returnValue = null;
        try {
            
            /***********************继续往下执行***********************/
            returnValue = pjp.proceed();
            //如果正常执行完说明,本地的try方法和远程的try执行都没有问题.
        } catch (Throwable tryingException) {
            if (tryingException instanceof OptimisticLockException
                    || ExceptionUtils.getRootCause(tryingException) instanceof OptimisticLockException) {
                //do not rollback, waiting for recovery job

            } else {
                Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
                logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);
                //try出现异常,说明有些资源是处理不了的,需要把整个事务回滚.
                transactionConfigurator.getTransactionManager().rollback();
            }

            throw tryingException;
        }
        //执行commit提交方法
        transactionConfigurator.getTransactionManager().commit();

        return returnValue;
    }

假如一切正常,则会调用当前transactionManager对象的commit方法提交

public void commit() {
    //获取绑定在当前线程中的Transaction
    Transaction transaction = getCurrentTransaction();
    //修改全局事务的状态为TransactionStatus.CONFIRMING,正在提交
    transaction.changeStatus(TransactionStatus.CONFIRMING);
    //把transaction更新到数据库中。
    transactionConfigurator.getTransactionRepository().update(transaction);
    try {
        //commit,发起本地的confirm方法调用和远程的方法调用
        transaction.commit();
        //删除该事务.
        transactionConfigurator.getTransactionRepository().delete(transaction);
    } catch (Throwable commitException) {
        logger.error("compensable transaction confirm failed.", commitException);
        throw new ConfirmingException(commitException);
    }
}

commit方法中不断遍历当前事务对象的参与者,调用参与者的commit方法

最终执行到terminator对象的invoke方法

15.Order的Confirm操作

private Object invoke(InvocationContext invocationContext) {

    if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {
        try {
            //根据接口类型从Spring中找到真实类对象,这里是PaymentServiceImpl的Bean对象
            Object target = BeanFactoryAdapter.getBean(invocationContext.getTargetClass());
            if (target == null && !invocationContext.getTargetClass().isInterface()) {
                target = invocationContext.getTargetClass().newInstance();
            }
            Method method = null;
            //根据invocationContext中存储的方法名和方法参数类型获取到该方法的引用对象
            method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
            //调用service中的confirm方法,同时把参数传过去了.
            return method.invoke(target, invocationContext.getArgs());
        } catch (Exception e) {
            throw new SystemException(e);
        }
    }
    return null;
}

在PaymentServiceImpl的confirm方法中,修改订单的状态为CONFIRMED,此时order参与者已经提交

16.Capital和RedPacket的Confirm操作

接着执行到根事务中Capital的参与者,此时的target对象CapitalTradeOrderService的动态代理对象,因为之前存在这个参与者中的方法是record方法,所以执行的也是CapitalTradeOrderService动态代理对象的record方法。

因为这个方法的第一个参数是TransactionContext,所以会被第二个切面拦截到,但是因为此时Transaction对象的状态值不是TRYING,所以此时判断失败继续往下执行

来到Capital模块的真实record,因为贴有==@Compensable==标签,所以会被第一个切面拦截,执行providerMethodProceed方法

private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext) throws Throwable {
//判断事务的状态处于哪个阶段
switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
    case TRYING:
        transactionConfigurator.getTransactionManager().propagationNewBegin(transactionContext);
        return pjp.proceed();
    //此时transactionContext的状态是CONFIRMING
    case CONFIRMING:
        try {
            //从capital的tcc数据库中拿到transaction对象,并设置为CONFIRMING,绑定到当前线程中
            transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
            //将改为CONFIRMING状态的事务保存在数据库中,执行本地的confirm方法,执行完后删除这个事务
            transactionConfigurator.getTransactionManager().commit();
            //后面没有pjp.proceed();
        } catch (NoExistedTransactionException excepton) {
            //the transaction has been commit,ignore it.
            //如果不存在这个事务,就表示这个事务以及提交被删除了,不去处理
        }
        break;
    case CANCELLING:
        try {
            transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
            transactionConfigurator.getTransactionManager().rollback();
        } catch (NoExistedTransactionException exception) {
            //the transaction has been rollback,ignore it.
        }
        break;
}

至此,Capital的confirm操作执行完成,同理RedPacket的confirm操作也执行完成

17.最后删除根事务,整个分布式事务结束


若中途出现异常

1.如果是TRY的阶段出现了异常

此时错误会被抛回到第一个Aop切面的returnValue = pjp.proceed();位置,此时后面的catch捕获到,执行roolback()方法

public void rollback() {
    //从当前线程获取获取transaction对象(此时上下文环境是在根事务中,事务对象之前已经绑定在当前线程中)
    Transaction transaction = getCurrentTransaction();
    //改变事务状态为删除
    transaction.changeStatus(TransactionStatus.CANCELLING);
    //更新到数据库中
    transactionConfigurator.getTransactionRepository().update(transaction);
    try {
        //调用rollback
        transaction.rollback();
        //rollback成功后删除数库中transaction对象
        transactionConfigurator.getTransactionRepository().delete(transaction);
    } catch (Throwable rollbackException) {
        logger.error("compensable transaction rollback failed.", rollbackException);
        throw new CancellingException(rollbackException);
    }
}

rollback方法中,类似commit方法,拿到当前事务的所有参与者,调用所有参与者cancelMethod方法,程序运行到参与者的根分布式事务环境时,也会去遍历本地参与者执行cancleMethod方法,最后回到根事务,删除根事务,数据回滚完毕

2.如果是confirm出现异常

只要TRY通过了,那么数据一定可以操作,所以就一定会执行confirm,所以confirm不会出现操作数据库异常,那么这里的异常就有可能是服务器宕机,此时有对应的定时器来管理,confirm方法要实现幂等

3.如果是rollback异常

有对应的定时机制处理


TCC中的定时任务

定时器的3要素:

  1. job:具体需要定时执行的方法
  2. trigger:触发点(与job一一对应,定时时间,间隔在此配置)
  3. schedule:调度器(可以调度多个trigger,包括不同类的trigger)
  • 在tcc-transaction.xml中配置定时器以及定时任务
<!-- 定时注解解析器 -->
<task:annotation-driven/>
<!--quartz定时任务的框架-->
<bean id="recoverScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"/>
<!--定时任务-->
<bean id="recoverScheduledJob" class="org.mengyun.tcctransaction.spring.recover.RecoverScheduledJob"
        init-method="init">
    <property name="transactionConfigurator" ref="tccTransactionConfigurator"/>
    <property name="transactionRecovery" ref="transactionRecovery"/>
    <property name="scheduler" ref="recoverScheduler"/>
</bean>
  • 定义自己的定时任务RecoverScheduledJob
    首先初始化定时器,将定时调用的方法,定时任务的名称等(通过API的方式)
/****配置任务器****/
//根据一个MethodInvokingJobDetailFactoryBean工程制作一个jobDetail,即制作一个任务
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
//将目标对象设置到任务中(注意是对象,而不是类)
jobDetail.setTargetObject(transactionRecovery);
//将将要调用的方法设置的任务中
jobDetail.setTargetMethod("startRecover");
//设置一个任务名
jobDetail.setName("transactionRecoveryJob");
//false表示多个job不会并发运行
jobDetail.setConcurrent(false);
jobDetail.afterPropertiesSet();
/********配置触发点(定时时间)********/
CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
cronTrigger.setBeanName("transactionRecoveryCronTrigger");
//配置Cron表达式
cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression());
cronTrigger.setJobDetail(jobDetail.getObject());
cronTrigger.afterPropertiesSet();
/*******配置调度器********/
scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());

//执行调度器
scheduler.start();
  • TransactionRecovery对象中定时执行的方法startRecover
    在TransactionRecovery类中编写,主要是将超过设置存活时间的transaction对象从数据库查出,3台服务器都会去执行这个定时任务,所以会根据根事务还是分支事务做一个过滤,(分支事务通过判断的时间是超时时间120乘上重试次数30)
    判断事务状态是否为CONFIRMING,如果是CONFIRMING则会调用transaction.commit()去完成commit方法,解决先持久化到数据库了,到了下一个服务confirm中宕机或回传异常的问题

判断事务状态是否为CANCELLING或者事务类型为ROOT,CANCELLING主要是解决解决先持久化到数据库了,到了下一个服务rollback中宕机或回传异常的问题,而ROOT则是因为如果根事务数据库中还存在ROOT类型的事务的话,就一定是异常,将这个分布式事务全部回滚

通过分析,可以知道第一个切面TccCompensableAspect是用来创建事务对象和完成提交或回滚任务的的,第二个切面TccTransactionContextAspect是用来创建参与者的


最终流程

1. 先执行所有的TRY

  • 1.首先创建在当前环境(主服务 -> Order的PlaceOrderServiceImpl)创建根分布式事务
  • 2.接着在根分布式事务中创建ROOT参与者,也就是主服务的参与者,这里是Order
  • 3.执行主服务的业务,操作数据库,但未提交,相当于Try
  • 4.添加到根分布式事务中第一个参与者(Capital)
  • 5.在对应的分布模块中创建分支分布式事务,这个事务关联着根分布式事务,接在在这个事务中添加PROVIDER参与者,也当前服务的提供,这里就是Capital
  • 6.执行分支服务的业务,相当于Capital的Try操作
  • 7.同理,在根分布式事务中创建RedPacket参与者
  • 8.在对于的分布模块创建分支分布式事务,添加参与者
  • 9.RedPacket的Try操作

此时第一部分所有的TRY都完成
第一部分_TRY

2.再执行所有的CONFIRM

TRY阶段完成后,事务就一定会执行,因为try能完成,就证明资源时可操作的(事实上,在tcc中,try过后,资源已经被操作了)根分布式事务中的所有参与者依次进行提交操作,调用confirmMakePayment方法确认事务提交,并删除每个模块中的事务数据库中的事务对象

注意,makePayment方法业务贴有@Transactional标签,由本地事务控制,也就是Order模块真正确认数据提交是执行完该方法

3.若TRY阶段有一个失败,则执行整体执行回滚,并利用定时器一直定时观察保持数据最终一致

发布了25 篇原创文章 · 获赞 9 · 访问量 6644

猜你喜欢

转载自blog.csdn.net/qq_40233503/article/details/87828667