Seata源码(二)AT模式下的客户端

前言

本章分析AT模式下的Seata客户端(1.5.0),包括RM和TM:

  1. 客户端启动:GlobalTransactional代理、DataSource代理;
  2. TM:全局事务开启、提交、回滚,事务传播,全局事务异常异常处理等;
  3. RM:RM一阶段,RM二阶段(提交、回滚);
  4. GlobalLock处理脏读脏写;

一、客户端启动

2022-06-17-16-26-44-image.png

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的几个方法:

  1. execute:执行实际业务,即执行目标对象的实际方法;
  2. name:获取当前全局事务的名字,默认取方法名;
  3. 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上做文章。

2022-07-01-17-14-22-image.png

这里重点关注三个点: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模板分为三步:

  1. 解析sql:默认情况下利用com.alibaba.druid.sql.SQLUtils#parseStatements解析sql为SQLRecognizer;
  2. 根据sql类型选择执行器Executor;
  3. 执行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在构造时注入了三个对象:

  1. statementProxy:PrepareStatementProxy实例;
  2. statementCallback:实际prepareStatement执行业务sql的callback函数,在PreparedStatementProxy.executeUpdate时传入;
  3. 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主要分为四步:

  1. 构建前置镜像beforeImage
  2. 执行sql
  3. 构建后置镜像afterImage
  4. 构建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总结

2022-07-05-16-55-42-image.png

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提交事务的核心方法,一共分为以下几步:

  1. 向TC注册分支事务,获取全局锁,如果获取全局锁失败,抛出LockConflictException异常,其他情况抛出SQLException;
  2. 写入undolog,之前在执行sql的时候保存到ConnectionProxy的undoLog写入db;
  3. 提交本地事务,将业务DML和undolog一起提交;
  4. 如果本地提交失败,向TC发送BranchReportRequest,并表明一阶段提交失败;
  5. 如果本地提交成功,默认情况下就直接结束返回;
// 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包含五个属性:

  1. xid:全局事务id,由TM向TC开启全局事务得到;
  2. lockKey:全局锁的key集合,如:dml01锁storage_tbl:8,9、dml02锁storage_tbl:1,2,最终lockKey是storage_tbl:8,9;storage_tbl:1,2;
  3. resourceId:代表当前rm数据源,如:jdbc:mysql://127.0.0.1:3306/db01;
  4. branchType:AT;
  5. 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一阶段提交总结

2022-07-05-17-16-57-image.png

4、回滚

RM一阶段回滚逻辑比较简单:

  1. 目标Connection执行rollback;
  2. 如果分支事务注册成功,但是可能由于本地事务提交失败,向TC汇报一阶段提交失败(BranchReportRequest);
  3. 清空上下文;
// 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二阶段提交总结

2022-07-07-11-34-51-image.png

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本地事务分为三步:

  1. 查询分支事务对应undoLog,select from undo_log where xid = ? and branch_id = ? for update;
  2. AbstractUndoExecutor利用undoLog中的前置镜像回滚本地事务;
  3. 处理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二阶段回滚总结

2022-07-07-13-32-05-image.png

五、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执行:

  1. 设置连接非自动提交
  2. 执行业务select for update语句,获得本地锁
  3. 查询业务sql涉及的所有id主键列,构建lockKeys
  4. 发送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作用的数据对于客户端不可见,实现全局事务读已提交隔离级别。

猜你喜欢

转载自juejin.im/post/7130817264789487646