[Distributed transaction] The core logic of Seata's @GlobalTransactional on the TM side

I. Overview

Seata relies on Spring's annotation mechanism to implement declarative transactions, that is, developers use @GlobalTransactional annotations for beans, and Seata rewrites the three stages of the life cycle of such beans through GlobalTransactionScanner to complete distributed transaction capabilities. These three steps are:

  1. Bean initialization phase (`afterPropertiesSet()``)

    • Initialize TM RM client and establish a long connection with TC
  2. Bean post-initialization phase (`wrapIfNecessary()``)

    • wrapIfNecessary As the name implies, if necessary, wrap it up; so if there is an annotation @GlobalTransactional on the method of the Bean (@GlobalLock, @TwoPhaseBusinessAction annotation is not mentioned in this article), then generate a proxy class for this type of Bean, The ability to implement the TM role in distributed transactions in the proxy logic of the target method
  3. Bean destruction phase (ShutdownHook)

    • Close the TM and RM clients when the Bean is destroyed

The initialization of RM and RM clients in this article will not be said for now, and will be mentioned later in combination with the registration center and reconnection mechanism. Part of the logic related to Netty is described in "Seata High-performance RPC Communication - Using Reactor Mode Skillfully" mention

2. Core logic of @GlobalTransactional

1) When GlobalTransactionScanner#wrapIfNecessary scans the spring bean, judge whether there is a @GlobalTransactional annotation (@GlobalLock, not mentioned here). After identifying the @GlobalTransactional annotation on the method, add the AOP interceptor GlobalTransactionalInterceptor to the bean. When the method in TCC mode is modified by @TwoPhaseBusinessAction, the corresponding Advice is TccActionInterceptor

2) When the target method is called, it first enters the GlobalTransactionalInterceptor#invoke. In this method, it first judges whether to disable the distributed transaction capability at runtime (1. Dynamically configure and close the transaction, 2. Degrade the transaction capability due to Seata exception) ; If it is still enabled, the subsequent logic is handed over to handleGlobalTransaction to complete.

3) The key logic in handleGlobalTransaction is 2 steps. The first step is to execute the global transaction through GlobalTransactionalInterceptor#transactionalTemplate; the second step is to deal with the abnormal result of the first step. In the second part, there will be a failureHandler call. The callback knows where the transaction went wrong.

4) What is defined in GlobalTransactionalInterceptor#transactionalTemplate is that the TM initiator executes the core logic of opening the global transaction, committing or rolling back the global transaction. When these methods are executed, they will judge that if the role is a TM participant, they will not do anything. There may be an execution chain There are multiple methods in the route decorated with @GlobalTransaction. The first @GlobalTransaction in the call link is the TM initiator, and the rest are TM participants. The key logic in this method is as follows:

  • Obtain the current global transaction object GlobalTransaction from the context (it is possible that there may be multiple methods in one execution link modified by @GlobalTransaction)
  • According to the value of propagation in the @GlobalTransactional annotation and the current situation of the global transaction object, decide the transaction propagation strategy, refer to the Spring document to understand the transaction propagation behavior
  • If the current global transaction object is empty, create a new global transaction object with the role of TM initiator
  • Complete the global transaction: start the global transaction, execute the business logic (in the AT mode, the execution of each branch transaction is in it), commit or roll back the global transaction.

3. Interpretation of @GlobalTransactional core source code

