seata与多租户问题分析
多租户模式为一个租户一个DataSource 隔离性比较好. 同时对业务拆分了微服务后就带来了分布式事务问题. 可以使用seata来解决这类问题.测试过程中对AT模式没有什么问题. 但是在TCC模式下commit/cache会丢失数据源的信息所以要在执行之前要切换当前租户.
解决
- 重写 GlobalTransactionScanner
@Bean
@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
@ConditionalOnMissingBean(GlobalTransactionScanner.class)
public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Custom Automatically configure Seata");
}
return new CustomGlobalTransactionScanner(seataProperties.getApplicationId(),
seataProperties.getTxServiceGroup(), failureHandler);
}
- CustomGlobalTransactionScanner 重写
wrapIfNecessary
构建自己的CustomTccActionInterceptor
@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 CustomTccActionInterceptor(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);
}
}
- CustomTccActionInterceptor 自定义
public class CustomTccActionInterceptor extends TccActionInterceptor {
private ActionInterceptorHandler actionInterceptorHandler = new CustomActionInterceptorHandler();
private volatile boolean disable = ConfigurationFactory.getInstance().getBoolean(
ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, DEFAULT_DISABLE_GLOBAL_TRANSACTION);
/**
* Instantiates a new Tcc action interceptor.
*
* @param remotingDesc the remoting desc
*/
public CustomTccActionInterceptor(RemotingDesc remotingDesc) {
this.remotingDesc = remotingDesc;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] arguments = invocation.getArguments();
if (ArrayUtil.isNotEmpty(arguments)) {
for (Object obj : arguments) {
if (obj instanceof BusinessActionContext) {
BusinessActionContext bizContext = (BusinessActionContext) obj;
//tenant 处理
Object tenant = bizContext.getActionContext("tenant");
if (Objects.nonNull(tenant)) {
DynamicDataSourceContextHolder.push(tenant.toString());
TenantContext tenantContext = TenantContextHolder.createEmptyContext();
tenantContext.setTenant(tenant.toString());
TenantContextHolder.setContext(tenantContext);
}
}
}
}
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<String, Object> 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();
}
}
- CustomActionInterceptorHandler 当tcc模式下将租户信息增加到BusinessActionContext
public class CustomActionInterceptorHandler extends ActionInterceptorHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomActionInterceptorHandler.class);
/**
* Handler the TCC Aspect
*
* @param method the method
* @param arguments the arguments
* @param businessAction the business action
* @param targetCallback the target callback
* @return map map
* @throws Throwable the throwable
*/
@Override
public Map<String, Object> proceed(Method method, Object[] arguments, String xid, TwoPhaseBusinessAction businessAction,
Callback<Object> targetCallback) throws Throwable {
Map<String, Object> ret = new HashMap<>(4);
//TCC name
String actionName = businessAction.name();
BusinessActionContext actionContext = new BusinessActionContext();
actionContext.setXid(xid);
//set action name
actionContext.setActionName(actionName);
//Creating Branch Record
String branchId = doTccActionLogStore(method, arguments, businessAction, actionContext);
actionContext.setBranchId(branchId);
//set the parameter whose type is BusinessActionContext
Class<?>[] types = method.getParameterTypes();
int argIndex = 0;
for (Class<?> cls : types) {
if (cls.getName().equals(BusinessActionContext.class.getName())) {
arguments[argIndex] = actionContext;
break;
}
argIndex++;
}
//the final parameters of the try method
ret.put(Constants.TCC_METHOD_ARGUMENTS, arguments);
//the final result
ret.put(Constants.TCC_METHOD_RESULT, targetCallback.execute());
return ret;
}
/**
* Creating Branch Record
*
* @param method the method
* @param arguments the arguments
* @param businessAction the business action
* @param actionContext the action context
* @return the string
*/
@Override
protected String doTccActionLogStore(Method method, Object[] arguments, TwoPhaseBusinessAction businessAction,
BusinessActionContext actionContext) {
String actionName = actionContext.getActionName();
String xid = actionContext.getXid();
//
Map<String, Object> context = fetchActionRequestContext(method, arguments);
//扩展context 增加通用的字段
TenantContext tenantContext = TenantContextHolder.getContext();
if (tenantContext != null) {
context.put("tenant", tenantContext.getTenant());
}
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<String, Object> 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);
}
}
}
- TenantContextHolder 是封装了ThreadLocal.
扩展
- TccCommit, TccPrepare,TccRollback
- 定义三个注解分别在tcc的三个方法使用. 使用aop方式拦截. 控制租户信息的清理.