tcc-transaction是TCC型事务java实现,具体项目地址  点我。本文通过tcc-transaction和Springcloud,分析下tcc-transaction的原理。

  要了解一个东西首先就要先会用它,tcc-transaction本身有多个模块,由于我们是和springcloud结合,所以我们只需要引入以下四个模块。

这次demo我们就两个服务,分别是Trade(交易)和account(积分)。交易在完成的同时,给当前用户增加指定的积分。各个项目只需要引入一个tcc配置类

@Configuration
@ImportResource(locations = "classpath:tcc-transaction.xml")
public class TccConfig { @Autowired
private TccDataSourceProperties properties; @Bean
public TransactionRepository transactionRepository(ObjectSerializer<?> serializer) {
SpringJdbcTransactionRepository repository = new SpringJdbcTransactionRepository();
repository.setDataSource(druidDataSource());
repository.setDomain(
"TRADE");
repository.setTbSuffix("_TRADE"
);
repository.setSerializer(serializer);
return repository;
} /**
* 设置恢复策略
* @return
*/
@Bean
public DefaultRecoverConfig defaultRecoverConfig(){
DefaultRecoverConfig defaultRecoverConfig=new DefaultRecoverConfig();
defaultRecoverConfig.setCronExpression("0 */1 * * * ?");
defaultRecoverConfig.setMaxRetryCount(120);
defaultRecoverConfig.setMaxRetryCount(30);
return defaultRecoverConfig;
} public DataSource druidDataSource(){
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(properties.getUrl());
datasource.setDriverClassName(properties.getDriverClassName());
datasource.setUsername(properties.getUsername());
datasource.setPassword(properties.getPassword());
datasource.setInitialSize(10);
datasource.setMinIdle(1);
datasource.setMaxWait(6000);
datasource.setMaxActive(10);
datasource.setMinEvictableIdleTimeMillis(1800000);
return datasource;
} @Bean
public ObjectSerializer<?> objectSerializer() {
return new KryoTransactionSerializer();
} }

TransactionRepository中的subffix要和表名一致,例如,trade表的建表语句如下