1) When GlobalTransactionScanner#wrapIfNecessary scans a spring bean, determine whether there is a @GlobalTransactional annotation on the method, and if so, add an interceptor GlobalTransactionalInterceptor to the bean, that is to say, after being marked by @GlobalTransactional and @GlobalLock, Seata enhances the distribution provided by AOP Type transaction capability in GlobalTransactionalInterceptor

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    
    
       // ... TCC 部分暂略

            Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
            Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);

            // 判断类或方法上是否有@GlobalTransactional 注解
            // 判断方法上有否有 @GlobalLock 注解
            if (!existsAnnotation(new Class[]{
    
    serviceInterface})
                && !existsAnnotation(interfacesIfJdk)) {
    
    
                return bean;
            }

            if (globalTransactionalInterceptor == null) {
    
    
                // 构建AOP的拦截器 GlobalTransactionalInterceptor
                globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
                // 运行时监听是否禁用分布式事务,如果禁用,那么拦截器中就不再使用分布式事务的能力
                ConfigurationCache.addConfigListener(
                        ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                        (ConfigurationChangeListener)globalTransactionalInterceptor);
            }
            // 下方getAdvicesAndAdvisorsForBean 方法中,就返回这个interceptor,
            // 也就是说被 @GlobalTransactional 和 @GlobalLock 标注后,Seata通过AOP增强提供的分布式事务能力在 GlobalTransactionalInterceptor中
            interceptor = globalTransactionalInterceptor;
        }

        LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
        // 如果是普通的bean,走父类的方法生成代理类即可
        if (!AopUtils.isAopProxy(bean)) {
    
    
            bean = super.wrapIfNecessary(bean, beanName, cacheKey);
        } else {
    
    
            // 如果已经是代理类,获取到advisor后,添加到该集合即可
            AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
            // 根据上面的interceptor生成advisor
            Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
            int pos;
            for (Advisor avr : advisor) {
    
    
                // Find the position based on the advisor's order, and add to advisors by pos
                pos = findAddSeataAdvisorPosition(advised, avr);
                advised.addAdvisor(pos, avr);
            }
        }
        PROXYED_SET.add(beanName);
        return bean;
    }
} catch (Exception exx) {
    
    
    throw new RuntimeException(exx);
}

2) In the invoke method of the interceptor GlobalTransactionalInterceptor, if it is judged that the distributed transaction capability is not disabled, the method marked with @GlobalTransactional is handed over to handleGlobalTransaction(xxx) for processing

public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
    
    
    //通过 methodInvocation.getThis() 获取当前方法调用的所属对象
    //通过 AopUtils.getTargetClass(xx) 获取当前对象的Class
    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)) {
    
    
        // BridgeMethodResolver.findBridgedMethod https://cloud.tencent.com/developer/article/1656258
        final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);
        // 获取目标方法上 @GlobalTransactional 的信息
        final GlobalTransactional globalTransactionalAnnotation =
            getAnnotation(method, targetClass, GlobalTransactional.class);
        // 获取目标方法上 @GlobalLock 的信息,@GlobalTransactional 和 @GlobalLock 不该同时存在
        // @GlobalTransactional 是开启全局事务
        // @GlobalLock 是按照全局事务的隔离级别查看数据
        final GlobalLock globalLockAnnotation = getAnnotation(method, targetClass, GlobalLock.class);
        // 禁用了,或者 开启了分布式事务能力降级,并且触发了降级的阈值
        boolean localDisable = disable || (ATOMIC_DEGRADE_CHECK.get() && degradeNum >= degradeCheckAllowTimes);
        if (!localDisable) {
    
    
            if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {
    
    
                AspectTransactional transactional;
                if (globalTransactionalAnnotation != null) {
    
    
                    // 通过 @GlobalTransactional的信息构建 全局事务的核心配置
                    transactional = new AspectTransactional(globalTransactionalAnnotation.timeoutMills(),
                        globalTransactionalAnnotation.name(), globalTransactionalAnnotation.rollbackFor(),
                        globalTransactionalAnnotation.rollbackForClassName(),
                        globalTransactionalAnnotation.noRollbackFor(),
                        globalTransactionalAnnotation.noRollbackForClassName(),
                        globalTransactionalAnnotation.propagation(),
                        globalTransactionalAnnotation.lockRetryInterval(),
                        globalTransactionalAnnotation.lockRetryTimes(),
                        globalTransactionalAnnotation.lockStrategyMode());
                } else {
    
    
                    transactional = this.aspectTransactional;
                }
                // 处理全局事务
                return handleGlobalTransaction(methodInvocation, transactional);
            } else if (globalLockAnnotation != null) {
    
    
                // 处理全局锁
                return handleGlobalLock(methodInvocation, globalLockAnnotation);
            }
        }
    }
    return methodInvocation.proceed();
}

