开源源码地址:https://github.com/changmingxie/tcc-transaction.git
项目demo地址:https://github.com/liuchaoOvO/tcc-transaction-demo.git
源码修改成springboot 源码地址:https://github.com/liuchaoOvO/tcc-transaction.git
主要涉及如下三个 Maven 项目:
-
tcc-transaction-core
:tcc-transaction 底层实现。 -
tcc-transaction-api
:tcc-transaction 使用 API。 -
tcc-transaction-spring
:tcc-transaction Spring 支持。
目录
TCC事务简介
为了解决在事务运行过程中,大颗粒度资源锁定的问题,提出一种新的事务模型,它是基于业务层面的事务定义。锁粒度完全由业务自己控制。它本质是一种补偿的思路。它把事务运行过程分成 Try、Confirm / Cancel 两个阶段。在每个阶段的逻辑由业务代码控制。这样就事务的锁粒度可以完全自由控制。业务可以在牺牲隔离性的情况下,获取更高的性能。本质上,TCC 通过多个参与者的 try / confirm / cancel 方法,实现事务的最终一致性。
架构模型图
-
Try 阶段
-
完成所有业务检查( 一致性 )
-
预留必须业务资源( 准隔离性 )
-
Try :尝试执行业务
-
-
Confirm / Cancel 阶段:
-
释放 Try 阶段预留的业务资源
-
Cancel 操作满足幂等性(先getXXXID()得到对应Try 尝试保存的数据,再补偿取消)
-
真正执行业务
-
不做任务业务检查
-
Confirm 操作满足幂等性
-
Confirm :确认执行业务
-
Cancel :取消执行业务
-
Confirm 与 Cancel 互斥
-
与 2PC协议 比较
-
没有单独的准备( Prepare )阶段,Try 操作兼备自愿操作与准备能力
-
Try 操作可以灵活选择业务资源的锁定粒度
-
较高开发成本
TCC柔性补偿性事务:尝试/ 确定/ 取消 三个接口都要我们自己去实现
在 TCC 里,一个业务活动可以有多个事务,每个业务操作归属于不同的事务,即一个事务可以包含多个业务操作。TCC-Transaction 将每个业务操作抽象成事务参与者,每个事务可以包含多个参与者。
TCC-Transaction 有两个拦截器,通过对 @Compensable AOP 切面( 参与者 try 方法 )进行拦截,透明化对参与者 confirm / cancel 方法调用,从而实现 TCC 。
两种拦截器:
可补偿事务拦截器:CompensableTransactionInterceptor
资源协调者拦截器:ResourceCoordinatorInterceptor
第一个拦截器,可补偿事务拦截器,实现如下功能:
-
在 Try 阶段,对事务的发起、传播。
-
在 Confirm / Cancel 阶段,对事务提交或回滚。
-
为什么会有对事务的传播呢? 在远程调用服务的参与者时,会通过"参数"( 需要序列化 )的形式传递事务给远程参与者。
第二个拦截器,资源协调者拦截器,实现如下功能:
-
在 Try 阶段,添加参与者到事务中。当事务上下文不存在时,进行创建。
源码
1、参与者对象 Participant :
public class Participant implements Serializable {
/**
* 事务编号
*/
private TransactionXid xid;
/**
* 确认执行业务方法调用上下文
*/
private InvocationContext confirmInvocationContext;
/**
* 取消执行业务方法
*/
private InvocationContext cancelInvocationContext;
/**
* 执行器
*/
private Terminator terminator = new Terminator();
/**
* 事务上下文编辑
*/
Class<? extends TransactionContextEditor> transactionContextEditorClass;
/**
* 提交事务
*/
public void commit() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CONFIRMING.getId()), confirmInvocationContext, transactionContextEditorClass);
}
/**
* 回滚事务
*/
public void rollback() {
terminator.invoke(new TransactionContext(xid, TransactionStatus.CANCELLING.getId()), cancelInvocationContext, transactionContextEditorClass);
}
...
}
2、InvocationContext。
执行方法调用上下文,记录类、方法名、参数类型数组、参数数组。通过这些属性,可以执行提交 / 回滚事务。在 org.mengyun.tcctransaction.Terminator
会看到具体的代码实现。
3、Terminator 执行器
public class Terminator implements Serializable {
private static final long serialVersionUID = -164958655471605778L;
public Object invoke(TransactionContext transactionContext, InvocationContext invocationContext, Class<? extends TransactionContextEditor> transactionContextEditorClass) {
if (StringUtils.isNotEmpty(invocationContext.getMethodName())) {
try {
// 获得 参与者对象
Object target = FactoryBuilder.factoryOf(invocationContext.getTargetClass()).getInstance();
// 获得 方法
Method method = target.getClass().getMethod(invocationContext.getMethodName(), invocationContext.getParameterTypes());
// 设置 事务上下文 到方法参数
FactoryBuilder.factoryOf(transactionContextEditorClass).getInstance().set(transactionContext, target, method, invocationContext.getArgs());
// 执行方法
return method.invoke(target, invocationContext.getArgs());
} catch (Exception e) {
throw new SystemException(e);
}
}
return null;
}
4、事务管理器 TransactionManager
事务管理器不关心接口是怎么实现的,只会根据情况去调用。1、启动事务。2、调用尝试的接口,3、提交或者失败调用取消接口。
TransactionManager
发起根事务
transactionManager 提供 begin()
方法,发起根事务。该方法在调用方法类型为 MethodType.ROOT 并且 事务处于 Try 阶段被调用。
TCC-Transaction 支持多个的事务独立存在,后创建的事务先提交(原因:ThreadLocal<Deque<Transaction>>),类似 Spring 的org.springframework.transaction.annotation.Propagation.REQUIRES_NEW
传播发起分支事务
transactionManager 调用 #propagationNewBegin(...)
方法,传播发起分支事务。该方法在调用方法类型为 MethodType.PROVIDER 并且 事务处于 Try 阶段被调用。
传播获取分支事务
transactionManager 调用 #propagationExistBegin(...)
方法,传播发起分支事务。该方法在调用方法类型为 MethodType.PROVIDER 并且 事务处于 Confirm / Cancel 阶段被调用。
提交事务
transactionManager 调用 #commit(...)
方法,提交事务。该方法在事务处于 Confirm / Cancel 阶段被调用。
回滚事务
transactionManager 调用 #rollback(...)
方法,取消事务,和 #commit()
方法基本类似。该方法在事务处于 Confirm / Cancel 阶段被调用。
添加参与者到事务
调用 #enlistParticipant(...)
方法,添加参与者到事务。该方法在事务处于 Try 阶段被调用。
5、事务拦截器方法
package org.mengyun.tcctransaction.interceptor;
public class CompensableTransactionInterceptor {
private TransactionManager transactionManager;
private Set<Class<? extends Exception>> delayCancelExceptions;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setDelayCancelExceptions(Set<Class<? extends Exception>> delayCancelExceptions) {
this.delayCancelExceptions = delayCancelExceptions;
}
//事务拦截器通过AOP环绕到加了注解@Compensable的方法,环绕执行下面的方法
public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
//获取到加了注解的方法本身
Method method = CompensableMethodUtils.getCompensableMethod(pjp);
//获取到注解@Compensable自己本身 --> 获取注解的属性:取消的方法,确认的方法
Compensable compensable = method.getAnnotation(Compensable.class);
Propagation propagation = compensable.propagation();
//获取事务上下文,RPC隐式参数中包含这个上下文
TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs());
boolean asyncConfirm = compensable.asyncConfirm();
boolean asyncCancel = compensable.asyncCancel();
//判断是否有存在的事务队列
boolean isTransactionActive = transactionManager.isTransactionActive();
if (!TransactionUtils.isLegalTransactionContext(isTransactionActive, propagation, transactionContext)) {
throw new SystemException("no active compensable transaction while propagation is mandatory for method " + method.getName());
}
MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);
/*
*这个方法判断:(!isTransactionActive && transactionContext == null)是否已经有事务
了,事务上下文是否为空
都满足说明式主事务,否则是分支事务
*/
switch (methodType) {
case ROOT:
return rootMethodProceed(pjp, asyncConfirm, asyncCancel);
case PROVIDER:
return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel);
default:
return pjp.proceed();
}
}
/**这里是主事务的操作
开启事务
注册,持久化事务
然后判断是cancel,confirm
清除事务
*/
private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable {
Object returnValue = null;
Transaction transaction = null;
try {
事务为空那么就开启一个全新的事务
transaction = transactionManager.begin();
try {
//继续后续操作
returnValue = pjp.proceed();
} catch (Throwable tryingException) {
if (!isDelayCancelException(tryingException)) {
logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);
transactionManager.rollback(asyncCancel);
}
throw tryingException;
}
transactionManager.commit(asyncConfirm);
} finally {
transactionManager.cleanAfterCompletion(transaction);
}
return returnValue;
}
private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext, boolean asyncConfirm, boolean asyncCancel) throws Throwable {
Transaction transaction = null;
try {
switch (TransactionStatus.valueOf(transactionContext.getStatus())) {
case TRYING:
transaction = transactionManager.propagationNewBegin(transactionContext);
return pjp.proceed();
case CONFIRMING:
try {
transaction = transactionManager.propagationExistBegin(transactionContext);
transactionManager.commit(asyncConfirm);
} catch (NoExistedTransactionException excepton) {
//the transaction has been commit,ignore it.
}
break;
case CANCELLING:
try {
transaction = transactionManager.propagationExistBegin(transactionContext);
transactionManager.rollback(asyncCancel);
} catch (NoExistedTransactionException exception) {
//the transaction has been rollback,ignore it.
}
break;
}
} finally {
transactionManager.cleanAfterCompletion(transaction);
}
Method method = ((MethodSignature) (pjp.getSignature())).getMethod();
return ReflectionUtils.getNullValue(method.getReturnType());
}
private boolean isDelayCancelException(Throwable throwable) {
if (delayCancelExceptions != null) {
for (Class delayCancelException : delayCancelExceptions) {
Throwable rootCause = ExceptionUtils.getRootCause(throwable);
if (delayCancelException.isAssignableFrom(throwable.getClass())
|| (rootCause != null && delayCancelException.isAssignableFrom(rootCause.getClass()))) {
return true;
}
}
}
return false;
}
}
添加tcc-transaction的使用方法
1、需要提供分布式事务支持的接口上添加@Compensable
2、在对应的接口实现上添加@Compensable
3、在接口实现上添加confirmMethod、cancelMethod、transactionContextEditor
4、实现对应的confirmMethod、cancelMethod 注意:confirm方法和cancel方法必须与try方法在同一个类中.。(根据类的反射找到方法invoke)
注意:
1、分布式事务里,不要轻易在业务层捕获所有异常,抛出异常才能被TCC感知到,然后回调。只捕获必要捕获的异常。
2、使用TCC-Transaction时,confirm和cancel的幂等性需要自己编写代码支持