TCC介绍
TCC是一种分布式事务的实现方式,其满足BASE的柔性事务
https://blog.csdn.net/qq_34996727/article/details/80628962
TCC整体步骤
TCC型整体事务其实就是两阶段提交,先Try准备数据,完成资源检查,成功之后再Confirm确认执行业务,如果Trying阶段失败,则取消执行业务
-
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-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.流程
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)的参与者
回到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*注解的,关于动态代理可以看我的一篇文章
被第二个切面切到后,执行相应的增强,首先获得绑定在本地线程中的事务对象,因为此时方法没有注解,所以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消费者的参与者
接着执行往下完成执行链,此时程序便RPC远程调用实现类的record
方法,接着执行相应的操作
在makePayment
方法中执行完==capitalTradeOrderService.record()方法后便创建完了一个capital消费参与者先不管其后续操作,那么在执行完redPacketTradeOrderService.record()==就会创建完redpacket的消费参与者
所以,左边的部分就创建完成了
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要素:
- job:具体需要定时执行的方法
- trigger:触发点(与job一一对应,定时时间,间隔在此配置)
- 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都完成
2.再执行所有的CONFIRM
TRY阶段完成后,事务就一定会执行,因为try能完成,就证明资源时可操作的(事实上,在tcc中,try过后,资源已经被操作了)根分布式事务中的所有参与者依次进行提交操作,调用confirmMakePayment
方法确认事务提交,并删除每个模块中的事务数据库中的事务对象
注意,makePayment
方法业务贴有@Transactional标签,由本地事务控制,也就是Order模块真正确认数据提交是执行完该方法