3) The key logic in handleGlobalTransaction is 2 steps. The first step is to execute the global transaction through GlobalTransactionalInterceptor#transactionalTemplate; the second step is to deal with the abnormal result of the first step, and the failureHandler will be called in the second part. The callback knows where the transaction went wrong. Look at the logic of step 2 first, because it is relatively clear, the type of exception to be captured is: TransactionalExecutor.ExecutionException, according to the value of different codes in the exception, do different processing (failureHandler callback methods are different, developers perceive through this callback What exception happened to the transaction. Even though Seata will catch the exception, after handling the exception related to the transaction based on the exception information, the original exception will still be thrown up, allowing the developer to still handle it as if it were not connected to the transaction.

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();
      // code不同,处理逻辑不通
      switch (code) {
    
    
          // 遇到异常,但TM正常完成了TM回滚,将原始异常抛出,业务逻辑仍只关注原始异常,知道异常时事务回滚了,但无需操心回滚的细节
          // 对应TC侧GlobalStatus状态为 Rollbacked (11)
          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();
          // 回滚失败了
          // 对应TC侧GlobalStatus状态有 RollbackFailed  TimeoutRollbackFailed RollbackRetryTimeout
          case RollbackFailure:
              failureHandler.onRollbackFailure(e.getTransaction(), e.getOriginalException());
              throw e.getOriginalException();
          // 回滚重试中
          // 对应TC侧GlobalStatus状态有 Rollbacking  RollbackRetrying RollbackRetryTimeout
          case RollbackRetrying:
              failureHandler.onRollbackRetrying(e.getTransaction(), e.getOriginalException());
              throw e.getOriginalException();
          // 因超时而回滚了
          // 对应TC侧GlobalStatus状态有 TimeoutRollbacking  TimeoutRollbackRetrying TimeoutRollbacked
          case TimeoutRollback:
              failureHandler.onTimeoutRollback(e.getTransaction(), e.getOriginalException());
              throw e.getCause();
          default:
              throw new ShouldNeverHappenException(String.format("Unknown TransactionalExecutor.Code: %s", code));
      }
  } finally {
    
    
      if (ATOMIC_DEGRADE_CHECK.get()) {
    
    
          EVENT_BUS.post(new DegradeCheckEvent(succeed));
      }
  }
}

The error handling part about rollback is a bit complicated. The following figure lists the TransactionalExecutor.Code corresponding to the GlobalStatus rollback status used by the TC side on the TM side of the client side during rollback. Through this mapping relationship, the logic of the server side is sorted out. , it will be easier to understand.
insert image description here

4) The first step in handleGlobalTransaction is to execute the global transaction through GlobalTransactionalInterceptor#transactionalTemplate.
insert image description here