CREATE TABLE `TCC_TRANSACTION_TRADE` (
`TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT,
`DOMAIN` varchar(100) DEFAULT NULL,
`GLOBAL_TX_ID` varbinary(32) NOT NULL,
`BRANCH_QUALIFIER` varbinary(32) NOT NULL,
`CONTENT` varbinary(8000) DEFAULT NULL,
`STATUS` int(11) DEFAULT NULL,
`TRANSACTION_TYPE` int(11) DEFAULT NULL,
`RETRIED_COUNT` int(11) DEFAULT NULL,
`CREATE_TIME` datetime DEFAULT NULL,
`LAST_UPDATE_TIME` datetime DEFAULT NULL,
`VERSION` int(11) DEFAULT NULL,
PRIMARY KEY (`TRANSACTION_ID`),
UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;  

引入之后我们便可以正式的使用了,首先我们先发布account的tcc服务

@Compensable(confirmMethod = "confirmAddPoint",cancelMethod = "cancleAddPoint")
public boolean addPoint(TransactionContext transactionContext, Integer point, String userId) {
Account account = accountService.
selectOne(new EntityWrapper<Account>().eq("user_id", userId));
CheckUtil.notNull(account,"account not null");
return true;
} @Override
public void confirmAddPoint(TransactionContext transactionContext, Integer point, String userId) {
//建议用悲观锁先锁定资源再去更新
logger.info("确定 新增积分");
throw new RuntimeException("模拟confirm阶段异常抛出");
// Account account = accountService.
// selectOne(new EntityWrapper<Account>().eq("user_id", userId));
// Long point1 = account.getPoint();
// long l = point1 + point;
// account.setPoint(l);
// boolean update = accountService.updateById(account);
// CheckUtil.notFalse(update,"account update fail");
} @Override
public void cancleAddPoint(TransactionContext transactionContext, Integer point, String userId) {
//trying阶段没做任何操作
}

发布tcc服务有4个约束

  1. 在服务方法上加上@Compensable注解
  2. 服务方法第一个入参类型为org.mengyun.tcctransaction.api.TransactionContext
  3. 服务方法的入参都须能序列化(实现Serializable接口)
  4. try方法、confirm方法和cancel方法入参类型须一样

我们再去调用account的tcc服务

@Compensable(confirmMethod = "confirmPay",cancelMethod = "canselPay")
public boolean pay(Long tradeId,String userId) {
Trade trade = tradeService.selectById(tradeId);
CheckUtil.notNull(trade,"trade not null");
CheckUtil.notNull(trade.getPrice(),"price not null");
CheckUtil.notFalse(trade.getStatus().compareTo(TradeStatusEnum.CONFIRMINF.getValue())==0,"trade is finish");
Result result = accountRouteService.addPoint(null,trade.getPrice().intValue(), userId);
CheckUtil.notFalse(result.isSuccess(),"积分增加失败");
return true;
} @Override
public void confirmPay(Long tradeId,String userId) {
logger.info("开始confirm pay");
Trade trade = tradeService.selectById(tradeId);
//订单已完结不作任何处理
if (trade.getStatus().compareTo(TradeStatusEnum.CONFIRMINF.getValue())!=0){
return;
}
Trade trade1=new Trade();
trade1.setId(trade.getId());
trade1.setStatus(TradeStatusEnum.SUCCESS.getValue());
boolean update = tradeService.updateById(trade1);
CheckUtil.notFalse(update,"trade fail to success");
} @Override
public void canselPay(Long tradeId,String userId) {
Trade trade = tradeService.selectById(tradeId);
//订单已完结不作任何处理
if (trade.getStatus().compareTo(TradeStatusEnum.CONFIRMINF.getValue())!=0){
return;
}
Trade trade1=new Trade();
trade1.setId(trade.getId());
trade1.setStatus(TradeStatusEnum.FAIL.getValue());
boolean update = tradeService.updateById(trade1);
CheckUtil.notFalse(update,"trade fail to failed");
}

与发布一个Tcc服务不同,本地Tcc服务方法有三个约束:

  1. 在服务方法上加上@Compensable注解
  2. 服务方法的入参都须能序列化(实现Serializable接口)
  3. try方法、confirm方法和cancel方法入参类型须一样

即与发布Tcc服务不同的是本地Tcc服务无需声明服务方法第一个入参类型为org.mengyun.tcctransaction.api.TransactionContext。

更细节的使用我们还是要参看官网,下面我们启动服务,故意在confirm阶段抛异常,就会发现trade和account会一直在confirm阶段不断重试,然后我们将confirm阶段异常去掉,两个服务都会同时达到成功,就像处于同一个数据库事务一样。

tcc主要依靠aop拦截来实现。我们可以先看下tcc-transaction.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"> <!-- 开启Spring对@AspectJ风格切面的支持(因为下面用到自定义的TCC补偿切面类) -->
<!-- @Aspect注解不能被Spring自动识别并注册为Bean,因此要通过xml的bean配置,或通过@Compenent注解标识其为Spring管理Bean -->
<aop:aspectj-autoproxy/> <context:component-scan base-package="org.mengyun.tcctransaction.spring"/> <bean id="springBeanFactory" class="org.mengyun.tcctransaction.spring.support.SpringBeanFactory"/> <!-- TCC事务配置器 -->
<bean id="tccTransactionConfigurator" class="org.mengyun.tcctransaction.spring.support.TccTransactionConfigurator">
</bean> <!-- 事务恢复 -->
<bean id="transactionRecovery" class="org.mengyun.tcctransaction.recover.TransactionRecovery">
<property name="transactionConfigurator" ref="tccTransactionConfigurator"/>
</bean> <!-- 可补偿事务拦截器 -->
<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> <!-- TCC补偿切面 -->
<bean id="tccCompensableAspect" class="org.mengyun.tcctransaction.spring.TccCompensableAspect">
<property name="compensableTransactionInterceptor" ref="compensableTransactionInterceptor"/>
</bean> <!-- TCC事务上下文切面 -->
<bean id="transactionContextAspect" class="org.mengyun.tcctransaction.spring.TccTransactionContextAspect">
<property name="resourceCoordinatorInterceptor" ref="resourceCoordinatorInterceptor"/>
</bean> <!-- 启用定时任务注解 -->
<task:annotation-driven/> <!-- 事务恢复任务调度器 -->
<bean id="recoverScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"/> <!-- 事务恢复调度任务,初始化方法:init -->
<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> </beans>

TccTransactionConfigurator配置了tcc的数据库连接和事务管理

public class TccTransactionConfigurator implements TransactionConfigurator {

    /**
* 事务库
*/
@Autowired
private TransactionRepository transactionRepository; /**
* 事务恢复配置
*/
@Autowired(required = false)
private RecoverConfig recoverConfig = DefaultRecoverConfig.INSTANCE; .....
}

TransactionRepository就是我们在TccConfig中配置的

@Bean
public TransactionRepository transactionRepository(ObjectSerializer<?> serializer) {
SpringJdbcTransactionRepository repository = new SpringJdbcTransactionRepository();
repository.setDataSource(druidDataSource());
repository.setDomain("ACCOUNT");
repository.setTbSuffix("_ACCOUNT");
repository.setSerializer(serializer);
return repository;
}

接下来就是两个拦截器和切面

两个拦截器的切点如下

TccCompensableAspect

@Pointcut("@annotation(org.mengyun.tcctransaction.Compensable)")
TccTransactionContextAspect

@Pointcut("execution(public * *(org.mengyun.tcctransaction.api.TransactionContext,..))||@annotation(org.mengyun.tcctransaction.Compensable)")

两个拦截器分别拦截  Compensable 注解和方法参数第一位是是TransactionContext 的方法。当我们调用以下方法时,拦截就会开始

@Compensable(confirmMethod = "confirmPay",cancelMethod = "canselPay")
public boolean pay(Long tradeId,String userId) {
......
return true;
}

首先进入的切面是 org.mengyun.tcctransaction.interceptor.CompensableTransactionInterceptor#interceptCompensableMethod

public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {

        // 从拦截方法的参数中获取事务上下文
TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs()); // 计算可补偿事务方法类型
MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, true); logger.debug("==>interceptCompensableMethod methodType:" + methodType.toString()); switch (methodType) {
case ROOT:
// 主事务方法的处理
return rootMethodProceed(pjp);
case PROVIDER:
// 服务提供者事务方法处理
return providerMethodProceed(pjp, transactionContext);
default:
return pjp.proceed(); // 其他的方法都是直接执行
}
}

切面首先会获取到TransactionContext,但是在服务的发起方TransactionContext是null,在下一步的计算方法类型的步骤中,切面通过是否含有注解和TransactionContext来计算类型。

private Object rootMethodProceed(ProceedingJoinPoint pjp) throws Throwable {
transactionConfigurator.getTransactionManager().begin(); // 事务开始(创建事务日志记录,并在当前线程缓存该事务日志记录)
Object returnValue = null; // 返回值
try {
logger.debug("==>rootMethodProceed try begin");
returnValue = pjp.proceed(); // Try (开始执行被拦截的方法)
logger.debug("==>rootMethodProceed try end");
} catch (OptimisticLockException e) {
logger.warn("==>compensable transaction trying exception.", e);
throw e; //do not rollback, waiting for recovery job
} catch (Throwable tryingException) {
logger.warn("compensable transaction trying failed.", tryingException);
transactionConfigurator.getTransactionManager().rollback();
throw tryingException;
}
logger.info("===>rootMethodProceed begin commit()");
transactionConfigurator.getTransactionManager().commit(); // Try检验正常后提交(事务管理器在控制提交)
return returnValue;
}

在rootMethodProceed中,首先新建一个全局事务,保存到数据库

public void begin() {
LOG.debug("==>begin()");
Transaction transaction = new Transaction(TransactionType.ROOT); // 事务类型为ROOT:1
LOG.debug("==>TransactionType:" + transaction.getTransactionType().toString() + ", Transaction Status:" + transaction.getStatus().toString());
TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
transactionRepository.create(transaction); // 创建事务记录,写入事务日志库
threadLocalTransaction.set(transaction); // 将该事务日志记录存入当前线程的事务局部变量中
}

接着会执行pjp.proceed(); 还记得上面讲的两个切面么,这个时候会被第二个切面了拦截

org.mengyun.tcctransaction.interceptor.ResourceCoordinatorInterceptor#interceptTransactionContextMethod

public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable {
LOG.debug("==>interceptTransactionContextMethod(ProceedingJoinPoint pjp)");
// 获取当前事务
Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
// Trying(判断是否Try阶段的事务)
if (transaction != null && transaction.getStatus().equals(TransactionStatus.TRYING)) {
LOG.debug("==>TransactionStatus:" + transaction.getStatus().toString());
// 从参数获取事务上下文
TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs());
// 获取事务补偿注解
Compensable compensable = getCompensable(pjp);
// 计算方法类型
MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, compensable != null ? true : false);
LOG.debug("==>methodType:" + methodType.toString());
switch (methodType) {
case ROOT:
generateAndEnlistRootParticipant(pjp); // 生成和登记根参与者
break;
case CONSUMER:
generateAndEnlistConsumerParticipant(pjp); // 生成并登记消费者的参与者
break;
case PROVIDER:
generateAndEnlistProviderParticipant(pjp); // 生成并登记服务提供者的参与者
break;
}
}
LOG.debug("==>pjp.proceed(pjp.getArgs())");
return pjp.proceed(pjp.getArgs());
}

这里计算出来的类型是root,我们直接看 generateAndEnlistRootParticipant

private Participant generateAndEnlistRootParticipant(ProceedingJoinPoint pjp) {
LOG.debug("==>generateAndEnlistRootParticipant(ProceedingJoinPoint pjp)");
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Compensable compensable = getCompensable(pjp);
String confirmMethodName = compensable.confirmMethod(); // 确认方法
String cancelMethodName = compensable.cancelMethod(); // 取消方法
// 获取当前事务
Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction();
// 获取事务Xid
TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId());
LOG.debug("==>TransactionXid:" + TransactionXid.byteArrayToUUID(xid.getGlobalTransactionId()).toString()
+ "|" + TransactionXid.byteArrayToUUID(xid.getBranchQualifier()).toString()); Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes()); // 构建确认方法的提交上下文
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,
new Terminator(confirmInvocation, cancelInvocation));
// 加入参与者
transaction.enlistParticipant(participant);
TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
// 更新事务
transactionRepository.update(transaction);
return participant;
}

generateAndEnlistRootParticipant主要是构建Compensable注解上的两个参数,confirmMethod 和 cancelMethod,并构建两个方法的上下文,序列化保存到数据库中。

现在终于可以执行我们真正的逻辑了。当我们执行到调用远程服务时。因为该方法的第一个参数是TransactionContext。所以又要被第二个切面拦截了。

Result result = accountRouteService.addPoint(null,trade.getPrice().intValue(), userId);
@PostMapping(value = "tccaccount/account/addPoint")
Result addPoint(@RequestBody TransactionContext context, @RequestParam(value = "point") Integer point,
@RequestParam(value = "userId") String userId);
public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) throws Throwable {
LOG.debug("==>interceptTransactionContextMethod(ProceedingJoinPoint pjp)");
// 获取当前事务
Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction(); // Trying(判断是否Try阶段的事务)
if (transaction != null && transaction.getStatus().equals(TransactionStatus.TRYING)) {
LOG.debug("==>TransactionStatus:" + transaction.getStatus().toString());
// 从参数获取事务上下文
TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs());
// 获取事务补偿注解
Compensable compensable = getCompensable(pjp);
// 计算方法类型
MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, compensable != null ? true : false);
LOG.debug("==>methodType:" + methodType.toString()); switch (methodType) {
case ROOT:
generateAndEnlistRootParticipant(pjp); // 生成和登记根参与者
break;
case CONSUMER:
generateAndEnlistConsumerParticipant(pjp); // 生成并登记消费者的参与者
break;
case PROVIDER:
generateAndEnlistProviderParticipant(pjp); // 生成并登记服务提供者的参与者
break;
}
}

这里methodType计算出来是CONSUMER

private Participant generateAndEnlistConsumerParticipant(ProceedingJoinPoint pjp) {
LOG.debug("==>generateAndEnlistConsumerParticipant(ProceedingJoinPoint pjp)");
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Transaction transaction = transactionConfigurator.getTransactionManager().getCurrentTransaction(); // 获取当前事务
TransactionXid xid = new TransactionXid(transaction.getXid().getGlobalTransactionId()); // 获取事务Xid
LOG.debug("==>TransactionXid:" + TransactionXid.byteArrayToUUID(xid.getGlobalTransactionId()).toString()
+ "|" + TransactionXid.byteArrayToUUID(xid.getBranchQualifier()).toString()); // 获取事务上下文参数的位置
int position = CompensableMethodUtils.getTransactionContextParamPosition(((MethodSignature) pjp.getSignature()).getParameterTypes()); // 给服务接口的TransactionContext参数设值
pjp.getArgs()[position] = new TransactionContext(xid, transaction.getStatus().getId()); // 构建事务上下文 Object[] tryArgs = pjp.getArgs(); // 获取服务接口参数
Object[] confirmArgs = new Object[tryArgs.length]; // 确认提交参数
Object[] cancelArgs = new Object[tryArgs.length]; // 取消提交参数 System.arraycopy(tryArgs, 0, confirmArgs, 0, tryArgs.length); // 数组拷贝
confirmArgs[position] = new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()); // 构建事务确认上下文
System.arraycopy(tryArgs, 0, cancelArgs, 0, tryArgs.length); // 数组拷贝
cancelArgs[position] = new TransactionContext(xid, TransactionStatus.CANCELLING.getId()); // 构建事务取消上下文
Class targetClass = ReflectionUtils.getDeclaringType(pjp.getTarget().getClass(), method.getName(), method.getParameterTypes()); // 构建确认方法的提交上下文
InvocationContext confirmInvocation = new InvocationContext(targetClass, method.getName(), method.getParameterTypes(), confirmArgs);
// 构建取消方法的提交上下文
InvocationContext cancelInvocation = new InvocationContext(targetClass, method.getName(), method.getParameterTypes(), cancelArgs); // 构建参与者对像
Participant participant =
new Participant(
xid,
new Terminator(confirmInvocation, cancelInvocation)); transaction.enlistParticipant(participant); // 加入到参与者
TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
transactionRepository.update(transaction); // 更新事务 return participant;
}

这个generateAndEnlistConsumerParticipant 方法干了什么呢。上文我们调用远程服务时,第一个入参的TransactionContext是null,所以这里我们我们构建了一个 TransactionContext 。这里的全局事务id和之前构造的TransactionContext 都是一致的,然后我们再构建 确认与取消方法保存到数据库中。现在我们在trade的切面拦截终于结束了。调用远程方法到account服务时,我们又遇到了新的一次拦截

@Compensable(confirmMethod = "confirmAddPoint",cancelMethod = "cancleAddPoint")
public boolean addPoint(TransactionContext transactionContext, Integer point, String userId) {
Account account = accountService.
selectOne(new EntityWrapper<Account>().eq("user_id", userId));
CheckUtil.notNull(account,"account not null");
return true;
}

这次的拦截和在trade遇到的相似,但是这次算出来的是 PROVIDER 类型

public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {

        // 从拦截方法的参数中获取事务上下文
TransactionContext transactionContext = CompensableMethodUtils.getTransactionContextFromArgs(pjp.getArgs()); // 计算可补偿事务方法类型
MethodType methodType = CompensableMethodUtils.calculateMethodType(transactionContext, true); logger.debug("==>interceptCompensableMethod methodType:" + methodType.toString()); switch (methodType) {
case ROOT:
return rootMethodProceed(pjp); // 主事务方法的处理
case PROVIDER:
return providerMethodProceed(pjp, transactionContext); // 服务提供者事务方法处理
default:
return pjp.proceed(); // 其他的方法都是直接执行
}
}

现在我们是trying阶段,所以现在第一个切面和第二个切面只需要在本地数据库保存下事务的二进制流,校验try阶段的逻辑即可。

private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext) throws Throwable {
logger.debug("==>providerMethodProceed transactionStatus:" + TransactionStatus.valueOf(transactionContext.getStatus()).toString());
switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
case TRYING:
logger.debug("==>providerMethodProceed try begin");
// 基于全局事务ID扩展创建新的分支事务,并存于当前线程的事务局部变量中.
transactionConfigurator.getTransactionManager().propagationNewBegin(transactionContext);
logger.debug("==>providerMethodProceed try end");
return pjp.proceed();
case CONFIRMING:
try {
logger.debug("==>providerMethodProceed confirm begin");
// 找出存在的事务并处理.
transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
transactionConfigurator.getTransactionManager().commit(); // 提交
logger.debug("==>providerMethodProceed confirm end");
} catch (NoExistedTransactionException excepton) {
//the transaction has been commit,ignore it.
}
break;
case CANCELLING:
try {
logger.debug("==>providerMethodProceed cancel begin");
transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
transactionConfigurator.getTransactionManager().rollback(); // 回滚
logger.debug("==>providerMethodProceed cancel end");
} catch (NoExistedTransactionException exception) {
//the transaction has been rollback,ignore it.
}
break;
}
Method method = ((MethodSignature) (pjp.getSignature())).getMethod();
return ReflectionUtils.getNullValue(method.getReturnType());
}

当account服务执行成功之后,我们回到trade服务  现在returnValue = pjp.proceed(); 这一行终于执行完了。也就是说各个事务的准备阶段都完成了。只有两种情况,成功和非成功(失败和超时)。我们乐观点,看看成功后是怎么提交全部事物的。

private Object rootMethodProceed(ProceedingJoinPoint pjp) throws Throwable {
logger.debug("==>rootMethodProceed"); transactionConfigurator.getTransactionManager().begin(); // 事务开始(创建事务日志记录,并在当前线程缓存该事务日志记录) Object returnValue = null; // 返回值
try { logger.debug("==>rootMethodProceed try begin");
returnValue = pjp.proceed(); // Try (开始执行被拦截的方法)
logger.debug("==>rootMethodProceed try end"); } catch (OptimisticLockException e) {
logger.warn("==>compensable transaction trying exception.", e);
throw e; //do not rollback, waiting for recovery job
} catch (Throwable tryingException) {
logger.warn("compensable transaction trying failed.", tryingException);
transactionConfigurator.getTransactionManager().rollback();
throw tryingException;
} logger.info("===>rootMethodProceed begin commit()");
transactionConfigurator.getTransactionManager().commit(); // Try检验正常后提交(事务管理器在控制提交) return returnValue;
}

事务提交总共分为两部分,

1.更改状态保存到数据库。做持久化保存,这样宕机了也能恢复。

2.反射调用切面拦截保存的参与者

public void commit() {
LOG.debug("==>TransactionManager commit()");
Transaction transaction = getCurrentTransaction(); transaction.changeStatus(TransactionStatus.CONFIRMING);
LOG.debug("==>update transaction status to CONFIRMING");
//更改状态为CONFIRMING,数据库持久化保存
transactionConfigurator.getTransactionRepository().update(transaction); try {
LOG.info("==>transaction begin commit()");
//事务提交
transaction.commit();
transactionConfigurator.getTransactionRepository().delete(transaction);
} catch (Throwable commitException) {
LOG.error("compensable transaction confirm failed.", commitException);
throw new ConfirmingException(commitException);
}
} public void commit() {
LOG.debug("==>Transaction.commit()");
for (Participant participant : participants) {
participant.commit();
}
} public void commit() {
LOG.debug("==>Participant.rollback()");
terminator.commit();
} public void commit() {
LOG.debug("==>Terminator commit invoke");
invoke(confirmInvocationContext);
} private Object invoke(InvocationContext invocationContext) { if (StringUtils.isNotEmpty(invocationContext.getMethodName())) { LOG.debug("==>Terminator invoke " + invocationContext.getTargetClass().getName() + "." + invocationContext.getMethodName()); try {
Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance(); // 找到要调用的目标方法
Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes()); // 调用服务方法,被再次被TccTransactionContextAspect和ResourceCoordinatorInterceptor拦截,但因为事务状态已经不再是TRYING了,所以直接执行远程服务
return method.invoke(target, invocationContext.getArgs()); // 调用服务方法 } catch (Exception e) {
throw new SystemException(e);
}
}
return null;
}

本地的反射调用时很好实现的,但是如何远程反射调用其他服务的confirm方法呢。

tcc-transaction根据transactionContext的状态字段,通过切面拦截 当transactionContext变成CONFIRMING时,就会反射调用confirmInvocationContext

case TRYING:
logger.debug("==>providerMethodProceed try begin");
// 基于全局事务ID扩展创建新的分支事务,并存于当前线程的事务局部变量中.
transactionConfigurator.getTransactionManager().propagationNewBegin(transactionContext);
logger.debug("==>providerMethodProceed try end");
return pjp.proceed();
case CONFIRMING:
try {
logger.debug("==>providerMethodProceed confirm begin");
// 找出存在的事务并处理.
transactionConfigurator.getTransactionManager().propagationExistBegin(transactionContext);
transactionConfigurator.getTransactionManager().commit();
// 提交
logger.debug("==>providerMethodProceed confirm end");
} catch (NoExistedTransactionException excepton) {
//the transaction has been commit,ignore it.
}
break;

到这里,一个tcc分布式理论上是已经完成了。但是,我们考虑下一种场景,如果在confirm阶段出现异常怎么办呢?

tcc-transaction还提供了恢复功能。他能从数据库找到还未完成的事物,间隔的去执行,我们也可以配置相关的策略

/**
* 设置恢复策略
* @return
*/
@Bean
public DefaultRecoverConfig defaultRecoverConfig(){
DefaultRecoverConfig defaultRecoverConfig=new DefaultRecoverConfig();
defaultRecoverConfig.setCronExpression("0 */1 * * * ?");
defaultRecoverConfig.setMaxRetryCount(120);
return defaultRecoverConfig;
}

以上就是tcc-transaction的分析

tcc-transaction 分析的更多相关文章

  1. TCC细读 - 1 例子流程

    http://www.iocoder.cn/categories/TCC-Transaction/ https://github.com/changmingxie/tcc-transaction 细读 ...

  2. LoadRunner 压测场景制定以及报告分析

    这里,我们利用 LoadRunner 来制定场景,且以测试 tps 值为导向,主要介绍手工场景 单服务器的业务请求处理能力 tps 值在 10~200 是合理的:如果是访问单接口不走关系型数据库的,访 ...

  3. 分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363 一.背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了 ...

  4. Lock wait timeout分析

    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction分析 1.4个用户连接数据库(A和D是本地回环登陆, ...

  5. MySQL数据库死锁分析

    背景说明: 公司内部一套自建分布式交易服务平台,在POC稳定性压力测试的时候出现了数据库死锁.(InnoDB引擎)由于保密性,假设是app_test表死锁了. 现象: 发生异常:Deadlock fo ...

  6. 【转】分布式事务之——tcc-transaction分布式TCC型事务框架搭建与实战案例

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/73731363 一.背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了 ...

  7. MySQL5.6 新特性之GTID

    背景: MySQL5.6在5.5的基础上增加了一些改进,本文章先对其中一个一个比较大的改进"GTID"进行说明. 概念: GTID即全局事务ID(global transactio ...

  8. 使用Connetion的属性RetainSameConnection

    在SSIS的组件中,很多都会连接到数据库进行操作,Connection有一个属性RetainSameConnection,默认值是False,控制着连接的打开和关闭的时机. 1,如果Connectio ...

  9. 解读Web Page Diagnostics网页细分图

    解读Web Page Diagnostics网页细分图 http://blog.sina.com.cn/s/blog_62b8fc330100red5.html Web Page Diagnostic ...

  10. MySQL 行锁 表锁机制

    MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑.遇到了可别乱踩.通过本章内容,带你学习MySQL的行锁 ...

随机推荐

  1. Linux服务器安装Oracle服务端总结

    摘要: 通过ssh远程连接linux服务器,上传Oracle11g的安装包,在无图形化界面的情况,安装Oracle服务器端.本例中linux服务器系统为CentOS6.5. #环境设置 1.检查服务器 ...

  2. BZOJ_1717_[Usaco2006 Dec]Milk Patterns 产奶的模式_后缀数组

    BZOJ_1717_[Usaco2006 Dec]Milk Patterns 产奶的模式_后缀数组 Description 农夫John发现他的奶牛产奶的质量一直在变动.经过细致的调查,他发现:虽然他 ...

  3. BZOJ_3555_[Ctsc2014]企鹅QQ_哈希

    BZOJ_3555_[Ctsc2014]企鹅QQ_哈希 Description PenguinQQ是中国最大.最具影响力的SNS(Social Networking Services)网站,以实名制为 ...

  4. 哎呀,我老大写Bug啦——记一次MessageQueue的优化

    MessageQueue,顾名思义消息队列,在系统开发中也是用的比较多的一个中间件吧.我们这里主要用它来做日志管理和订单管理的,记得老老大(恩,是的,就是老老大,因为他已经跳槽了)还在的时候,当时也是 ...

  5. python --- 快速排序算法

    在快速排序中引入递归和分治的概念(关于递归和分治的概念会单独写一篇来进行介绍) 问的解决思路: 快速排序的基本思想本身就是分治法,通过分割,将无序序列分成两部分,其中前一部分的元素值都要小于后一部分的 ...

  6. 从mysql中拿到的数据构造为列表

    最近测试接口遇到一个问题,用python2.7从mysql中取到的数据是元祖类型的,元祖内部的元素也是一个元祖(并且部分元素的编码格式是unicode的): 类似这样: ((10144, u''), ...

  7. 介绍几款 Python 类型检查工具

    近日,微软在 Github 上开源了一个 Python 静态类型检查工具:pyright ,引起了社区内的多方关注. 微软在开源项目上的参与力度是越来越大了,不说收购 Github 这种大的战略野心, ...

  8. HTML5和CSS3的新特性

    html5的新特性 添加了用于媒介回放的 <video>,<audio> 元素 添加了语义标签譬如 header.footer.nav 等等元素 添加了用于绘画的 canvas ...

  9. html的<h>标签

    <h>标签:标题标签. <h>标签只有六个:<h1>........<h6>

  10. WordPress怎样设置菜单栏旋转小图标

    最近我在浏览别的博客的文章时,无意间发现了一个很好看的小装饰.那就是在WordPress菜单栏上的小图标.于是我研究了研究,弄到了设置方法之后决定把它分享出来. 菜单栏的小图标 设置步骤: 1, 我们 ...