写在前面
seata-TCC模式是一种侵入式的分布式事务解决方案,每个分支事务都需要自己实现TCC的行为,支持把 “自定义” 的分支事务纳入到全局事务的管理中。
跟AT模式对比,TCC可以拥有更高的自主性与性能,但需要面对实现一个高效正确的TCC行为的带来的高复杂度,比如保证幂等性、保证一致性、防止悬挂等逻辑都需要由自己实现的TCC行为进行控制。
在代码实现中,AT模式与TCC模式大同小异,都是一样通过TM、RM、TC三种协同处理全局事务。只不过TCC模式下分支事务的注册、提交或回滚是调用自己实现的TCC行为类进行处理。
TCC-TM端分析
首先说明,TCC流程同样需要打上@GolbalTransaction注解的TM范围中完成分支注册、提交或回滚,不在细说。
TCC-RM端分析
在TCC流程中,同样是流经SeataAutoConfiguration->GlobalTransactionScanner进行处理,不同的是,TCC会额外选择
1.打上@LocalTCC、@TwoPhaseBusinessAction注解的localTCCbean。
2.打上@TwoPhaseBusinessAction注解的远程referenceTCCbean。
进行代理(为什么远程reference也要进行代理?这是我看TCC源码最大的疑惑点,猜测是下游服务不再需要打上@LocalTCC进行代理或者最上游不需要传递xid到下游进行处理,TM的分支提交或回滚回调都在最上游的调用处进行处理)。选择时会保存解析好的TCCResource和RemotingDesc。
TCCResource的保存是TCC能够执行的关键,下面先给出保存代码,后面会再次讲到TCCResource。
public RemotingDesc parserRemotingServiceInfo(Object bean, String beanName, RemotingParser remotingParser) {
RemotingDesc remotingBeanDesc = remotingParser.getServiceDesc(bean, beanName);
if (remotingBeanDesc == null) {
return null;
}
remotingServiceMap.put(beanName, remotingBeanDesc);
Class> interfaceClass = remotingBeanDesc.getInterfaceClass();
Method[] methods = interfaceClass.getMethods();
if (remotingParser.isService(bean, beanName)) {
try {
//service bean, registry resource
Object targetBean = remotingBeanDesc.getTargetBean();
for (Method m : methods) {
TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class);
if (twoPhaseBusinessAction != null) {
TCCResource tccResource = new TCCResource();
tccResource.setActionName(twoPhaseBusinessAction.name());
tccResource.setTargetBean(targetBean);
tccResource.setPrepareMethod(m);
tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());
tccResource.setCommitMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(),
new Class[] {BusinessActionContext.class}));
tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());
tccResource.setRollbackMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(),
new Class[] {BusinessActionContext.class}));
//registry tcc resource
DefaultResourceManager.get().registerResource(tccResource);
}
}
} catch (Throwable t) {
throw new FrameworkException(t, "parser remoting service error");
}
}
if (remotingParser.isReference(bean, beanName)) {
//reference bean, TCC proxy
remotingBeanDesc.setReference(true);
}
return remotingBeanDesc;
}
public static boolean isTccAutoProxy(Object bean, String beanName, ApplicationContext applicationContext) {
boolean isRemotingBean = parserRemotingServiceInfo(bean, beanName);
//get RemotingBean description
RemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
//is remoting bean
if (isRemotingBean) {
if (remotingDesc != null && remotingDesc.getProtocol() == Protocols.IN_JVM) {
//LocalTCC
return isTccProxyTargetBean(remotingDesc);
} else {
// sofa:reference / dubbo:reference, factory bean
return false;
}
} else {
if (remotingDesc == null) {
//check FactoryBean
if (isRemotingFactoryBean(bean, beanName, applicationContext)) {
remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);
return isTccProxyTargetBean(remotingDesc);
} else {
return false;
}
} else {
return isTccProxyTargetBean(remotingDesc);
}
}
}
public static boolean isTccProxyTargetBean(RemotingDesc remotingDesc) {
if (remotingDesc == null) {
return false;
}
//check if it is TCC bean
boolean isTccClazz = false;
Class> tccInterfaceClazz = remotingDesc.getInterfaceClass();
Method[] methods = tccInterfaceClazz.getMethods();
TwoPhaseBusinessAction twoPhaseBusinessAction;
for (Method method : methods) {
twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
if (twoPhaseBusinessAction != null) {
isTccClazz = true;
break;
}
}
if (!isTccClazz) {
return false;
}
short protocols = remotingDesc.getProtocol();
//LocalTCC
if (Protocols.IN_JVM == protocols) {
//in jvm TCC bean , AOP
return true;
}
// sofa:reference / dubbo:reference, AOP
return remotingDesc.isReference();
}
当需要进行TCC代理时,GlobalTransactionScanner中的wrapIfNecessary会使用TccActionInterceptor进行代理处理,接管原对象。
@Override
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
try {
synchronized (PROXYED_SET) {
if (PROXYED_SET.contains(beanName)) {
return bean;
}
interceptor = null;
//check TCC proxy
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)interceptor);
} else {
Class> serviceInterface = SpringProxyUtils.findTargetClass(bean);
Class>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
if (!existsAnnotation(new Class[]{serviceInterface})
&& !existsAnnotation(interfacesIfJdk)) {
return bean;
}
if (interceptor == null) {
if (globalTransactionalInterceptor == null) {
globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
ConfigurationCache.addConfigListener(
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)globalTransactionalInterceptor);
}
interceptor = globalTransactionalInterceptor;
}
}
LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
if (!AopUtils.isAopProxy(bean)) {
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
for (Advisor avr : advisor) {
advised.addAdvisor(0, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
} catch (Exception exx) {
throw new RuntimeException(exx);
}
}
来到TccActionInterceptor,调用原有对象会经过TccActionInterceptor的invoke方法,判断调用的原方法是否打上了@TwoPhaseBusinessAction注解,没打上直接执行invocation.proceed()原方法,否则取出xid与绑定TCC模式到上下文,取出方法与入参、原方法执行回调、@TwoPhaseBusinessAction注解参数,调用ActionInterceptorHandler的proceed方法进行处理,处理完成后需要清除上下文。
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
if (!RootContext.inGlobalTransaction() || disable || RootContext.inSagaBranch()) {
//not in transaction
return invocation.proceed();
}
Method method = getActionInterfaceMethod(invocation);
TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
//try method
if (businessAction != null) {
//save the xid
String xid = RootContext.getXID();
//save the previous branchType
BranchType previousBranchType = RootContext.getBranchType();
//if not TCC, bind TCC branchType
if (BranchType.TCC != previousBranchType) {
RootContext.bindBranchType(BranchType.TCC);
}
try {
Object[] methodArgs = invocation.getArguments();
//Handler the TCC Aspect
Map ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
invocation::proceed);
//return the final result
return ret.get(Constants.TCC_METHOD_RESULT);
}
finally {
//if not TCC, unbind branchType
if (BranchType.TCC != previousBranchType) {
RootContext.unbindBranchType();
}
}
}
return invocation.proceed();
}
进入actionInterceptorHandler.proceed,核心方法只有调用doTccActionLogStore获取到batchId设置到上下文中,然后将上下文放置在入参中,执行原方法后返回。
public Map proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
Callback
进入doTccActionLogStore,会进行以下步骤:
1.调用fetchActionRequestContext,将打上@BusinessActionContextParameter的方法入参放到上下文中。
2.调用initBusinessContext,将@TwoPhaseBusinessAction的参数(pepare、commit、rolback、action方法名)放到上下文中。
3.调用initFrameworkContext,将本地ip放到上下文中。
4.调用branchRegister,向TC进行分支注册。
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
BusinessActionContext actionContext) {
String actionName = actionContext.getActionName();
String xid = actionContext.getXid();
//
Map context = fetchActionRequestContext(method, arguments);
context.put(Constants.ACTION_START_TIME, System.currentTimeMillis());
//init business context
initBusinessContext(context, method, businessAction);
//Init running environment context
initFrameworkContext(context);
actionContext.setActionContext(context);
//init applicationData
Map applicationContext = new HashMap<>(4);
applicationContext.put(Constants.TCC_ACTION_CONTEXT, context);
String applicationContextStr = JSON.toJSONString(applicationContext);
try {
//registry branch record
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.TCC, actionName, null, xid,
applicationContextStr, null);
return String.valueOf(branchId);
} catch (Throwable t) {
String msg = String.format("TCC branch Register error, xid: %s", xid);
LOGGER.error(msg, t);
throw new FrameworkException(t, msg);
}
}
向TC注册分支的时候,会夹带上面提到的TCC上下文与actionName一起到TC,同样是以RMclient进行交互,不再细说。
TCC与AT模式最主要的区别是分支事务的提交与回滚回调有截然不同的处理。
分支事务的提交与回滚回调会流经TCCResourceManager处理,首先看下分支事务的提交,调用了branchCommit方法,会取出actionName对应的TCCResource,获得目标bean与对应的commit方法,将从回调带来的上下文信息与xid、batchId重新组成businessActionContext上下文,使用反射对commit方法进行调用,调用后会返回是否成功的状态,来决定TC是否重试分支提交。
@Override
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
Method commitMethod = tccResource.getCommitMethod();
if (targetTCCBean == null || commitMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = commitMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource commit result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Committed : BranchStatus.PhaseTwo_CommitFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_CommitFailed_Retryable;
}
}
同样的,分支回调会走到branchRollback方法,也是根据actionName获取TCCResource,获得目标bean与对应的rollback方法,将从回调带来的上下文信息与xid、batchId重新组成businessActionContext上下文,使用反射对rollback方法进行调用,调用后会返回是否成功的状态,来决定TC是否重试分支回滚。
@Override
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
TCCResource tccResource = (TCCResource)tccResourceCache.get(resourceId);
if (tccResource == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not exist, resourceId: %s", resourceId));
}
Object targetTCCBean = tccResource.getTargetBean();
Method rollbackMethod = tccResource.getRollbackMethod();
if (targetTCCBean == null || rollbackMethod == null) {
throw new ShouldNeverHappenException(String.format("TCC resource is not available, resourceId: %s", resourceId));
}
try {
//BusinessActionContext
BusinessActionContext businessActionContext = getBusinessActionContext(xid, branchId, resourceId,
applicationData);
Object ret = rollbackMethod.invoke(targetTCCBean, businessActionContext);
LOGGER.info("TCC resource rollback result : {}, xid: {}, branchId: {}, resourceId: {}", ret, xid, branchId, resourceId);
boolean result;
if (ret != null) {
if (ret instanceof TwoPhaseResult) {
result = ((TwoPhaseResult)ret).isSuccess();
} else {
result = (boolean)ret;
}
} else {
result = true;
}
return result ? BranchStatus.PhaseTwo_Rollbacked : BranchStatus.PhaseTwo_RollbackFailed_Retryable;
} catch (Throwable t) {
String msg = String.format("rollback TCC resource error, resourceId: %s, xid: %s.", resourceId, xid);
LOGGER.error(msg, t);
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
TCC-TC端分析
同样的,RM端分支注册会创建BatchSession保存在数据库,TM执行全局事务的提交或回滚时也会进行回调与补偿处理,TC端的处理不再细说。