methodInvocation.proceed() in its execute() method is a business logic method. Before and after the execution of this business logic method, the transaction management capability of TM is added. What is defined in this class is that the TM initiator executes the core logic of opening the global transaction, committing or rolling back the global transaction. When these methods are executed, it will judge what to do if the role is a TM participant

  1. Obtain the current global transaction object GlobalTransaction from the context (there may be multiple methods in one execution link modified by @GlobalTransaction)
  2. According to the value of propagation in the @GlobalTransactional annotation and the current situation of the global transaction object, decide the transaction propagation strategy, refer to the Spring document to understand the transaction propagation behavior
  3. If the current global transaction object is empty, create a new global transaction object with the role of TM initiator
  4. Complete the global transaction: start the global transaction, execute the business logic (in the AT mode, the execution of each branch transaction is in it), commit or roll back the global transaction.
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'.
    /**
     * 从上下文中,获取当前事务对象
     * 1.当前事务getcurrent为空:则当前是事务的发起者TM(Launcher)
     * 2.当前事务getCurrent不为空:则当前是事务参与者;
     *      事务嵌套的情况下(如:A和B两个方法都标注了@GlobalTransactional,A方法中会调用B方法),
     *      对于A来说是TM,而对于B来说,因为全局事务A不为空,那么B就是参与者(GlobalTransactionRole.Participant)
     *      那么此处返回的GlobalTransaction中xid是事务A的xid,B的角色是GlobalTransactionRole.Participant
     */
    GlobalTransaction tx = GlobalTransactionContext.getCurrent();

    // 1.2 Handle the transaction propagation.
    // 下面是处理事务的传播特性,如果没有指定,默认是REQUIRED
    // REQUIRED:如果本来有事务,则加入该事务,如果没有事务,则创建新的事务
    Propagation propagation = txInfo.getPropagation();
    SuspendedResourcesHolder suspendedResourcesHolder = null;
    try {
    
    
        //事务的传播机制, 根据不同的传播行为,执行不同的逻辑
        switch (propagation) {
    
    
            case NOT_SUPPORTED:
                // If transaction is existing, suspend it.
                if (existingTransaction(tx)) {
    
    
                    suspendedResourcesHolder = tx.suspend();
                }
                // Execute without transaction and return.
                return business.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;
            case SUPPORTS:
                // If transaction is not existing, execute without transaction.
                if (notExistingTransaction(tx)) {
    
    
                    return business.execute();
                }
                // Continue and execute with new transaction
                break;
            case REQUIRED:
                // If current transaction is existing, execute with current transaction,
                // else continue and execute with new transaction.
                break;
            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;
            default:
                throw new TransactionException("Not Supported Propagation:" + propagation);
        }

        // 1.3 If null, create new transaction with role 'GlobalTransactionRole.Launcher'.
        // 如果tx为空,表示是事务的发起者TM,则创建一个角色为Launcher的GlobalTransaction
        if (tx == null) {
    
    
            tx = GlobalTransactionContext.createNew();
        }

        // set current tx config to holder
        // 应对事物嵌套的场景,Participant的某些配置覆盖Launcher的配置,
        // 待participant的事务处理完毕后,仍需要恢复Launcher中的配置
        GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);

        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.
            // 开启事务,如果是TM 发起者(GlobalTransactionRole.Launcher),那么才会向TC发送开启全局事务的RPC请求
            // 开启事务是在,DefaultTransactionManager.begin方法中向TC同步发送 GlobalBeginRequest
            // 但如果是参与者GlobalTransactionRole.Participant,则不向TC发请求,仅承担分支事务的职责,但需注意hooks仍是被调用的
            // TC 端收到请求,开启全局事务成功后生成并返回一个全局唯一的 XID。
            // TM 将 XID 保存到 ThreadLocal 中,后续RPC调用中这个XID也会被透传
            beginTransaction(txInfo, tx);

            Object rs;
            try {
    
    
                // Do Your Business
                // 执行业务方法,如果有RPC调用,则发起RPC调用时携带上xid
                rs = business.execute();
            } catch (Throwable ex) {
    
    
                // 3. The needed business exception to rollback.
                // 异常时,如匹配到异常规则才执行回滚;
                // 否则内部还是执行事务提交,需注意即使事务提交成功了,接下来还有异常抛出
                // 也就是说Seata关注异常,根据异常信息来抉择分布式事务该你怎么处理,但并不会因为自己对异常处理了就把异常吞掉。
                // 回滚请求是在DefaultTransactionManager.rollback 中向TC同步发送 GlobalRollbackRequest,
                // 发送回滚请求有重试机制,默认5次,可通过 client.tm.rollbackRetryCount 调整
                completeTransactionAfterThrowing(txInfo, tx, ex);
                throw ex;
            }

            // 4. everything is fine, commit.
            // 提交事务
            // 但如果如果检测到已超时,则执行回滚事务
            // 若未超时,才执行事务提交
            // 提交请求是在DefaultTransactionManager.commit 中向TC同步发送 GlobalCommitRequest,有重试机制
            // 发送提交请求有重试机制,默认5次,可通过 client.tm.commitRetryCount 调整
            commitTransaction(tx, txInfo);

            return rs;
        } finally {
    
    
            //5. clear
            // 恢复原事务配置,触发hook回执,清理hook
            resumeGlobalLockConfig(previousConfig);
            // 触发 afterHook
            triggerAfterCompletion();
            // 清除hook,
            cleanUp();
        }
    } finally {
    
    
        // If the transaction is suspended, resume it.
        // 如果有被挂起的事务,这里将其恢复
        if (suspendedResourcesHolder != null) {
    
    
            tx.resume(suspendedResourcesHolder);
        }
    }
}

