前言
本章分析AT模式下的Seata客户端(1.5.0),包括RM和TM:
- 客户端启动:GlobalTransactional代理、DataSource代理;
- TM:全局事务开启、提交、回滚,事务传播,全局事务异常异常处理等;
- RM:RM一阶段,RM二阶段(提交、回滚);
- GlobalLock处理脏读脏写;
一、客户端启动
1、GlobalTransactionScanner初始化
GlobalTransactionScanner初始化,开启TM和RM客户端,底层是Netty。由于GlobalTransactionScanner是BeanPostProcessor,所以相较于其他单例Bean会较早初始化。
// GlobalTransactionScanner.java
@Override
public void afterPropertiesSet() {
// service.disableGlobalTransaction = false
if (disableGlobalTransaction) {
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)this);
return;
}
if (initialized.compareAndSet(false, true)) {
// 启动TMClient和RMClient
initClient();
}
}
private void initClient() {
//init TM
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
//init RM
RMClient.init(applicationId, txServiceGroup);
}
复制代码
2、GlobalTransactionScanner创建全局事务代理
GlobalTransactionScanner,针对存在seata全局事务(@GlobalTransactional)的bean,注解创建代理对象。
由于GlobalTransactionScanner继承自AbstractAutoProxyCreator,底层是在postProcessAfterInitialization阶段(每个Bean初始化完成后),为目标Bean创建代理。
public class GlobalTransactionScanner extends AbstractAutoProxyCreator
复制代码
关注wrapIfNecessary方法,这是父类AbstractAutoProxyCreator的protected方法,返回一个Object,往往是代理后的bean。
GlobalTransactionScanner在这里重写,主要是因为要对全局事务切面和本地事务切面的顺序做处理,全局事务切面逻辑GlobalTransactionalInterceptor要在本地事务切面逻辑TransactionInterceptor之前执行。如果bean还未被代理,可以执行super.wrapIfNecessary;否则需要seata自己处理GlobalTransactionalInterceptor和TransactionInterceptor的顺序问题。
// GlobalTransactionScanner.java
private MethodInterceptor interceptor;
private MethodInterceptor globalTransactionalInterceptor;
@Override
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
synchronized (PROXYED_SET) {
if (PROXYED_SET.contains(beanName)) {
return bean;
}
interceptor = null;
if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
// TCC逻辑...
} else {
// 找类上的GlobalTransactional或GlobalLock注解
Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
if (!existsAnnotation(new Class[]{serviceInterface})
&& !existsAnnotation(interfacesIfJdk)) {
return bean;
}
// 创建全局单例GlobalTransactionalInterceptor
if (globalTransactionalInterceptor == null) {
globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
}
interceptor = globalTransactionalInterceptor;
}
if (!AopUtils.isAopProxy(bean)) {
// 如果bean还没有被Spring代理,走Spring代理逻辑
bean = super.wrapIfNecessary(bean, beanName, cacheKey);
} else {
// 否则自己创建Advisor,找到GlobalTransactionalInterceptor应该处于Advisor链的位置
AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
// 利用父类的辅助方法,转换GlobalTransactionalInterceptor为advisor
Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
int pos;
for (Advisor avr : advisor) {
// 处理Spring事务切面和seata切面的顺序
// GlobalTransactionalInterceptor要在TransactionInterceptor之前执行
pos = findAddSeataAdvisorPosition(advised, avr);
advised.addAdvisor(pos, avr);
}
}
PROXYED_SET.add(beanName);
return bean;
}
}
// AbstractAutoProxyCreator让子类提供切面
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource customTargetSource)
throws BeansException {
return new Object[]{interceptor};
}
复制代码
3、SeataAutoDataSourceProxyCreator创建DataSource代理
SeataAutoDataSourceProxyCreator继承自AbstractAutoProxyCreator,所以和GlobalTransactionScanner一样,在Bean初始化结束阶段(postProcessAfterInitialization),创建SpringAOP代理。
public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator
复制代码
对于DataSource类型的所有Bean,都会调用父类wrapIfNecessary创建SpringAop代理。(除SeataDataSourceProxy类型以外)
如果成功代理(bean != enhancer),再创建一个DataSource的真正代理SeataDataSourceProxy,并放入DataSourceProxyHolder存储原始DataSource和代理DataSource的关系。
// SeataAutoDataSourceProxyCreator.java
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// we only care DataSource bean
if (!(bean instanceof DataSource)) {
return bean;
}
// when this bean is just a simple DataSource, not SeataDataSourceProxy
if (!(bean instanceof SeataDataSourceProxy)) {
Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);
// this mean this bean is either excluded by user or had been proxy before
if (bean == enhancer) {
return bean;
}
// else, build proxy, put <origin, proxy> to holder and return enhancer
DataSource origin = (DataSource) bean;
SeataDataSourceProxy proxy = buildProxy(origin, dataSourceProxyMode);
DataSourceProxyHolder.put(origin, proxy);
return enhancer;
}
// ... 用户自己创建了SeataDataSourceProxy类型的Bean,忽略
}
SeataDataSourceProxy buildProxy(DataSource origin, String proxyMode) {
if (BranchType.AT.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxy(origin);
}
if (BranchType.XA.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxyXA(origin);
}
throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + proxyMode);
}
复制代码
根据SpringAop的辅助类AbstractAutoProxyCreator,关注getAdvicesAndAdvisorsForBean方法,DataSource的拦截逻辑在SeataAutoDataSourceProxyAdvice中。
public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
setProxyTargetClass(!useJdkProxy);
this.excludes = new HashSet<>(Arrays.asList(excludes));
this.dataSourceProxyMode = dataSourceProxyMode;
this.advisors = buildAdvisors(dataSourceProxyMode);
}
private Object[] buildAdvisors(String dataSourceProxyMode) {
Advice advice = new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode);
return new Object[]{new DefaultIntroductionAdvisor(advice)};
}
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) {
return advisors;
}
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
if (excludes.contains(beanClass.getName())) {
return true;
}
return SeataProxy.class.isAssignableFrom(beanClass);
}
复制代码
另外这里用到了IntroductionInfo+IntroductionAdvisor,将所有代理的DataSourceBean都实现了SeataProxy标记接口。
public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo {
private final Class<?>[] attachedInterfaces = new Class[]{SeataProxy.class};
@Override
public Class<?>[] getInterfaces() {
return attachedInterfaces;
}
}
复制代码
二、TM
对于一个全局事务来说,分为两种角色:
- Launcher:开启全局事务的角色,即TM;
- Participant:加入当前全局事务的角色,一般什么事都不做,只会传递全局事务(xid),忽略;
public enum GlobalTransactionRole {
// The one begins the current global transaction.
Launcher,
// The one just joins into a existing global transaction.
Participant
}
复制代码
TM的主要逻辑控制,都在GlobalTransactionalInterceptor拦截器中。
GlobalTransactionalInterceptor将GlobalTransactional注解封装为AspectTransactional实体类,进入handleGlobalTransaction方法。
// GlobalTransactionalInterceptor
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
Class<?> targetClass =
methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
final GlobalTransactional globalTransactionalAnnotation =
getAnnotation(method, targetClass, GlobalTransactional.class);
final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
if (!localDisable) {
if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {
AspectTransactional transactional;
if (globalTransactionalAnnotation != null) {
transactional = new AspectTransactional(globalTransactionalAnnotation.timeoutMills(),
globalTransactionalAnnotation.name(), globalTransactionalAnnotation.rollbackFor(),
globalTransactionalAnnotation.noRollbackForClassName(),
globalTransactionalAnnotation.noRollbackFor(),
globalTransactionalAnnotation.noRollbackForClassName(),
globalTransactionalAnnotation.propagation(),
globalTransactionalAnnotation.lockRetryInterval(),
globalTransactionalAnnotation.lockRetryTimes());
} else {
transactional = this.aspectTransactional;
}
// GlobalTransactional
return handleGlobalTransaction(methodInvocation, transactional);
} else if (globalLockAnnotation != null) {
// GlobalLock
return handleGlobalLock(methodInvocation, globalLockAnnotation);
}
}
}
return methodInvocation.proceed();
}
复制代码
handleGlobalTransaction底层逻辑在TransactionalTemplate.execute模板方法中,在模板里会回调用户TransactionalExecutor的几个方法:
- execute:执行实际业务,即执行目标对象的实际方法;
- name:获取当前全局事务的名字,默认取方法名;
- getTransactionInfo:将AspectTransactional二次处理,封装为TransactionInfo;
// GlobalTransactionalInterceptor
Object handleGlobalTransaction(final MethodInvocation methodInvocation,
final AspectTransactional aspectTransactional) throws Throwable {
boolean succeed = true;
try {
return transactionalTemplate.execute(new TransactionalExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
public String name() {
String name = aspectTransactional.getName();
if (!StringUtils.isNullOrEmpty(name)) {
return name;
}
return formatMethod(methodInvocation.getMethod());
}
@Override
public TransactionInfo getTransactionInfo() {
// reset the value of timeout
int timeout = aspectTransactional.getTimeoutMills();
if (timeout <= 0 || timeout == DEFAULT_GLOBAL_TRANSACTION_TIMEOUT) {
timeout = defaultGlobalTransactionTimeout;
}
TransactionInfo transactionInfo = new TransactionInfo();
transactionInfo.setTimeOut(timeout);
transactionInfo.setName(name());
transactionInfo.setPropagation(aspectTransactional.getPropagation());
transactionInfo.setLockRetryInterval(aspectTransactional.getLockRetryInterval());
transactionInfo.setLockRetryTimes(aspectTransactional.getLockRetryTimes());
Set<RollbackRule> rollbackRules = new LinkedHashSet<>();
for (Class<?> rbRule : aspectTransactional.getRollbackFor()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (String rbRule : aspectTransactional.getRollbackForClassName()) {
rollbackRules.add(new RollbackRule(rbRule));
}
for (Class<?> rbRule : aspectTransactional.getNoRollbackFor()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
for (String rbRule : aspectTransactional.getNoRollbackForClassName()) {
rollbackRules.add(new NoRollbackRule(rbRule));
}
transactionInfo.setRollbackRules(rollbackRules);
return transactionInfo;
}
});
} catch (TransactionalExecutor.ExecutionException e) {
TransactionalExecutor.Code code = e.getCode();
switch (code) {
//...异常处理
}
} finally {
if (degradeCheck) {
EVENT_BUS.post(new DegradeCheckEvent(succeed));
}
}
}
复制代码
1、全局事务信息
全局事务在开始之前,首先获取GlobalTransaction。
// TransactionalTemplate
public Object execute(TransactionalExecutor business) throws Throwable {
// 1. Get transactionInfo
TransactionInfo txInfo = business.getTransactionInfo();
if (txInfo == null) {
throw new ShouldNeverHappenException("transactionInfo does not exist");
}
// 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 Handle the transaction propagation.
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
// 处理事务传播级别
}
// 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
if (tx == null) {
tx = GlobalTransactionContext.createNew();
}
// ...
}
// ...
}
复制代码
GlobalTransaction tx = GlobalTransactionContext.getCurrent
这个方法区分当前的全局事务角色是什么,如果RootContext中持有xid全局事务id,那么是事务参与者,否则是事务创建者TM:
// GlobalTransactionContext
public static GlobalTransaction getCurrent() {
String xid = RootContext.getXID();
if (xid == null) {
// 代表当前是全局事务创建者Launcher
return null;
}
// 代表当前是全局事务参与者
return new DefaultGlobalTransaction(xid, GlobalStatus.Begin, GlobalTransactionRole.Participant);
}
复制代码
如果是发起者TM,返回tx是null,当事务传播级别为REQUIRED时(默认),会通过GlobalTransactionContext.createNew
创建新的GlobalTransaction;
如果是参与者,有上游传入的xid,返回tx就是非空,代表当前正处于一个全局事务中。
// GlobalTransactionContext
public static GlobalTransaction createNew() {
return new DefaultGlobalTransaction();
}
复制代码
GlobalTransaction的实现是DefaultGlobalTransaction,包含四个重要属性:
- transactionManager:事务管理器,负责begin、commit、rollback,所以实际事务操作DefaultGlobalTransaction都是委托里面的transactionManager实现的;
- xid:全局事务id;
- status:全局事务状态;
- role:全局事务角色;
// DefaultGlobalTransaction
// 事务管理器,负责begin、commit、rollback
private TransactionManager transactionManager;
// 全局事务id
private String xid;
// 全局事务状态
private GlobalStatus status;
// 全局事务角色
private GlobalTransactionRole role;
DefaultGlobalTransaction() {
this(null, GlobalStatus.UnKnown, GlobalTransactionRole.Launcher);
}
DefaultGlobalTransaction(String xid, GlobalStatus status, GlobalTransactionRole role) {
this.transactionManager = TransactionManagerHolder.get();
this.xid = xid;
this.status = status;
this.role = role;
}
复制代码
2、处理事务传播
根据TransactionInfo的propagation(注解中的propagation),执行不同的事务传播逻辑。
// TransactionalTemplate.execute
// 1.1 Get current transaction
GlobalTransaction tx = GlobalTransactionContext.getCurrent();
// 1.2 Handle the transaction propagation.
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
// 处理事务传播级别
}
复制代码
REQUIRED:默认传播级别,如果当前有全局事务就加入,如果当前没全局事务,就开启新的全局事务(GlobalTransactionContext.createNew)。
// TransactionalTemplate.execute
case REQUIRED:
break;
复制代码
NEVER:如果tx不为空,处于全局事务中,抛出异常;否则直接执行业务;
MANDATORY:如果tx为空,未处于全局事务中,抛出异常,否则作为Participant参与全局事务;
// TransactionalTemplate.execute
case NEVER:
// If transaction is existing, throw exception.
if (existingTransaction(tx)) {
throw new TransactionException(
String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"
, tx.getXid()));
} else {
// Execute without transaction and return.
return business.execute();
}
case MANDATORY:
// If transaction is not existing, throw exception.
if (notExistingTransaction(tx)) {
throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");
}
// Continue and execute with current transaction.
break;
复制代码
NOT_SUPPORTED:不支持事务,如果当前处于全局事务中,则挂起当前事务,当业务执行完毕,再恢复全局事务;
SUPPORTS:支持事务,如果当前未处于全局事务中,则无事务执行业务,否则加入当前全局事务;
// TransactionalTemplate.execute
case NOT_SUPPORTED:
// If transaction is existing, suspend it.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
}
// Execute without transaction and return.
return business.execute();
case SUPPORTS:
// If transaction is not existing, execute without transaction.
if (notExistingTransaction(tx)) {
return business.execute();
}
// Continue and execute with new transaction
break;
复制代码
事务的挂起与恢复,在全局事务中就体现在ThreadLocal中存储的xid:
- 当事务挂起时,从RootContext中移除xid,返回一个SuspendedResourcesHolder存储xid;
- 当事务恢复时,将SuspendedResourcesHolder中存储的xid再放入RootContext;
// DefaultGlobalTransaction
public SuspendedResourcesHolder suspend() throws TransactionException {
// In order to associate the following logs with XID, first get and then unbind.
String xid = RootContext.getXID();
if (xid != null) {
RootContext.unbind();
return new SuspendedResourcesHolder(xid);
} else {
return null;
}
}
复制代码
REQUIRES_NEW:如果当前处于全局事务,则挂起当前全局事务,开启一个新的全局事务;否则直接创建一个新的全局事务;
// TransactionalTemplate.execute
case REQUIRES_NEW:
// If transaction is existing, suspend it, and then begin new transaction.
if (existingTransaction(tx)) {
suspendedResourcesHolder = tx.suspend();
tx = GlobalTransactionContext.createNew();
}
// Continue and execute with new transaction
break;
复制代码
seata并没有提供NESTED事务传播级别(Spring事务中NESTED底层是基于database的savepoint机制)。
3、开启全局事务
TransactionalTemplate在构建完全局事务信息后,开始执行begin、business、commit/rollback。
// TransactionalTemplate.execute
try {
// 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,
// else do nothing. Of course, the hooks will still be triggered.
beginTransaction(txInfo, tx);
Object rs;
try {
// Do Your Business
rs = business.execute();
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. everything is fine, commit.
commitTransaction(tx);
return rs;
} finally {
//5. clear
resumeGlobalLockConfig(previousConfig);
triggerAfterCompletion();
cleanUp();
}
复制代码
对于全局事务的发起者来说,即TM,这里会调用DefaultTransactionManager的begin方法向TC(seata-server)创建全局事务。TC会返回一个xid(全局事务id),TM将事务状态从Unknown转变为Begin,并将xid存储在ThreadLocal中(RootContext)。
// TransactionalTemplate
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeBegin();
tx.begin(txInfo.getTimeOut(), txInfo.getName());
triggerAfterBegin();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.BeginFailure);
}
}
// DefaultGlobalTransaction
public void begin(int timeout, String name) throws TransactionException {
if (role != GlobalTransactionRole.Launcher) { // 事务参与者,什么事都不做
assertXIDNotNull();
return;
}
assertXIDNull();
String currentXid = RootContext.getXID();
if (currentXid != null) {
throw new IllegalStateException("Global transaction already exists," +
" can't begin a new global transaction, currentXid = " + currentXid);
}
// DefaultTransactionManager -> GlobalBeginRequest -> TC
xid = transactionManager.begin(null, null, name, timeout);
// Unknown->Begin
status = GlobalStatus.Begin;
RootContext.bind(xid);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Begin new global transaction [{}]", xid);
}
}
复制代码
4、提交全局事务
当业务正常处理完毕后(如果TM还承担了RM的角色,那么本地事务也都执行完毕了),TM会将xid提交给TC,TC会返回当前事务状态,status由TC决定,TM最后会将xid从RootContext中解绑,全局事务结束。
// TransactionalTemplate
private void commitTransaction(GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
triggerBeforeCommit();
tx.commit();
triggerAfterCommit();
} catch (TransactionException txe) {
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.CommitFailure);
}
}
// DefaultGlobalTransaction
public void commit() throws TransactionException {
if (role == GlobalTransactionRole.Participant) {
return;
}
assertXIDNotNull();
// 默认重试5次 client.tm.commitRetryCount
int retry = COMMIT_RETRY_COUNT <= 0 ? DEFAULT_TM_COMMIT_RETRY_COUNT : COMMIT_RETRY_COUNT;
try {
while (retry > 0) {
try {
retry--;
// GlobalCommitRequest 发送全局事务id给TC,TC会返回全局事务状态给TM
status = transactionManager.commit(xid);
break;
} catch (Throwable ex) {
LOGGER.error("Failed to report global commit [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
if (retry == 0) {
throw new TransactionException("Failed to report global commit", ex);
}
}
}
} finally {
// 将xid从ThreadLocal中移除,代表这个全局事务已经结束
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
}
复制代码
5、回滚全局事务
默认情况下,GlobalTransactional注解不配置noRollbackFor全局事务都会选择回滚,如果执行rollback成功,会用TransactionalExecutor.ExecutionException包装原始异常抛出。
// TransactionalTemplate
private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException {
if (txInfo != null && txInfo.rollbackOn(originalException)) {
try {
// 默认所有异常都会回滚
rollbackTransaction(tx, originalException);
} catch (TransactionException txe) {
// Failed to rollback
throw new TransactionalExecutor.ExecutionException(tx, txe,
TransactionalExecutor.Code.RollbackFailure, originalException);
}
} else {
// 如果GlobalTransactional排除部分异常,可以选择提交
commitTransaction(tx);
}
}
private void rollbackTransaction(GlobalTransaction tx, Throwable originalException) throws TransactionException, TransactionalExecutor.ExecutionException {
triggerBeforeRollback();
tx.rollback();
triggerAfterRollback();
// 3.1 Successfully rolled back
throw new TransactionalExecutor.ExecutionException(tx, GlobalStatus.RollbackRetrying.equals(tx.getLocalStatus())
? TransactionalExecutor.Code.RollbackRetrying : TransactionalExecutor.Code.RollbackDone, originalException);
}
复制代码
TM向TC发送回滚请求,请求包含xid,由TC返回全局事务状态。这和TM发送提交请求类似。
// DefaultGlobalTransaction
public void rollback() throws TransactionException {
if (role == GlobalTransactionRole.Participant) {
return;
}
assertXIDNotNull();
// 默认重试5次 client.tm.rollbackRetryCount
int retry = ROLLBACK_RETRY_COUNT <= 0 ? DEFAULT_TM_ROLLBACK_RETRY_COUNT : ROLLBACK_RETRY_COUNT;
try {
while (retry > 0) {
try {
retry--;
// GlobalRollbackRequest,包含全局事务id给TC,TC会返回全局事务状态给TM
status = transactionManager.rollback(xid);
break;
} catch (Throwable ex) {
LOGGER.error("Failed to report global rollback [{}],Retry Countdown: {}, reason: {}", this.getXid(), retry, ex.getMessage());
if (retry == 0) {
throw new TransactionException("Failed to report global rollback", ex);
}
}
}
} finally {
if (xid.equals(RootContext.getXID())) {
suspend();
}
}
}
复制代码
6、异常处理
TM针对不同情况的异常处理如下:
// GlobalTransactionalInterceptor
Object handleGlobalTransaction(final MethodInvocation methodInvocation,
final AspectTransactional aspectTransactional) throws Throwable {
boolean succeed = true;
try {
return transactionalTemplate.execute(new TransactionalExecutor() {
// ...
});
} catch (TransactionalExecutor.ExecutionException e) {
TransactionalExecutor.Code code = e.getCode();
switch (code) {
case RollbackDone:
throw e.getOriginalException();
case BeginFailure:
succeed = false;
failureHandler.onBeginFailure(e.getTransaction(), e.getCause());
throw e.getCause();
case CommitFailure:
succeed = false;
failureHandler.onCommitFailure(e.getTransaction(), e.getCause());
throw e.getCause();
case RollbackFailure:
failureHandler.onRollbackFailure(e.getTransaction(), e.getOriginalException());
throw e.getOriginalException();
case RollbackRetrying:
failureHandler.onRollbackRetrying(e.getTransaction(), e.getOriginalException());
throw e.getOriginalException();
default:
throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code));
}
} finally {
// ...
}
}
复制代码
RollbackDone:全局事务回滚成功,抛出原始业务异常即可;
BeginFailure:全局事务开启失败,DefaultFailureHandlerImpl.onBeginFailure打印日志,抛出ExecutionException异常;
// DefaultFailureHandlerImpl
public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
LOGGER.warn("Failed to begin transaction. ", cause);
}
复制代码
CommitFailure/RollbackFailure:全局事务提交或回滚失败,这是由于TM请求TC返回失败,提交CheckTimerTask每10s发送一次GlobalStatusRequest给TC获取当前xid对应的全局事务状态,当TC返回目标状态后结束查询任务(最多查询360次)。
// DefaultFailureHandlerImpl
public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
LOGGER.warn("Failed to commit transaction[" + tx.getXid() + "]", cause);
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Committed), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
LOGGER.warn("Failed to rollback transaction[" + tx.getXid() + "]", originalException);
timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Rollbacked), SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
protected class CheckTimerTask implements TimerTask {
@Override
public void run(Timeout timeout) throws Exception {
if (!isStopped) {
if (++count > RETRY_MAX_TIMES) {
return;
}
isStopped = shouldStop(tx, required);
timer.newTimeout(this, SCHEDULE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}
}
private boolean shouldStop(final GlobalTransaction tx, GlobalStatus required) {
try {
GlobalStatus status = tx.getStatus();
if (status == required || status == GlobalStatus.Finished) {
return true;
}
} catch (TransactionException e) {
LOGGER.error("fetch GlobalTransaction status error", e);
}
return false;
}
复制代码
RollbackRetrying:TM向TC发送全局事务回滚请求成功,TC返回状态是RollbackRetrying。处理方式同RollbackFailure。
三、RM一阶段
RM一阶段处理本地事务,主要是在DataSource、Connection、Statement上做文章。
这里重点关注三个点:1)获取Connection得到ConnectionProxy;2)执行SQL;3)提交/回滚
1、获取Connection
在启动阶段,SeataAutoDataSourceProxyCreator为所有DataSource类型Bean创建了SpringAop代理,代理逻辑在SeataAutoDataSourceProxyAdvice的invoke方法中。
getConnection方法会由SeataDataSourceProxy执行。
public class SeataAutoDataSourceProxyAdvice implements MethodInterceptor, IntroductionInfo {
private final BranchType branchType;
public SeataAutoDataSourceProxyAdvice(String dataSourceProxyMode) {
this.branchType = BranchType.get(dataSourceProxyMode);
RootContext.setDefaultBranchType(this.branchType);
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// case1 如果没有开启全局事务,执行目标方法
if (!inExpectedContext()) {
return invocation.proceed();
}
Method method = invocation.getMethod();
String name = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
// case2 如果目标方法不是DataSource的方法,执行目标方法
Method declared;
try {
declared = DataSource.class.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
return invocation.proceed();
}
DataSource origin = (DataSource) invocation.getThis();
// case3 获取SeataDataSourceProxy,执行代理方法
SeataDataSourceProxy proxy = DataSourceProxyHolder.get(origin);
Object[] args = invocation.getArguments();
return declared.invoke(proxy, args);
}
boolean inExpectedContext() {
if (RootContext.requireGlobalLock()) {
return true;
}
if (!RootContext.inGlobalTransaction()) {
return false;
}
return branchType == RootContext.getBranchType();
}
}
复制代码
DataSourceProxy是AT模式下的SeataDataSourceProxy实现。
getConnection方法通过实际DataSource得到实际Connection后,封装为ConnectionProxy代理,返回给用户代码。
如此,之后通过ConnectionProxy获取PrepareStatement也会是PrepareStatementProxy对象。
// DataSourceProxy
@Override
public ConnectionProxy getConnection() throws SQLException {
Connection targetConnection = targetDataSource.getConnection();
return new ConnectionProxy(this, targetConnection);
}
复制代码
2、执行sql
PreparedStatementProxy执行sql都交给ExecuteTemplate执行模板,传入业务方法(实际执行sql)作为callback在模板中被回调。
public class PreparedStatementProxy extends AbstractPreparedStatementProxy
implements PreparedStatement, ParametersHolder {
@Override
public Map<Integer,ArrayList<Object>> getParameters() {
return parameters;
}
public PreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement,
String targetSQL) throws SQLException {
super(connectionProxy, targetStatement, targetSQL);
}
@Override
public boolean execute() throws SQLException {
return ExecuteTemplate.execute(this, (statement, args) -> statement.execute());
}
@Override
public ResultSet executeQuery() throws SQLException {
return ExecuteTemplate.execute(this, (statement, args) -> statement.executeQuery());
}
@Override
public int executeUpdate() throws SQLException {
return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate());
}
}
复制代码
ExecuteTemplate
ExecuteTemplate模板分为三步:
- 解析sql:默认情况下利用com.alibaba.druid.sql.SQLUtils#parseStatements解析sql为SQLRecognizer;
- 根据sql类型选择执行器Executor;
- 执行sql:用第二步构建的执行器执行sql;
// ExecuteTemplate
public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,
StatementProxy<S> statementProxy,
StatementCallback<T, S> statementCallback,
Object... args) throws SQLException {
if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
return statementCallback.execute(statementProxy.getTargetStatement(), args);
}
String dbType = statementProxy.getConnectionProxy().getDbType();
if (CollectionUtils.isEmpty(sqlRecognizers)) {
// 1. 解析sql
sqlRecognizers = SQLVisitorFactory.get(statementProxy.getTargetSQL(), dbType);
}
Executor<T> executor;
if (CollectionUtils.isEmpty(sqlRecognizers)) {
executor = new PlainExecutor<>(statementProxy, statementCallback);
} else {
// 2. 根据sql类型,选择不同的Executor执行器
if (sqlRecognizers.size() == 1) {
SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
switch (sqlRecognizer.getSQLType()) {
case INSERT:
executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,
new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},
new Object[]{statementProxy, statementCallback, sqlRecognizer});
break;
case UPDATE:
executor = new UpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
case DELETE:
executor = new DeleteExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
case SELECT_FOR_UPDATE:
executor = new SelectForUpdateExecutor<>(statementProxy, statementCallback, sqlRecognizer);
break;
case INSERT_ON_DUPLICATE_UPDATE:
switch (dbType) {
case JdbcConstants.MYSQL:
case JdbcConstants.MARIADB:
executor =
new MySQLInsertOrUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
break;
default:
throw new NotSupportYetException(dbType + " not support to INSERT_ON_DUPLICATE_UPDATE");
}
break;
default:
executor = new PlainExecutor<>(statementProxy, statementCallback);
break;
}
} else {
executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);
}
}
T rs;
try {
// 3. 执行sql
rs = executor.execute(args);
} catch (Throwable ex) {
if (!(ex instanceof SQLException)) {
ex = new SQLException(ex);
}
throw (SQLException) ex;
}
return rs;
}
复制代码
总结一下sql类型和执行器的对应关系:
sql类型 | 执行器 |
---|---|
insert | InsertExecutor |
update | UpdateExecutor |
delete | DeleteExecutor |
select for update | SelectForUpdateExecutor |
insert on duplicate key update(MySQL) | MySQLInsertOrUpdateExecutor |
多个sql组合执行(update table set field=? where id = ?;update table set field=? where id = ?) | MultiExecutor |
其他(seata不做任何代理逻辑) | PlainExecutor |
Executor
以update storage_tbl set count = count - ? where commodity_code = ?为例,进入UpdateExecutor执行sql。
UpdateExecutor在构造时注入了三个对象:
- statementProxy:PrepareStatementProxy实例;
- statementCallback:实际prepareStatement执行业务sql的callback函数,在PreparedStatementProxy.executeUpdate时传入;
- sqlRecognizer:sql解析后的SQLRecognizer实例;
public class UpdateExecutor<T, S extends Statement> extends AbstractDMLBaseExecutor<T, S> {
public UpdateExecutor(StatementProxy<S> statementProxy, StatementCallback<T, S> statementCallback,
SQLRecognizer sqlRecognizer) {
super(statementProxy, statementCallback, sqlRecognizer);
}
}
复制代码
BaseTransactionalExecutor是UpdateExecutor的基类,execute将一些上下文属性放入ConnectionProxy。
// BaseTransactionalExecutor
public T execute(Object... args) throws Throwable {
String xid = RootContext.getXID();
if (xid != null) {
statementProxy.getConnectionProxy().bind(xid);
}
statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
return doExecute(args);
}
复制代码
AbstractDMLBaseExecutor是DML语句的抽象实现,继承自BaseTransactionalExecutor,实现doExecute方法。
// AbstractDMLBaseExecutor
public T doExecute(Object... args) throws Throwable {
AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
if (connectionProxy.getAutoCommit()) {
// 当autocommit=true时
return executeAutoCommitTrue(args);
} else {
// 当autocommit=false时,处于本地事务中
return executeAutoCommitFalse(args);
}
}
复制代码
当autocommit=true时,seata自己开启了事务,设置autocommit=false,目的是本地事务与seata的undolog在一个事务中提交,其底层还是调用了autocommit=false的逻辑。
// AbstractDMLBaseExecutor
protected T executeAutoCommitTrue(Object[] args) throws Throwable {
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
try {
// 手动修改autocommit=false,开启本地事务,为了undolog和这个sql放在一个事务里提交
connectionProxy.changeAutoCommit();
// commit阶段 获取全局锁的重试策略
return new LockRetryPolicy(connectionProxy).execute(() -> {
// 执行autocommit=false的逻辑
T result = executeAutoCommitFalse(args);
// 提交事务
connectionProxy.commit();
return result;
});
} catch (Exception e) {
// when exception occur in finally,this exception will lost, so just print it here
LOGGER.error("execute executeAutoCommitTrue error:{}", e.getMessage(), e);
if (!LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {
connectionProxy.getTargetConnection().rollback();
}
throw e;
} finally {
connectionProxy.getContext().reset();
// 恢复autocommit=true
connectionProxy.setAutoCommit(true);
}
}
复制代码
这里我们重点关注autocommit=false的逻辑,将执行sql与本地事务提交分开讨论。
执行一个DML主要分为四步:
- 构建前置镜像beforeImage
- 执行sql
- 构建后置镜像afterImage
- 构建undoLog,存储到ConnectionProxy
// AbstractDMLBaseExecutor
protected T executeAutoCommitFalse(Object[] args) throws Exception {
if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
throw new NotSupportYetException("multi pk only support mysql!");
}
// 1. 构建beforeImage
TableRecords beforeImage = beforeImage();
// 2. 执行sql
T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
int updateCount = statementProxy.getUpdateCount();
if (updateCount > 0) {
// 3. 构建afterImage
TableRecords afterImage = afterImage(beforeImage);
// 4. 构建undolog放入ConnectionProxy
prepareUndoLog(beforeImage, afterImage);
}
return result;
}
复制代码
对于update语句来说,UpdateExecutor通过select for update获取前置镜像,如update语句是:update storage_tbl set count = count - ? where commodity_code = ?,seata获取前置镜像拼接的sql是:SELECT id, count FROM storage_tbl WHERE commodity_code = ? FOR UPDATE。
// UpdateExecutor
protected TableRecords beforeImage() throws SQLException {
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
TableMeta tmeta = getTableMeta();
// 构建select for update
String selectSQL = buildBeforeImageSQL(tmeta, paramAppenderList);
// 执行select for update 返回结果即beforeImage
return buildTableRecords(tmeta, selectSQL, paramAppenderList);
}
private String buildBeforeImageSQL(TableMeta tableMeta, ArrayList<List<Object>> paramAppenderList) {
SQLUpdateRecognizer recognizer = (SQLUpdateRecognizer) sqlRecognizer;
List<String> updateColumns = recognizer.getUpdateColumns();
StringBuilder prefix = new StringBuilder("SELECT ");
StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL());
String whereCondition = buildWhereCondition(recognizer, paramAppenderList);
if (StringUtils.isNotBlank(whereCondition)) {
suffix.append(WHERE).append(whereCondition);
}
// ...
suffix.append(" FOR UPDATE");
StringJoiner selectSQLJoin = new StringJoiner(", ", prefix.toString(), suffix.toString());
// ...
return selectSQLJoin.toString();
}
复制代码
对于delete语句来说,和update一样,也是通过select for update获取镜像数据,不同点在于默认情况下,update只会将更新字段放到前置镜像中,而delete会将所有字段放到前置镜像。
// DeleteExecutor
protected TableRecords beforeImage() throws SQLException {
SQLDeleteRecognizer visitor = (SQLDeleteRecognizer) sqlRecognizer;
TableMeta tmeta = getTableMeta(visitor.getTableName());
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
String selectSQL = buildBeforeImageSQL(visitor, tmeta, paramAppenderList);
return buildTableRecords(tmeta, selectSQL, paramAppenderList);
}
private String buildBeforeImageSQL(SQLDeleteRecognizer visitor, TableMeta tableMeta, ArrayList<List<Object>> paramAppenderList) {
String whereCondition = buildWhereCondition(visitor, paramAppenderList);
StringBuilder suffix = new StringBuilder(" FROM ").append(getFromTableInSQL());
if (StringUtils.isNotBlank(whereCondition)) {
suffix.append(WHERE).append(whereCondition);
}
suffix.append(" FOR UPDATE");
StringJoiner selectSQLAppender = new StringJoiner(", ", "SELECT ", suffix.toString());
for (String column : tableMeta.getAllColumns().keySet()) {
selectSQLAppender.add(getColumnNameInSQL(ColumnUtils.addEscape(column, getDbType())));
}
return selectSQLAppender.toString();
}
复制代码
对于insert语句来说,前置镜像除了表结构,不包含任何东西。
对于insert on duplicate key update,底层还是select for update获取的前置镜像。
// MySQLInsertOrUpdateExecutor
public TableRecords beforeImage() throws SQLException {
TableMeta tmeta = getTableMeta();
//after image sql the same of before image
if (StringUtils.isBlank(selectSQL)) {
paramAppenderList = new ArrayList<>();
selectSQL = buildImageSQL(tmeta);
}
return buildTableRecords2(tmeta, selectSQL, paramAppenderList);
}
public TableRecords buildTableRecords2(TableMeta tableMeta, String selectSQL, ArrayList<List<Object>> paramAppenderList) throws SQLException {
ResultSet rs = null;
try (PreparedStatement ps = statementProxy.getConnection().prepareStatement(selectSQL + " FOR UPDATE")) {
if (CollectionUtils.isNotEmpty(paramAppenderList)) {
for (int i = 0, ts = paramAppenderList.size(); i < ts; i++) {
List<Object> paramAppender = paramAppenderList.get(i);
for (int j = 0, ds = paramAppender.size(); j < ds; j++) {
ps.setObject(i * ds + j + 1, "NULL".equals(paramAppender.get(j).toString()) ? null : paramAppender.get(j));
}
}
}
rs = ps.executeQuery();
return TableRecords.buildRecords(tableMeta, rs);
} finally {
IOUtil.close(rs);
}
}
复制代码
执行完sql后,构建后置镜像逻辑都类似,通过普通select语句即可得到后置镜像。delete的后置镜像和insert的前置镜像一样,只有表结构。
比如UpdateExecutor的后置镜像,通过执行SELECT id, count FROM storage_tbl WHERE (id) in ( (?) )得到。
// UpdateExecutor
protected TableRecords afterImage(TableRecords beforeImage) throws SQLException {
TableMeta tmeta = getTableMeta();
if (beforeImage == null || beforeImage.size() == 0) {
return TableRecords.empty(getTableMeta());
}
// 普通select语句by主键,查询字段仅包含更新字段
String selectSQL = buildAfterImageSQL(tmeta, beforeImage);
ResultSet rs = null;
try (PreparedStatement pst = statementProxy.getConnection().prepareStatement(selectSQL)) {
SqlGenerateUtils.setParamForPk(beforeImage.pkRows(), getTableMeta().getPrimaryKeyOnlyName(), pst);
rs = pst.executeQuery();
return TableRecords.buildRecords(tmeta, rs);
} finally {
IOUtil.close(rs);
}
}
复制代码
构建完后置镜像后,执行sql的最后一步是构建undoLog,这一步仅仅是构造undoLog,在一阶段提交undoLog才起作用。
BaseTransactionalExecutor#prepareUndoLog在构建undoLog之前,还构建了lockKeys,即全局锁的key。
对于MySQL来说,只有一个主键,lockKeys的模式是:{表名}:{主键值1},...,{主键值n}。比如update storage_tbl set count = count - ? where commodity_code = ?,对应的数据记录是id=8和id=9,那么lockKeys=storage_tbl:8,9。
SQLUndoLog包含四个属性:sql类型、表名、前置镜像、后置镜像。
// BaseTransactionalExecutor
protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) {
return;
}
if (SQLType.UPDATE == sqlRecognizer.getSQLType()) {
if (beforeImage.getRows().size() != afterImage.getRows().size()) {
throw new ShouldNeverHappenException("Before image size is not equaled to after image size, probably because you updated the primary keys.");
}
}
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
// 构建lockKeys放入ConnectionProxy,{表名}:{主键值1},...,{主键值n}
TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
String lockKeys = buildLockKey(lockKeyRecords);
if (null != lockKeys) {
connectionProxy.appendLockKey(lockKeys);
// 构建undolog放入ConnectionProxy
SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
connectionProxy.appendUndoLog(sqlUndoLog);
}
}
protected SQLUndoLog buildUndoItem(TableRecords beforeImage, TableRecords afterImage) {
SQLType sqlType = sqlRecognizer.getSQLType();
String tableName = sqlRecognizer.getTableName();
SQLUndoLog sqlUndoLog = new SQLUndoLog();
sqlUndoLog.setSqlType(sqlType);
sqlUndoLog.setTableName(tableName);
sqlUndoLog.setBeforeImage(beforeImage);
sqlUndoLog.setAfterImage(afterImage);
return sqlUndoLog;
}
复制代码
RM一阶段执行SQL总结
3、提交
ConnectionProxy#commit方法用LockRetryPolicy对doCommit方法进行重试。
// ConnectionProxy
private final LockRetryPolicy lockRetryPolicy = new LockRetryPolicy(this);
public void commit() throws SQLException {
try {
lockRetryPolicy.execute(() -> {
doCommit();
return null;
});
} catch (SQLException e) {
if (targetConnection != null && !getAutoCommit() && !getContext().isAutoCommitChanged()) {
rollback();
}
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
复制代码
如果当前处于全局事务中ConnectionProxy#commit走processGlobalTransactionCommit方法。
// ConnectionProxy
private void doCommit() throws SQLException {
if (context.inGlobalTransaction()) { // xid
processGlobalTransactionCommit();
} else if (context.isGlobalLockRequire()) {
processLocalCommitWithGlobalLocks();
} else {
targetConnection.commit();
}
}
复制代码
processGlobalTransactionCommit方法是RM提交事务的核心方法,一共分为以下几步:
- 向TC注册分支事务,获取全局锁,如果获取全局锁失败,抛出LockConflictException异常,其他情况抛出SQLException;
- 写入undolog,之前在执行sql的时候保存到ConnectionProxy的undoLog写入db;
- 提交本地事务,将业务DML和undolog一起提交;
- 如果本地提交失败,向TC发送BranchReportRequest,并表明一阶段提交失败;
- 如果本地提交成功,默认情况下就直接结束返回;
// ConnectionProxy
private void processGlobalTransactionCommit() throws SQLException {
try {
// 1. 向TC注册分支事务,获取全局锁
register();
} catch (TransactionException e) {
// 获取全局锁失败,抛出LockConflictException异常
recognizeLockKeyConflictException(e, context.buildLockKeys());
}
try {
// 2. mysql写入undolog
UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);
// 3. 提交本地事务,释放本地锁
targetConnection.commit();
} catch (Throwable ex) {
LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex);
// 4. 本地事务提交失败,发送BranchReportRequest PhaseOne_Failed
report(false);
throw new SQLException(ex);
}
if (IS_REPORT_SUCCESS_ENABLE) { // 这里默认为false,不会走
// 5. 本地事务提交成功,发送BranchReportRequest PhaseOne_Done
report(true);
}
context.reset();
}
复制代码
注册分支事务
注册分支事务基于分支类型AT委派到DataSourceManager处理,其父类AbstractResourceManager实现的branchRegister方法向TC发送BranchRegisterRequest。
// ConnectionProxy
private void register() throws TransactionException {
if (!context.hasUndoLog() || !context.hasLockKey()) {
return;
}
Long branchId = DefaultResourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(),
null, context.getXid(), context.getApplicationData(), context.buildLockKeys());
context.setBranchId(branchId);
}
// DefaultResourceManager
public Long branchRegister(BranchType branchType, String resourceId,
String clientId, String xid, String applicationData, String lockKeys)
throws TransactionException {
return getResourceManager(branchType).branchRegister(branchType, resourceId, clientId, xid, applicationData,
lockKeys);
}
// AbstractResourceManager(DataSourceManager)
public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
try {
BranchRegisterRequest request = new BranchRegisterRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
request.setBranchType(branchType);
request.setApplicationData(applicationData);
BranchRegisterResponse response = (BranchRegisterResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
}
return response.getBranchId();
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", rex);
}
}
复制代码
BranchRegisterRequest包含五个属性:
- xid:全局事务id,由TM向TC开启全局事务得到;
- lockKey:全局锁的key集合,如:dml01锁storage_tbl:8,9、dml02锁storage_tbl:1,2,最终lockKey是storage_tbl:8,9;storage_tbl:1,2;
- resourceId:代表当前rm数据源,如:jdbc:mysql://127.0.0.1:3306/db01;
- branchType:AT;
- applicationData:扩展信息,可以理解为一个map;
TC处理成功会返回branchId,即本次注册成功的分支事务id。
如果返回失败,会抛出RmTransactionException异常。
反过来关注recognizeLockKeyConflictException方法处理TransactionException异常,如果TC返回获取全局锁失败,会抛出LockConflictException,对于这个异常在提交阶段是有重试处理的;TC返回其他异常,抛出SQLException,导致RM一阶段事务回滚,最终TM会回滚全局事务。
// ConnectionProxy
private void recognizeLockKeyConflictException(TransactionException te, String lockKeys) throws SQLException {
if (te.getCode() == TransactionExceptionCode.LockKeyConflict
|| te.getCode() == TransactionExceptionCode.LockKeyConflictFailFast) {
StringBuilder reasonBuilder = new StringBuilder("get global lock fail, xid:");
reasonBuilder.append(context.getXid());
if (StringUtils.isNotBlank(lockKeys)) {
reasonBuilder.append(", lockKeys:").append(lockKeys);
}
throw new LockConflictException(reasonBuilder.toString(), te.getCode());
} else {
throw new SQLException(te);
}
}
复制代码
保存UndoLog
// ConnectionProxy.processGlobalTransactionCommit
UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);
复制代码
RM数据源使用MySQL时,UndoLogManager的实现类是MySQLUndoLogManager,flushUndoLogs方法由抽象父类AbstractUndoLogManager实现基础框架。
// AbstractUndoLogManager
public void flushUndoLogs(ConnectionProxy cp) throws SQLException {
ConnectionContext connectionContext = cp.getContext();
if (!connectionContext.hasUndoLog()) {
return;
}
String xid = connectionContext.getXid();
long branchId = connectionContext.getBranchId();
// UndoLog内容,包含xid、branchId、执行sql阶段存储的SQLUndoLog集合
BranchUndoLog branchUndoLog = new BranchUndoLog();
branchUndoLog.setXid(xid);
branchUndoLog.setBranchId(branchId);
branchUndoLog.setSqlUndoLogs(connectionContext.getUndoItems());
// 默认使用jackson序列化UndoLog
UndoLogParser parser = UndoLogParserFactory.getInstance();
byte[] undoLogContent = parser.encode(branchUndoLog);
// 如果UndoLog大于64KB启用压缩,默认使用zip压缩
CompressorType compressorType = CompressorType.NONE;
if (needCompress(undoLogContent)) {
compressorType = ROLLBACK_INFO_COMPRESS_TYPE;
undoLogContent = CompressorFactory.getCompressor(compressorType.getCode()).compress(undoLogContent);
}
// 子类实现
insertUndoLogWithNormal(xid, branchId, buildContext(parser.getName(), compressorType), undoLogContent, cp.getTargetConnection());
}
复制代码
看一下undo_log表每个字段存储的内容。
// MySQLUndoLogManager
protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent,
Connection conn) throws SQLException {
insertUndoLog(xid, branchId, rollbackCtx, undoLogContent, State.Normal, conn);
}
private void insertUndoLog(String xid, long branchId, String rollbackCtx, byte[] undoLogContent,
State state, Connection conn) throws SQLException {
try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) {
pst.setLong(1, branchId); // 分支事务id
pst.setString(2, xid); // 全局事务id
pst.setString(3, rollbackCtx); // 回滚上下文,包含序列化方式、压缩方式
pst.setBytes(4, undoLogContent); // UndoLog
pst.setInt(5, state.getValue()); // 状态
pst.executeUpdate();
} catch (Exception e) {
if (!(e instanceof SQLException)) {
e = new SQLException(e);
}
throw (SQLException) e;
}
}
复制代码
反馈提交结果
经过分支事务注册,写入undo_log之后,本地事务提交。如果提交失败,RM向TC汇报BranchReportRequest,告知TC一阶段提交失败;如果提交成功,默认情况下不做任何操作。
// ConnectionProxy
private void report(boolean commitDone) throws SQLException {
if (context.getBranchId() == null) {
return;
}
int retry = REPORT_RETRY_COUNT; // 5次
while (retry > 0) {
try {
DefaultResourceManager.get().branchReport(BranchType.AT, context.getXid(), context.getBranchId(),
commitDone ? BranchStatus.PhaseOne_Done : BranchStatus.PhaseOne_Failed, null);
return;
} catch (Throwable ex) {
LOGGER.error("Failed to report [" + context.getBranchId() + "/" + context.getXid() + "] commit done ["
+ commitDone + "] Retry Countdown: " + retry);
retry--;
if (retry == 0) {
throw new SQLException("Failed to report branch status " + commitDone, ex);
}
}
}
}
// AbstractResourceManager
public void branchReport(BranchType branchType, String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException {
BranchReportRequest request = new BranchReportRequest();
request.setXid(xid);
request.setBranchId(branchId);
request.setStatus(status);
request.setApplicationData(applicationData);
BranchReportResponse response = (BranchReportResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request);
// ...
}
复制代码
全局锁重试
注册分支事务阶段,TC会根据BranchRegisterRequest中的lockKey去获取全局锁,可能返回异常LockConflictException,针对于这个异常LockRetryPolicy会执行重试。
// ConnectionProxy.LockRetryPolicy
public <T> T execute(Callable<T> callable) throws Exception {
// autocommit=true时,在AbstractDMLBaseExecutor.executeAutoCommitTrue自己管理重试,不需要在这里重复执行重试
if (LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT && connection.getContext().isAutoCommitChanged()) {
return callable.call();
} else {
return doRetryOnLockConflict(callable);
}
}
protected <T> T doRetryOnLockConflict(Callable<T> callable) throws Exception {
LockRetryController lockRetryController = new LockRetryController();
while (true) {
try {
return callable.call();
} catch (LockConflictException lockConflict) {
onException(lockConflict);
if (connection.getContext().isAutoCommitChanged()
&& lockConflict.getCode() == TransactionExceptionCode.LockKeyConflictFailFast) {
lockConflict.setCode(TransactionExceptionCode.LockKeyConflict);
}
lockRetryController.sleep(lockConflict);
} catch (Exception e) {
onException(e);
throw e;
}
}
}
复制代码
LockRetryController控制获取全局锁的重试次数和重试间隔,默认情况下重试次数lockRetryTimes=30次,重试间隔(睡眠时间)lockRetryInterval=10ms。
public LockRetryController() {
this.lockRetryInterval = getLockRetryInterval();
this.lockRetryTimes = getLockRetryTimes();
}
public void sleep(Exception e) throws LockWaitTimeoutException {
if (--lockRetryTimes < 0 || (e instanceof LockConflictException
&& ((LockConflictException)e).getCode() == TransactionExceptionCode.LockKeyConflictFailFast)) {
throw new LockWaitTimeoutException("Global lock wait timeout", e);
}
try {
Thread.sleep(lockRetryInterval);
} catch (InterruptedException ignore) {
}
}
复制代码
RM一阶段提交总结
4、回滚
RM一阶段回滚逻辑比较简单:
- 目标Connection执行rollback;
- 如果分支事务注册成功,但是可能由于本地事务提交失败,向TC汇报一阶段提交失败(BranchReportRequest);
- 清空上下文;
// ConnectionProxy
public void rollback() throws SQLException {
targetConnection.rollback();
if (context.inGlobalTransaction() && context.isBranchRegistered()) {
report(false);
}
context.reset();
}
复制代码
四、RM二阶段
DefaultRMHandler负责处理二阶段TC发来的请求,主要包括提交和回滚。
public class DefaultRMHandler extends AbstractRMHandler {
protected static Map<BranchType, AbstractRMHandler> allRMHandlersMap = new ConcurrentHashMap<>();
}
复制代码
只考虑BranchType=AT模式,实际AbstractRMHandler实现类是RMHandlerAT。
1、分支事务提交
AbstractRMHandler使用模板方法(exceptionHandleTemplate)+callback(AbstractCallback)的方式处理分支事务提交请求。
public abstract class AbstractRMHandler extends AbstractExceptionHandler
implements RMInboundHandler, TransactionMessageHandler {
@Override
public BranchCommitResponse handle(BranchCommitRequest request) {
BranchCommitResponse response = new BranchCommitResponse();
exceptionHandleTemplate(new AbstractCallback<BranchCommitRequest, BranchCommitResponse>() {
@Override
public void execute(BranchCommitRequest request, BranchCommitResponse response)
throws TransactionException {
doBranchCommit(request, response);
}
}, request, response);
return response;
}
}
复制代码
模板方法exceptionHandleTemplate在基类AbstractExceptionHandler中。
// AbstractExceptionHandler
public <T extends AbstractTransactionRequest, S extends AbstractTransactionResponse> void exceptionHandleTemplate(Callback<T, S> callback, T request, S response) {
try {
callback.execute(request, response);
callback.onSuccess(request, response);
} catch (TransactionException tex) {
// 打印日志...
callback.onTransactionException(request, response, tex);
} catch (RuntimeException rex) {
LOGGER.error("Catch RuntimeException while do RPC, request: {}", request, rex);
callback.onException(request, response, rex);
}
}
复制代码
AbstractCallback实现了通用的onSuccess、onTransactionException、onException实现,子类一般只要实现execute执行方法。
public abstract static class AbstractCallback<T extends AbstractTransactionRequest, S extends AbstractTransactionResponse>
implements Callback<T, S> {
@Override
public void onSuccess(T request, S response) {
response.setResultCode(ResultCode.Success);
}
@Override
public void onTransactionException(T request, S response,
TransactionException tex) {
response.setTransactionExceptionCode(tex.getCode());
response.setResultCode(ResultCode.Failed);
response.setMsg("TransactionException[" + tex.getMessage() + "]");
}
@Override
public void onException(T request, S response, Exception rex) {
response.setResultCode(ResultCode.Failed);
response.setMsg("RuntimeException[" + rex.getMessage() + "]");
}
}
复制代码
AbstractRMHandler实际委托ResourceManager实现分支事务提交。
// AbstractRMHandler
protected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response)
throws TransactionException {
String xid = request.getXid();
long branchId = request.getBranchId();
String resourceId = request.getResourceId();
String applicationData = request.getApplicationData();
BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId,
applicationData);
response.setXid(xid);
response.setBranchId(branchId);
response.setBranchStatus(status);
}
复制代码
直接定位到AT模式下的ResourceManager。
// DataSourceManager
private final AsyncWorker asyncWorker = new AsyncWorker(this);
public BranchStatus branchCommit(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
return asyncWorker.branchCommit(xid, branchId, resourceId);
}
复制代码
AsyncWorker异步处理分支事务提交,将xid、branchId、resourceId封装为Phase2Context,放入阻塞队列,等待一个1秒执行一次的定时任务消费队列中的Phase2Context。
// AsyncWorker
// 分支事务提交任务队列
private final BlockingQueue<Phase2Context> commitQueue;
// 线程池
private final ScheduledExecutorService scheduledExecutor;
public AsyncWorker(DataSourceManager dataSourceManager) {
this.dataSourceManager = dataSourceManager;
commitQueue = new LinkedBlockingQueue<>(ASYNC_COMMIT_BUFFER_LIMIT);
ThreadFactory threadFactory = new NamedThreadFactory("AsyncWorker", 2, true);
scheduledExecutor = new ScheduledThreadPoolExecutor(2, threadFactory);
scheduledExecutor.scheduleAtFixedRate(this::doBranchCommitSafely, 10, 1000, TimeUnit.MILLISECONDS);
}
public BranchStatus branchCommit(String xid, long branchId, String resourceId) {
Phase2Context context = new Phase2Context(xid, branchId, resourceId);
addToCommitQueue(context);
return BranchStatus.PhaseTwo_Committed;
}
private void addToCommitQueue(Phase2Context context) {
if (commitQueue.offer(context)) {
return;
}
// 如果队列已满,主动协助消费
CompletableFuture.runAsync(this::doBranchCommitSafely, scheduledExecutor)
.thenRun(() -> addToCommitQueue(context));
}
复制代码
最终二阶段提交在RM侧就是异步删除UndoLog。
// AsyncWorker
private void deleteUndoLog(final Connection conn, UndoLogManager undoLogManager, List<Phase2Context> contexts) {
Set<String> xids = new LinkedHashSet<>(contexts.size());
Set<Long> branchIds = new LinkedHashSet<>(contexts.size());
contexts.forEach(context -> {
xids.add(context.xid);
branchIds.add(context.branchId);
});
try {
undoLogManager.batchDeleteUndoLog(xids, branchIds, conn);
if (!conn.getAutoCommit()) {
conn.commit();
}
} catch (SQLException e) {
LOGGER.error("Failed to batch delete undo log", e);
try {
conn.rollback();
addAllToCommitQueue(contexts);
} catch (SQLException rollbackEx) {
LOGGER.error("Failed to rollback JDBC resource after deleting undo log failed", rollbackEx);
}
}
}
复制代码
RM二阶段提交总结
2、分支事务回滚
AbstractRMHandler使用模板方法(exceptionHandleTemplate)+callback(AbstractCallback)的方式处理分支事务回滚请求,最终委派DataSourceManager处理。
// AbstractRMHandler
public BranchRollbackResponse handle(BranchRollbackRequest request) {
BranchRollbackResponse response = new BranchRollbackResponse();
exceptionHandleTemplate(new AbstractCallback<BranchRollbackRequest, BranchRollbackResponse>() {
@Override
public void execute(BranchRollbackRequest request, BranchRollbackResponse response)
throws TransactionException {
doBranchRollback(request, response);
}
}, request, response);
return response;
}
protected void doBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response)
throws TransactionException {
String xid = request.getXid();
long branchId = request.getBranchId();
String resourceId = request.getResourceId();
String applicationData = request.getApplicationData();
BranchStatus status = getResourceManager().branchRollback(request.getBranchType(), xid, branchId, resourceId,
applicationData);
response.setXid(xid);
response.setBranchId(branchId);
response.setBranchStatus(status);
}
// DataSourceManager
public BranchStatus branchRollback(BranchType branchType, String xid, long branchId, String resourceId,
String applicationData) throws TransactionException {
DataSourceProxy dataSourceProxy = get(resourceId);
if (dataSourceProxy == null) {
throw new ShouldNeverHappenException(String.format("resource: %s not found",resourceId));
}
try {
UndoLogManagerFactory.getUndoLogManager(dataSourceProxy.getDbType()).undo(dataSourceProxy, xid, branchId);
} catch (TransactionException te) {
if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) {
return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;
} else {
return BranchStatus.PhaseTwo_RollbackFailed_Retryable;
}
}
return BranchStatus.PhaseTwo_Rollbacked;
}
复制代码
RM二阶段回滚就是执行undoLog里的前置镜像,回滚一阶段的本地事务。具体逻辑都在AbstractUndoLogManager(MySQLUndoLogManager)中。
// AbstractUndoLogManager
public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException {
Connection conn = null;
ResultSet rs = null;
PreparedStatement selectPST = null;
boolean originalAutoCommit = true;
for (; ; ) {
try {
conn = dataSourceProxy.getPlainConnection();
if (originalAutoCommit = conn.getAutoCommit()) {
conn.setAutoCommit(false);
}
// Step1 根据xid+branchId查询undo_log
selectPST = conn.prepareStatement(SELECT_UNDO_LOG_SQL);
selectPST.setLong(1, branchId);
selectPST.setString(2, xid);
rs = selectPST.executeQuery();
boolean exists = false;
while (rs.next()) {
exists = true;
int state = rs.getInt(ClientTableColumnsName.UNDO_LOG_LOG_STATUS);
if (!canUndo(state)) {
return;
}
String contextString = rs.getString(ClientTableColumnsName.UNDO_LOG_CONTEXT);
Map<String, String> context = parseContext(contextString);
byte[] rollbackInfo = getRollbackInfo(rs);
String serializer = context == null ? null : context.get(UndoLogConstants.SERIALIZER_KEY);
UndoLogParser parser = serializer == null ? UndoLogParserFactory.getInstance()
: UndoLogParserFactory.getInstance(serializer);
BranchUndoLog branchUndoLog = parser.decode(rollbackInfo);
try {
setCurrentSerializer(parser.getName());
List<SQLUndoLog> sqlUndoLogs = branchUndoLog.getSqlUndoLogs();
if (sqlUndoLogs.size() > 1) {
Collections.reverse(sqlUndoLogs);
}
// Step2 回滚本地事务
for (SQLUndoLog sqlUndoLog : sqlUndoLogs) {
TableMeta tableMeta = TableMetaCacheFactory.getTableMetaCache(dataSourceProxy.getDbType()).getTableMeta(
conn, sqlUndoLog.getTableName(), dataSourceProxy.getResourceId());
sqlUndoLog.setTableMeta(tableMeta);
AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(
dataSourceProxy.getDbType(), sqlUndoLog);
undoExecutor.executeOn(conn);
}
} finally {
removeCurrentSerializer();
}
}
// See https://github.com/seata/seata/issues/489
// Step3 undo_log处理
if (exists) {
// RM一阶段正常提交,删除undoLog
deleteUndoLog(xid, branchId, conn);
conn.commit();
} else {
// RM一阶段提交失败,TC由于全局事务超时发起回滚
// 此时RM的本地事务可能提交,这里插入一个undo_log来防止RM提交事务成功导致数据不一致
insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn);
conn.commit();
}
return;
} catch (SQLIntegrityConstraintViolationException e) {
// insertUndoLogWithGlobalFinished 如果插入undo_log由于唯一约束失败,那么执行重试回滚
// 日志...
} catch (Throwable e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException rollbackEx) {
LOGGER.warn("Failed to close JDBC resource while undo ... ", rollbackEx);
}
}
throw new BranchTransactionException(BranchRollbackFailed_Retriable, String
.format("Branch session rollback failed and try again later xid = %s branchId = %s %s", xid,
branchId, e.getMessage()), e);
} finally {
// 关闭资源
}
}
}
复制代码
二阶段回滚RM本地事务分为三步:
- 查询分支事务对应undoLog,select from undo_log where xid = ? and branch_id = ? for update;
- AbstractUndoExecutor利用undoLog中的前置镜像回滚本地事务;
- 处理undoLog;
重点看2、3两步。
回滚一阶段事务
第二步:根据undoLog回滚一阶段本地事务。
// AbstractUndoExecutor
public void executeOn(Connection conn) throws SQLException {
// Step1 比较当前数据和前后镜像,如果情况允许才会回滚
if (IS_UNDO_DATA_VALIDATION_ENABLE && !dataValidationAndGoOn(conn)) {
return;
}
PreparedStatement undoPST = null;
try {
// Step2 构建回滚sql 如 UPDATE a SET x = ? WHERE pk1 in (?)
String undoSQL = buildUndoSQL();
undoPST = conn.prepareStatement(undoSQL);
TableRecords undoRows = getUndoRows();
// 根据主键回滚前置镜像中每一行数据
for (Row undoRow : undoRows.getRows()) {
ArrayList<Field> undoValues = new ArrayList<>();
List<Field> pkValueList = getOrderedPkList(undoRows, undoRow, getDbType(conn));
for (Field field : undoRow.getFields()) {
if (field.getKeyType() != KeyType.PRIMARY_KEY) {
undoValues.add(field);
}
}
// Step3 替换占位符
undoPrepare(undoPST, undoValues, pkValueList);
// Step4 根据id回滚一行
undoPST.executeUpdate();
}
} catch (Exception ex) {
if (ex instanceof SQLException) {
throw (SQLException) ex;
} else {
throw new SQLException(ex);
}
}
finally {
//important for oracle
IOUtil.close(undoPST);
}
}
复制代码
在执行回滚之前,dataValidationAndGoOn方法会比较当前数据和前后镜像,如果情况允许才会回滚。
protected boolean dataValidationAndGoOn(Connection conn) throws SQLException {
TableRecords beforeRecords = sqlUndoLog.getBeforeImage();
TableRecords afterRecords = sqlUndoLog.getAfterImage();
// case1 前后镜像一致,不需要回滚,返回false
Result<Boolean> beforeEqualsAfterResult = DataCompareUtils.isRecordsEquals(beforeRecords, afterRecords);
if (beforeEqualsAfterResult.getResult()) {
return false;
}
// select for update by 主键 获取当前快照数据
TableRecords currentRecords = queryCurrentRecords(conn);
// case2 当前快照数据和后置镜像不一致
Result<Boolean> afterEqualsCurrentResult = DataCompareUtils.isRecordsEquals(afterRecords, currentRecords);
if (!afterEqualsCurrentResult.getResult()) {
// case2-1 如果前置镜像与当前快照一致,也不需要回滚,返回false
Result<Boolean> beforeEqualsCurrentResult = DataCompareUtils.isRecordsEquals(beforeRecords, currentRecords);
if (beforeEqualsCurrentResult.getResult()) {
return false;
} else {
// case2-2 当前快照数据和后置镜像不一致,抛出异常,有脏数据
throw new SQLException("Has dirty records when undo.");
}
}
// case3 当前快照数据与后置镜像一致,可以执行回滚
return true;
}
复制代码
处理UndoLog
如果RM一阶段commit成功,undo_log存在,即exists=true,只要删除undo_log即可,与回滚事务一起提交;
如果RM一阶段未commit成功,undo_log不存在,可能由于RM一阶段业务处理超时,导致TC判断全局事务超时发起二阶段回滚,此时RM一阶段事务提交和RM二阶段回滚可能同时发生。
为了确保数据一致性,利用undo_log的xid和branch_id的唯一约束,插入一条status=GlobalFinished的数据来解决这个问题。
如果RM一阶段事务提交成功,即一阶段提交插入status=Normal的undo_log成功,那么在二阶段回滚时insertUndoLogWithGlobalFinished会抛出SQLIntegrityConstraintViolationException唯一约束冲突,AbstractUndoLogManager.undo重试二阶段回滚本地事务,最终会回滚成功;
如果二阶段回滚插入status=GlobalFinished的undo_log成功,那么RM一阶段就不会提交成功(发生唯一约束冲突),从而解决数据一致性问题。
// AbstractUndoLogManager
public void undo(DataSourceProxy dataSourceProxy, String xid, long branchId) throws TransactionException {
for (; ; ) {
try {
// ...
// See https://github.com/seata/seata/issues/489
// Step3 undo_log处理
if (exists) {
// RM一阶段正常提交,删除undoLog
deleteUndoLog(xid, branchId, conn);
conn.commit();
} else {
// RM一阶段超时,TC由于全局事务超时发起回滚
// 此时RM的本地事务可能同时提交,这里插入一个undo_log来防止RM提交事务成功导致数据不一致
insertUndoLogWithGlobalFinished(xid, branchId, UndoLogParserFactory.getInstance(), conn);
conn.commit();
}
return;
} catch (SQLIntegrityConstraintViolationException e) {
// insertUndoLogWithGlobalFinished 如果插入undo_log由于唯一约束失败,那么执行重试回滚
// 日志...
}
// ...
}
}
// MySQLUndoLogManager
protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException {
insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), State.GlobalFinished, conn);
}
复制代码
RM二阶段回滚总结
五、GlobalLock的作用
AT模式下,全局事务的隔离性是读未提交,即RM一阶段写入的数据,在二阶段提交前就能被其他session读到。
为了解决脏读和脏写的问题,Seata提供了GlobalLock注解。
如果客户端要避免脏读,在业务方法上标注GlobalLock走Seata代理逻辑,此外使用select for update作为查询语句走Seata代理数据源;
如果客户端要避免脏写,在业务方法上标注GlobalLock走Seata代理逻辑,所有update、insert、delete都会走Seata代理数据源。
在@GlobalLock注解方法执行前,GlobalTransactionalInterceptor拦截并在上下文中标注启用GlobalLock(RootContext.bindGlobalLockFlag),然后执行业务方法。
// GlobalTransactionalInterceptor
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
Class<?> targetClass =
methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
final GlobalTransactional globalTransactionalAnnotation =
getAnnotation(method, targetClass, GlobalTransactional.class);
final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
boolean localDisable = disable || (degradeCheck && degradeNum >= degradeCheckAllowTimes);
if (!localDisable) {
if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {
// GlobalTransactional...
} else if (globalLockAnnotation != null) {
return handleGlobalLock(methodInvocation, globalLockAnnotation);
}
}
}
return methodInvocation.proceed();
}
private Object handleGlobalLock(final MethodInvocation methodInvocation, final GlobalLock globalLockAnno) throws Throwable {
return globalLockTemplate.execute(new GlobalLockExecutor() {
@Override
public Object execute() throws Throwable {
return methodInvocation.proceed();
}
@Override
public GlobalLockConfig getGlobalLockConfig() {
GlobalLockConfig config = new GlobalLockConfig();
config.setLockRetryInterval(globalLockAnno.lockRetryInterval());
config.setLockRetryTimes(globalLockAnno.lockRetryTimes());
return config;
}
});
}
public class GlobalLockTemplate {
public Object execute(GlobalLockExecutor executor) throws Throwable {
boolean alreadyInGlobalLock = RootContext.requireGlobalLock();
if (!alreadyInGlobalLock) {
// 设置启用GlobalLock标志
RootContext.bindGlobalLockFlag();
}
GlobalLockConfig myConfig = executor.getGlobalLockConfig();
// 绑定GlobalLock配置
GlobalLockConfig previousConfig = GlobalLockConfigHolder.setAndReturnPrevious(myConfig);
try {
// 执行业务方法
return executor.execute();
} finally {
if (!alreadyInGlobalLock) {
RootContext.unbindGlobalLockFlag();
}
if (previousConfig != null) {
GlobalLockConfigHolder.setAndReturnPrevious(previousConfig);
} else {
GlobalLockConfigHolder.remove();
}
}
}
}
复制代码
对于脏读场景,客户端业务sql要使用select for update语句,seata会代理这个查询sql,通过SelectForUpdateExecutor执行:
- 设置连接非自动提交
- 执行业务select for update语句,获得本地锁
- 查询业务sql涉及的所有id主键列,构建lockKeys
- 发送GlobalLockQueryRequest给TC,如果TC发现资源+主键id被其他全局事务锁住(lock_table),这里会抛出异常
// SelectForUpdateExecutor
public T doExecute(Object... args) throws Throwable {
Connection conn = statementProxy.getConnection();
DatabaseMetaData dbmd = conn.getMetaData();
T rs;
Savepoint sp = null;
boolean originalAutoCommit = conn.getAutoCommit();
try {
// 1. 设置非自动提交
if (originalAutoCommit) {
conn.setAutoCommit(false);
} else if (dbmd.supportsSavepoints()) {
sp = conn.setSavepoint();
} else {
throw new SQLException("not support savepoint. please check your db version");
}
LockRetryController lockRetryController = new LockRetryController();
ArrayList<List<Object>> paramAppenderList = new ArrayList<>();
String selectPKSQL = buildSelectSQL(paramAppenderList);
while (true) {
try {
// 2. 执行业务查询方法 select for update 获取本地锁
rs = statementCallback.execute(statementProxy.getTargetStatement(), args);
// 3. 查询需要获取全局锁的主键id集合
TableRecords selectPKRows = buildTableRecords(getTableMeta(), selectPKSQL, paramAppenderList);
String lockKeys = buildLockKey(selectPKRows);
if (StringUtils.isNullOrEmpty(lockKeys)) {
break;
}
// 4. 发送GlobalLockQueryRequest给TC,如果抛出异常则代表结果集有未提交数据
if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) {
statementProxy.getConnectionProxy().checkLock(lockKeys);
} else {
throw new RuntimeException("Unknown situation!");
}
break;
} catch (LockConflictException lce) {
if (sp != null) {
conn.rollback(sp);
} else {
conn.rollback();
}
lockRetryController.sleep(lce);
}
}
} finally {
// ...
}
return rs;
}
复制代码
对于脏写场景,发送GlobalLockQueryRequest发生在commit阶段,在这之前已经在例如UpdateExecutor中获取了本地锁。
private void doCommit() throws SQLException {
if (context.inGlobalTransaction()) {
processGlobalTransactionCommit();
} else if (context.isGlobalLockRequire()) {
processLocalCommitWithGlobalLocks();
} else {
targetConnection.commit();
}
}
private void processLocalCommitWithGlobalLocks() throws SQLException {
// 发送GlobalLockQueryRequest给TC,如果抛出异常则代表结果集有未提交数据
checkLock(context.buildLockKeys());
try {
targetConnection.commit();
} catch (Throwable ex) {
throw new SQLException(ex);
}
context.reset();
}
复制代码
GlobalLockQueryRequest继承BranchRegisterRequest,没有其他属性。
public class GlobalLockQueryRequest extends BranchRegisterRequest {
}
复制代码
请求体只包含三个属性,xid全局事务id在仅有GlobalLock的场景下为空。
// DataSourceManager
public boolean lockQuery(BranchType branchType, String resourceId, String xid, String lockKeys) throws TransactionException {
GlobalLockQueryRequest request = new GlobalLockQueryRequest();
request.setXid(xid);
request.setLockKey(lockKeys);
request.setResourceId(resourceId);
try {
GlobalLockQueryResponse response;
if (RootContext.inGlobalTransaction() || RootContext.requireGlobalLock()) {
response = (GlobalLockQueryResponse) RmNettyRemotingClient.getInstance().sendSyncRequest(request);
} else {
throw new RuntimeException("unknow situation!");
}
if (response.getResultCode() == ResultCode.Failed) {
throw new TransactionException(response.getTransactionExceptionCode(),
"Response[" + response.getMsg() + "]");
}
return response.isLockable();
} catch (TimeoutException toe) {
throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", toe);
} catch (RuntimeException rex) {
throw new RmTransactionException(TransactionExceptionCode.LockableCheckFailed, "Runtime", rex);
}
}
复制代码
如果TC用db存储,底层和RM一阶段获取全局锁差不多。执行select xid, transaction_id, branch_id, resource_id, table_name, pk, row_key, gmt_create, gmt_modified,status from lock_table where row_key in ( ? ) order by status desc,如果结果xid与当前全局事务不一致,则返回isLockable=false,代表锁被其他全局事务争用,GlobalLock作用的数据对于客户端不可见,实现全局事务读已提交隔离级别。