In TransactionalTemplate#execute, there are hook callbacks when starting a transaction, committing or rolling back a transaction, but from the 1.6.1 version of the code, there is no place to register the hook. Why is TransactionHookManager#registerHook useless?

Fourth, the start and stop of business capabilities

GlobalTransactionalInterceptor#invoke has a very critical line of code, which is used to determine whether the current service call is TM using distributed transaction capabilities (open transaction + commit | rollback transaction), if not, only execute the original business logic.

boolean localDisable = disable || (ATOMIC_DEGRADE_CHECK.get() && degradeNum >= degradeCheckAllowTimes);
  1. disable is used to control whether to disable distributed transaction capabilities at startup and runtime

    • The default value is false, corresponding to the configuration key: service.disableGlobalTransaction, which can be changed dynamically in the configuration center
  2. (ATOMIC_DEGRADE_CHECK.get() && degradeNum >= degradeCheckAllowTimes) Its function is that if the degrade detection is enabled and the number of consecutive transaction failures reaches the threshold, the transaction capability will be automatically disabled first; at the same time, there is also a scheduled task that will be intermittently enabled by simulating global transactions +Try by means of submission, when the number of consecutive successful trials reaches the threshold, the transaction capability is automatically activated.

    • ATOMIC_DEGRADE_CHECK Configure whether to enable transaction degradation check through client.tm.degradeCheck, the default is false, do not enable transaction degradation check
    • The following two configurations are meaningful only if the transaction downgrade check is enabled;
    • degradeCheckAllowTimes specifies the number of degrade checks allowed, specified by client.tm.degradeCheckAllowTimes,
    • degradeCheckPeriod specifies the frequency of the simulation test, which is specified by client.tm.degradeCheckPeriod. This configuration is meaningful only when the transaction degrade check is enabled;

Runtime switch changes

1) Monitor the changes of ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION
In GlobalTransactionScanner#wrapIfNecessary, when creating globalTransactionalInterceptor, monitor the changes of ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.

if (globalTransactionalInterceptor == null) {
    
    
    // 构建AOP的拦截器 GlobalTransactionalInterceptor
    globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
    // 运行时监听是否禁用分布式事务,如果禁用,那么拦截器中就不再使用分布式事务的能力
    ConfigurationCache.addConfigListener(
            ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
            (ConfigurationChangeListener)globalTransactionalInterceptor);
}

2) Monitor the changes of ConfigurationKeys.CLIENT_DEGRADE_CHECK
In the constructor of GlobalTransactionalInterceptor, read the configuration, initialize each key configuration, and add a listener to monitor the changes of ConfigurationKeys.CLIENT_DEGRADE_CHECK

public GlobalTransactionalInterceptor(FailureHandler failureHandler) {
    
    
    this.failureHandler = failureHandler == null ? DEFAULT_FAIL_HANDLER : failureHandler;
    // 初始化 disable 的值,读取配置service.disableGlobalTransaction
    this.disable = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
        DEFAULT_DISABLE_GLOBAL_TRANSACTION);
    this.order =
        ConfigurationFactory.getInstance().getInt(ConfigurationKeys.TM_INTERCEPTOR_ORDER, TM_INTERCEPTOR_ORDER);
    // 需要注意,degradeCheckPeriod 和 degradeCheckAllowTimes 在首次启动后读取配置后赋值,之后不再感知变更。
    boolean degradeCheck = ConfigurationFactory.getInstance().getBoolean(ConfigurationKeys.CLIENT_DEGRADE_CHECK,
        DEFAULT_TM_DEGRADE_CHECK);
    degradeCheckPeriod = ConfigurationFactory.getInstance()
            .getInt(ConfigurationKeys.CLIENT_DEGRADE_CHECK_PERIOD, DEFAULT_TM_DEGRADE_CHECK_PERIOD);
    degradeCheckAllowTimes = ConfigurationFactory.getInstance()
            .getInt(ConfigurationKeys.CLIENT_DEGRADE_CHECK_ALLOW_TIMES, DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES);
    // 通过 GuavaEventBus 来监听事务成功还是失败,以调整统计计数
    EVENT_BUS.register(this);
    // 如果满足条件则开启降级检测
    if (degradeCheck && degradeCheckPeriod > 0 && degradeCheckAllowTimes > 0) {
    
    
        startDegradeCheck();
    }
    // 监听配置项 CLIENT_DEGRADE_CHECK 的变更。
    ConfigurationCache.addConfigListener(ConfigurationKeys.CLIENT_DEGRADE_CHECK, this);
    this.initDefaultGlobalTransactionTimeout();
}

3) After listening to configuration changes at runtime, adjust the degradation detection capability

Whether the transaction is disabled, the corresponding configuration key is: service.disableGlobalTransaction
Whether to enable the client's degradation detection, the corresponding configuration key is: client.tm.degradeCheck

@Override
public void onChangeEvent(ConfigurationChangeEvent event) {
    
    
    if (ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION.equals(event.getDataId())) {
    
    
        LOGGER.info("{} config changed, old value:{}, new value:{}", ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                disable, event.getNewValue());
        disable = Boolean.parseBoolean(event.getNewValue().trim());
    } else if (ConfigurationKeys.CLIENT_DEGRADE_CHECK.equals(event.getDataId())) {
    
    
        boolean degradeCheck = Boolean.parseBoolean(event.getNewValue());
        // 如果禁用降级检测,关闭降级检测
        if (!degradeCheck) {
    
    
            degradeNum = 0;
            stopDegradeCheck();
        } else if (degradeCheckPeriod > 0 && degradeCheckAllowTimes > 0) {
    
    
            // 如果开启检测,并且满足条件,则开启降级检测
            startDegradeCheck();
        }
    }
}

4) Turn on the degradation detection. When the transaction capability degradation detection is enabled, turn on the flag switch, and a single-threaded thread pool will be created, and the transaction capability detection will be performed with this thread pool. Simulate the opening and submission of a set of global transactions to detect whether the TC is in normal service. The name of the transaction is degradeCheck

private static void startDegradeCheck() {
    
    
    if (!ATOMIC_DEGRADE_CHECK.compareAndSet(false, true)) {
    
    
        return;
    }
    if (executor != null && !executor.isShutdown()) {
    
    
        return;
    }
    // 如果启动降级检测,就创建一个单线程的线程池,线程名称前缀为degradeCheckWorker
    executor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("degradeCheckWorker", 1, true));
    // 定时任务的频率默认值是2000毫秒,通过client.tm.degradeCheckPeriod配置
    executor.scheduleAtFixedRate(() -> {
    
    
        // 定时任务也有条件,会判断当前是否要做TC事务能力试探
        if (ATOMIC_DEGRADE_CHECK.get()) {
    
    
            try {
    
    
                // 模拟一套全局事务的开启和提交,用于检测TC是否正常服务,事务的名字是degradeCheck
                String xid = TransactionManagerHolder.get().begin(null, null, "degradeCheck", 60000);
                TransactionManagerHolder.get().commit(xid);
                // 如果模拟的事务正常提交了,则投递降级检测成功的消息,onDegradeCheck中做计数梳理
                EVENT_BUS.post(new DegradeCheckEvent(true));
            } catch (Exception e) {
    
    
                // 如果模拟的事务遇到的问题,则投递降级检测失败的消息,onDegradeCheck中做计数梳理
                EVENT_BUS.post(new DegradeCheckEvent(false));
            }
        }
    }, degradeCheckPeriod, degradeCheckPeriod, TimeUnit.MILLISECONDS);
}

5) If the transaction capability degradation detection is stopped, turn off the flag switch and destroy the thread pool.

private static void stopDegradeCheck() {
    
    
    // 关闭标志开关
    if (!ATOMIC_DEGRADE_CHECK.compareAndSet(true, false)) {
    
    
        return;
    }
    // 关闭定时任务的线程池,其中的定时任务自然也被销毁
    if (executor != null && !executor.isShutdown()) {
    
    
        executor.shutdown();
    }
}

6) Detection statistics
The event processing mechanism of EventBus in Guava (an implementation of the observer mode (production/consumption model)) is used to realize statistical calculations related to degradation detection. When executing a real transaction or a simulated transaction, the event information of success or failure will be delivered according to the transaction result, as follows

EVENT_BUS.post(new DegradeCheckEvent(true));
EVENT_BUS.post(new DegradeCheckEvent(false));

Listen to the transaction success or failure event to adjust the statistics count. The registration of the observer occurs in the GlobalTransactionalInterceptor, and its constructor has EVENT_BUS.register(this), which is used to register the observer, that is, the consumer of the event. Where is the logic of consumption? There is a @Subscribe annotation above the GlobalTransactionalInterceptor#onDegradeCheck method, indicating that this method is the main body of the event's consumption process. Its core logic is as follows:

  • Here there is 1 threshold degradeCheckAllowTimes and two counters reachNum, degradeNum, both of which are threshold comparisons
  • degradeNum records the number of consecutive failures. When the number of failures does not reach the threshold, the degradeNum technology will be restored to 0 when a success is encountered.
  • When the number of consecutive failures degradeNum reaches the threshold, the transaction is disabled, and the business logic will not use the reuse transaction.
  • Then rely on the default transaction in the scheduled task to test whether the TC is normal, the default frequency is 2000 milliseconds, configured through client.tm.degradeCheckPeriod
  • When the executor timing task encounters a failure during the trial process, reset the count reachNum of continuous success of the trial to 0
  • The transaction capability will not be reactivated until the number of consecutive successes of tentative transactions in the scheduled task of the executor reaches the threshold.
@Subscribe // @Subscribe监听 EVENT_BUS 的的事件,
public static void onDegradeCheck(DegradeCheckEvent event) {
    
    
    if (event.isRequestSuccess()) {
    
    
        // 当 degradeNum >= degradeCheckAllowTimes 时,实际是事务已经被禁用了
        // 那什么情况下,事务能力被重新激活呢?
        // 当降级激活后,executor定时任务试探事务要连续成功次数达到阈值后,才会重新激活事务能力。
        if (degradeNum >= degradeCheckAllowTimes) {
    
    
            reachNum++;
            if (reachNum >= degradeCheckAllowTimes) {
    
    
                reachNum = 0;
                degradeNum = 0;
                if (LOGGER.isInfoEnabled()) {
    
    
                    LOGGER.info("the current global transaction has been restored");
                }
            }
        } else if (degradeNum != 0) {
    
    
            // 当失败次数未达到阈值的时候,遇到一次成功就把degradeNum技术恢复成0,意味着degradeNum是记录了连续失败次数。
            degradeNum = 0;
        }
    } else {
    
    
        if (degradeNum < degradeCheckAllowTimes) {
    
    
            degradeNum++;
            // 当连续失败达到阈值后,打印warn日志,the current global transaction has been automatically downgraded
            // 并且会激活降级
            if (degradeNum >= degradeCheckAllowTimes) {
    
    
                if (LOGGER.isWarnEnabled()) {
    
    
                    LOGGER.warn("the current global transaction has been automatically downgraded");
                }
            }
            //当降级激活后,会有定时任务试探事务能力是否正常,在试探过程中一旦遇到一次失败,就把试探连续成功的计数reachNum重置为0
            //也就是说当降级激活后,定时任务试探事务要连续成功次数达到阈值后,才会重新激活事务能力。
        } else if (reachNum != 0) {
    
    
            reachNum = 0;
        }
    }
}

Guess you like

Origin blog.csdn.net/u011397981/article/details/130647987