六TCC-transaction的事务恢复

六 TCC-transaction的事务恢复

五 TCC-transaction的事务恢复

由于2.X提供了embedded和server的部署模式,在embedded模式下,事务恢复仍旧由tcc-client实现;在server模式时,事务恢复交给tcc-server模块实现。这里,我仅以tcc-client端(embedded模式)的事务模式,研究Tcc-transaction的事务恢复过程。

image-20230523162107624

5.1 TCC-Client的启动

public class SpringTccClient extends TccClient implements TransactionManagerFactory {

    public SpringTccClient() {
        super(new ClientConfig());
    }

    public SpringTccClient(ClientConfig clientConfig) {
        super(clientConfig);
    }
}

SpringTccClient类在spring配置类中,实例化进入spring容器。

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan(value = "org.mengyun.tcctransaction", excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {XmlTccTransactionConfiguration.class})})
public class AnnotationTccTransactionConfiguration {

     @Bean("springBeanFactory")
    public SpringBeanFactory getSpringBeanFactory() {
        return new SpringBeanFactory();
    }
  
    @Bean
    @DependsOn({"springBeanFactory"})
    public SpringTccClient getTccClient() {
        return new SpringTccClient(clientConfig);
    }
    
    }

随后,在TccClient的初始化过程中:

  @Override
    @PostConstruct
    //spring容器启动的时候,执行该方法
    public void start() throws Exception {
        this.isStarting = true;

        //如果TCC-Transaction的客户端client的存储模式为REMOTING,表示使用server的部署结构
        if (this.clientConfig.getStorageType() == StorageType.REMOTING) {
            try {
                this.registryService.start();
                this.registryService.subscribe();
            } catch (Exception e) {
                logger.error("failed to initialize registryService, stop the application!", e);
                StopUtils.stop();
            }
            initializeRemotingClient(); //初始化远程client
          
        } else { //此时,使用本地存储,事务对象的持久化和事务恢复,都在本地

            registerDomain(this.clientConfig.getDomain());
            if (transactionRepository.supportRecovery() && this.clientConfig.isRecoveryEnabled()) {
							//TAG1 scheduler
              //TAG2 scheduler.registerScheduleAndStartIfNotPresent
                scheduler.registerScheduleAndStartIfNotPresent(this.clientConfig.getDomain());
            }
        }

        this.isStarting = false;
    }

tccclient.start方法在spring容器启动加载该对象时,执行。

TccClient.start():
		1 StorageType.REMOTING 当使用remoting存储模式时,需要向remotingClient注册当前client;
    2 其他情况,使用本地的持久化和事务恢复
      scheduler.registerScheduleAndStartIfNotPresent

TAG1 scheduler

当前scheduler对象在实例化时创建:

        this.scheduler = new RecoveryScheduler(this.clientConfig);
public class RecoveryScheduler {

    public static final String JOB_NAME = "TCC_JOB_%s"; //任务名称
    public static final String TRIGGER_NAME = "TCC_TRIGGER_%s"; //触发任务的名称
    private static final Logger logger = LoggerFactory.getLogger(RecoveryScheduler.class.getSimpleName());
    private RecoveryConfig recoveryConfig;
		//缓存domain和Scheduler---一个domain一个Scheduler
    private Map<String, Scheduler> schedulers = new ConcurrentHashMap<>();

    public RecoveryScheduler(RecoveryConfig recoveryConfig) {
        this.recoveryConfig = recoveryConfig;
    }

TAG2 scheduler.registerScheduleAndStartIfNotPresent

向RecoveryScheduler定时恢复任务器中创建、缓存scheduler并启动

    public void registerScheduleAndStartIfNotPresent(String domain) {
        Scheduler scheduler = registerScheduleIfNotPresent(domain); //创建、缓存scheduler
        start(scheduler);//启动scheduler
    }

    public Scheduler registerScheduleIfNotPresent(String domain) {
      //如果没有创建过切没有缓存入schedulers的本地缓存中
        if (!schedulers.containsKey(domain)) {
            synchronized (RecoveryScheduler.class) {
              //如果没有创建和缓存过Scheduler
                if (!schedulers.containsKey(domain)) { 
                  //TAG2.1 createScheduler
                    Scheduler scheduler = createScheduler(domain);
                  //TAG2.2 scheduleJob
                    scheduleJob(scheduler, domain);
                    schedulers.put(domain, scheduler);
                }
            }
        }
        return schedulers.get(domain);
    }

TAG2.1 createScheduler 创建scheduler内的任务JobDetail

该方法创建scheduler内执行的任务,并设置定时任务的触发器

   private void scheduleJob(Scheduler scheduler, String domain) {

        String jobName = String.format(JOB_NAME, domain);
        String triggerName = String.format(TRIGGER_NAME, domain);

     //创建Quartz定时任务执行器的JobDetail
     //TAG2.1.1 QuartzRecoveryTask任务
        JobDetail jobDetail = JobBuilder.newJob(QuartzRecoveryTask.class).withIdentity(jobName).build();
        jobDetail.getJobDataMap().put(MixAll.DOMAIN, domain);
        //定义触发器
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerName)
                //TCC配置的操作间隔:0/30 * * * * ? 每30秒一次
                .withSchedule(CronScheduleBuilder.cronSchedule(recoveryConfig.getCronExpression())
                        //如果任务时间完成-下一次触发时间<misfireThreshold,立即执行;否则,等待下一次触发时间
                        .withMisfireHandlingInstructionDoNothing()).build();

        try {
          //检查当前job任务是否存在于scheduler中
            if (!scheduler.checkExists(JobKey.jobKey(jobName))) {
              
              //TAG2.1.2 scheduler.scheduleJob
              //如果不存在,就按JobDetail、触发器cronTrigger执行定时任务
                scheduler.scheduleJob(jobDetail, cronTrigger);
            } else {
                if (recoveryConfig.isUpdateJobForcibly()) {
                  //如果存在,就先删除scheduler内的job,再传入并执行新的job
                    scheduler.deleteJob(JobKey.jobKey(jobName));
                    scheduler.scheduleJob(jobDetail, cronTrigger);
                }
            }
        } catch (SchedulerException se) {
            try {
                scheduler.shutdown();
            } catch (Exception ignore) {
                //ignore
            }
            throw new SystemException(String.format("register recovery task for domain<%s> failed", domain), se);
        }
    }

Quartz每30秒执行QuartzRecoveryTask任务,进行事务的恢复重试。

TAG2.1.1 QuartzRecoveryTask任务

这是TCC-Transaction创建的Quartz定时任务执行类

@DisallowConcurrentExecution
public class QuartzRecoveryTask implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        String domain = context.getJobDetail().getJobDataMap().getString(MixAll.DOMAIN);
        logger.info("start recovery {}", domain);
      //RTask1 TransactionStoreRecovery.startRecover(domain)
        FactoryBuilder.factoryOf(TccService.class).getInstance().getTransactionStoreRecovery().startRecover(domain);
    }
}

RTask1 TransactionStoreRecovery.startRecover(domain)执行任务恢复

 public class TransactionStoreRecovery implements Closeable {

    public void startRecover(String domain) {

        try {
            String offset = null;

            int totalCount = 0;
            do {

              //RT1.1 loadErrorTransactionsByPage分页加载错误的事务transaction
                Page<TransactionStore> page = loadErrorTransactionsByPage(domain, offset);

                if (!page.getData().isEmpty()) {
                  //RT1.2 并发恢复错误事务transaction
                    concurrentRecoveryErrorTransactions(page.getData());
                    offset = page.getNextOffset();
                    totalCount += page.getData().size();
                } else {
                    break;
                }
            } while (true);

            // 告警
            AlertManager.tryAlert(domain, totalCount, transactionStorage);

            logger.debug("total recovery count {} from repository:{}", totalCount, transactionStorage.getClass().getName());
        } catch (Throwable e) {
            logger.error("recovery failed from repository:{}.", transactionStorage.getClass().getName(), e);
        }
    }
    
    }

RT1.1 loadErrorTransactionsByPage分页加载错误的事务transaction

    private Page<TransactionStore> loadErrorTransactionsByPage(String domain, String offset) {

        long currentTimeInMillis = Instant.now().toEpochMilli();
            //事务恢复间隔默认设置30s
      //RT1.1.1 transactionStorage).findAllUnmodifiedSince
        return ((StorageRecoverable) transactionStorage).findAllUnmodifiedSince(domain, new Date(currentTimeInMillis - recoveryConfig.getRecoverDuration() * 1000), offset, recoveryConfig.getFetchPageSize());
    }

注意这里对于异常事务的定义:

new Date(currentTimeInMillis - recoveryConfig.getRecoverDuration() * 1000)

就是当前时间-事务恢复间隔>事务变更时间。也就是事务的修改事件,在超过事务恢复间隔时间后,仍旧未被执行,就被定义为异常的事务。对于这部分事务需要进行恢复、重试。

RT1.1.1 transactionStorage).findAllUnmodifiedSince 调用数据库查询transaction

找到所有未修改的事务信息。

public class JdbcTransactionStorage extends AbstractTransactionStorage implements StorageRecoverable {

    @Override
    public Page<TransactionStore> findAllUnmodifiedSince(String domain, Date date, String offset, int pageSize) {
        return pageList(domain, date, offset, pageSize, false);
    }
  
      private Page<TransactionStore> pageList(String domain, Date date, String offset, int pageSize, boolean isMarkDeleted) {

        List<TransactionStore> transactions = new ArrayList<>();

        Connection connection = null;
        PreparedStatement stmt = null;

        int currentOffset = StringUtils.isEmpty(offset) ? 0 : Integer.parseInt(offset);

        try {
          //获取数据库连接
            connection = this.getConnection();
/**………………………………………………………………………………………………………创建查询语句…………………………………………………………………………………………………………………………… */
            StringBuilder builder = new StringBuilder();
            builder.append(SQL_SELECT_PREFIX_FOR_TCC_TRANSACTION + getTableName() + " WHERE LAST_UPDATE_TIME < ?");
            builder.append(" AND IS_DELETE = ?");
            builder.append(StringUtils.isNotEmpty(domain) ? " AND DOMAIN = ?" : "");
            builder.append(" ORDER BY TRANSACTION_ID ASC");
            builder.append(String.format(" LIMIT %s, %d", currentOffset, pageSize));

            stmt = connection.prepareStatement(builder.toString());
						//设置sql语句内的参数
            stmt.setTimestamp(1, new Timestamp(date.getTime()));
            stmt.setInt(2, isMarkDeleted ? MARK_DELETED_YES : MARK_DELETED_NO);

            if (StringUtils.isNotEmpty(domain)) {
                stmt.setString(3, domain);
            }
						//执行sql语句
            ResultSet resultSet = stmt.executeQuery();

            this.constructTransactions(resultSet, transactions);
        } catch (Throwable e) {
            throw new TransactionIOException(e);
        } finally {
            closeStatement(stmt);
            this.releaseConnection(connection);
        }

        return new Page<>(String.valueOf(currentOffset + transactions.size()), transactions);
    }
  
}

这里,对在持久存储数据库查询的语句:

SELECT DOMAIN,ROOT_XID,XID,CONTENT,STATUS,TRANSACTION_TYPE,CREATE_TIME,LAST_UPDATE_TIME,RETRIED_COUNT,VERSION,IS_DELETE,ROOT_DOMAIN,REQUEST_ID 
FROM TableName
 WHERE LAST_UPDATE_TIME < TIME
 AND IS_DELETE =0 #未被标记删除
 AND DOMAIN =domain
 ORDER BY TRANSACTION_ID ASC
  currentOffset, pageSize

从数据库查询error的transaction

RT1.2 concurrentRecoveryErrorTransactions 并发恢复错误事务transaction

  private void concurrentRecoveryErrorTransactions(List<TransactionStore> transactions) throws InterruptedException, ExecutionException {

        initLogStatistics();
				//创建恢复transaction的task
        List<RecoverTask> tasks = new ArrayList<>();
        for (TransactionStore transaction : transactions) {
          //RT1.2.1 RecoverTask error任务的task
            tasks.add(new RecoverTask(transaction));
        }
			//RT1.2.2 recoveryExecutorService.invokeAll批量执行任务
        List<Future<Void>> futures = recoveryExecutorService.invokeAll(tasks, CONCURRENT_RECOVERY_TIMEOUT, TimeUnit.SECONDS);

        for (Future future : futures) {
            future.get();
        }
    }

RT1.2.1 RecoverTask error任务的task

TransactionStoreRecovery的内部类

   class RecoverTask implements Callable<Void> {

        TransactionStore transaction;

        public RecoverTask(TransactionStore transaction) {
            this.transaction = transaction;
        }

        @Override
        public Void call() throws Exception {
          
            recoverErrorTransaction(transaction);
            return null;
        }
    }
  private void recoverErrorTransaction(TransactionStore transactionStore) {
				
    //如果当前重试次数,超过recoveryConfig.getMaxRetryCount()
        if (transactionStore.getRetriedCount() > recoveryConfig.getMaxRetryCount()) { //1

            logSync.lock();
            try {
              //当triggerMaxRetryPrintCount的log输出不大于max,继续输出重试信息
                if (triggerMaxRetryPrintCount.get() < logMaxPrintCount) {
                    logger.error(
                            "recover failed with max retry count,will not try again. domain:{}, xid:{}, rootDomain:{}, rootXid:{}, status:{},retried count:{}",
                            transactionStore.getDomain(),
                            transactionStore.getXid(),
                            transactionStore.getRootDomain(),
                            transactionStore.getRootXid(),
                            transactionStore.getStatusId(),
                            transactionStore.getRetriedCount());
                    triggerMaxRetryPrintCount.incrementAndGet();
                } else if (triggerMaxRetryPrintCount.get() == logMaxPrintCount) {
                    logger.error("Too many transactionStore's retried count max then MaxRetryCount during one page transactions recover process , will not print errors again!");
                }

            } finally {
                logSync.unlock();
            }

            return;
        } //1
    
/** …………………………………………………………………………………………………未超过重试次数时……………………………………………………………………………………………………………………………*/
        try {
				/**……………………………………………………………………………………………………根事务的恢复逻辑…………………………………………………………………… */
            if (transactionStore.getTransactionTypeId() == TransactionType.ROOT.getId()) {

                switch (TransactionStatus.valueOf(transactionStore.getStatusId())) {
                    case CONFIRMING:
                        commitTransaction(transactionStore); 
                        break;
                    case CANCELLING:
                        rollbackTransaction(transactionStore);
                        break;
                    case TRYING:
                        tryTreatAsFailed(transactionStore, TransactionStatus.CANCELLING);
                        break;
                    default:
                        //ignore it.
                        break;

                }

            } 
				/**……………………………………………………………………………………………………branch分支事务的恢复逻辑…………………………………………………………………… */
          else {

                //transactionStore type is BRANCH
                switch (TransactionStatus.valueOf(transactionStore.getStatusId())) {
                    case CONFIRMING:
                    //ERR1 commitTransaction
                        commitTransaction(transactionStore);
                        break;
                    case CANCELLING:
                    case TRY_FAILED:
                    //ERR2 rollbackTransaction
                        rollbackTransaction(transactionStore);
                        break;
                    case TRY_SUCCESS:

                        if (storageMode == StorageMode.CENTRAL) {

                            //check the root transactionStore
                            TransactionStore rootTransaction = transactionStorage.findByXid(transactionStore.getRootDomain(), transactionStore.getRootXid());

                            if (rootTransaction == null) {
                                // In this case means the root transactionStore is already rollback.
                                // Need cancel this branch transactionStore.
                                rollbackTransaction(transactionStore);
                            } else {
                                switch (TransactionStatus.valueOf(rootTransaction.getStatusId())) {
                                    case CONFIRMING:
                                        commitTransaction(transactionStore);
                                        break;
                                    case CANCELLING:
                                        rollbackTransaction(transactionStore);
                                        break;
                                    default:
                                        break;
                                }
                            }
                        }
                        break;
                    case TRYING:
                    //ERR3 tryTreatAsFailed
                        tryTreatAsFailed(transactionStore, TransactionStatus.TRY_FAILED);
                        break;
                    default:
                        //ignore it.
                        break;
                }

            }

        } catch (Throwable throwable) {

            if (throwable instanceof TransactionOptimisticLockException
                    || ExceptionUtils.getRootCause(throwable) instanceof TransactionOptimisticLockException) {

                logger.warn(
                        "optimisticLockException happened while recover. txid:{}, status:{},retried count:{}",
                        transactionStore.getXid(),
                        transactionStore.getStatusId(),
                        transactionStore.getRetriedCount());
            } else {

                logSync.lock();
                try {
                    if (recoveryFailedPrintCount.get() < logMaxPrintCount) {
                        try {
                            logger.error("recover failed, txid:{}, status:{},retried count:{},transactionStore content:{}",
                                    transactionStore.getXid(),
                                    transactionStore.getStatusId(),
                                    transactionStore.getRetriedCount(),
                                    jackson.writeValueAsString(transactionStore), throwable);
                        } catch (JsonProcessingException e) {
                            logger.error("failed to serialize transactionStore {}", transactionStore.toString(), e);
                        }
                        recoveryFailedPrintCount.incrementAndGet();
                    } else if (recoveryFailedPrintCount.get() == logMaxPrintCount) {
                        logger.error("Too many transactionStore's recover error during one page transactions recover process , will not print errors again!");
                    }
                } finally {
                    logSync.unlock();
                }
            }
        }
    }

这里,对error事务的恢复逻辑如下:

事务状态主要有:CONFIRMING、CANCELLING、TRYING、TRY_FAILED、TRY_SUCCESSED。

根事务主要需要对CONFIRMING、CANCELLING、TRYING进行处理;

分支事务,需要对所有情况处理,此外,对于TRY_SUCCESSED的事务状态,要根据根事务root的事务状态:如果store模式为central中心存储,仅用于客户端,当取值为CENTRAL,分支事务补偿时会对TRY_SUCCESS状态的事件,进行处理。

如果此时root事务不存在,则表示根事务已经rollback,此时branch需要rollback;

如果root事务存在,处于confirming,就confirm;cancelling,就cnacel;其他情况,不处理。

transactionStore.getTransactionTypeId() ROOT根事务时
  事务当前状态:
  	 1 case CONFIRMING:
       commitTransaction(transactionStore); //重新commit
     2 case CANCELLING:
       rollbackTransaction(transactionStore);//重新rollback
     3 case TRYING:
       tryTreatAsFailed(transactionStore, TransactionStatus.CANCELLING); //将其视作失败,并设置状态为cancelling
		其他情况:
      忽略不处理

transactionStore.getTransactionTypeId() BRANCH分支事务时
  事务当前状态:
     1 case CONFIRMING:
       commitTransaction(transactionStore);

     2 case CANCELLING:
     case TRY_FAILED:
       rollbackTransaction(transactionStore);

		3 case TRY_SUCCESS:
		if (storageMode == StorageMode.CENTRAL) {
      if (rootTransaction == null)
        rollbackTransaction(transactionStore)
      else
        case CONFIRMING:
            commitTransaction(transactionStore); //重新commit
     		case CANCELLING:
       rollbackTransaction(transactionStore);//重新rollback
      其他情况:
        不处理
        
     4 case TRYING:
       tryTreatAsFailed(transactionStore, TransactionStatus.TRY_FAILED); //将其视作失败,并设置状态为TRY_FAILED
ERR1 commitTransaction
   private void commitTransaction(TransactionStore transactionStore) {
        recoveryExecutor.commit(transactionStore);
    }
public class ClientRecoveryExecutor implements RecoveryExecutor {

    public void commit(TransactionStore transactionStore) {

        Transaction transaction = TransactionConvertor.getTransaction(transactionSerializer, transactionStore);

      //将事务的重试次数+1
        transaction.setRetriedCount(transaction.getRetriedCount() + 1);
        transaction.setStatus(TransactionStatus.CONFIRMING); //设置事务状态confirming
        try {
            transactionRepository.update(transaction); //更新
        } catch (TransactionOptimisticLockException e) {
            logger.debug("multiple instances try to recovery<commit> the same transaction<{}>, this instance ignore the recovery.", transactionStore.getXid());
            return;
        }
        transaction.commit(); //重新执行commit提交
        transactionRepository.delete(transaction); //删除
    }
ERR2 rollbackTransaction

rollback的恢复处理,和commitTransaction相同

ERR3 tryTreatAsFailed
    private void tryTreatAsFailed(TransactionStore transactionStore, TransactionStatus transactionStatus) {
        Date lastUpdateTime = transactionStore.getLastUpdateTime();
        Date currentTime = new Date();
      //trying阶段的恢复,视作failed,同样具有最大次数
        int maxTimeTreatTryingAsFailed = recoveryConfig.getMaxTimeTreatTryingAsFailed();
        if (maxTimeTreatTryingAsFailed > 0
                && (currentTime.getTime() - lastUpdateTime.getTime()) > maxTimeTreatTryingAsFailed * 1000) {
            //update the status to cancel or try failed, waiting for the recovery task to recover
          //版本号+1
            transactionStore.setVersion(transactionStore.getVersion() + 1);
          //TransactionStatus.TRY_FAILED设置transaction的状态为try-failed
            transactionStore.setStatusId(transactionStatus.getId());
            transactionStorage.update(transactionStore);
        }
    }

这里的操作,是将transaction的状态设置为TransactionStatus.TRY_FAILED。等待重试时候,进行rollback。

注意:

对于root根事务的事务恢复,只对trying的视作cancelling,然后再下一次的失败重试时,会对root为cancelling的直接rollback;

对于branch的分支事务,对于trying的视作try-failed。然后等待下一次再次的重试,在branch的再次恢复中,对于try-failed的会直接rollback;

RT1.2.2 recoveryExecutorService.invokeAll批量执行任务

调用恢复transaction的线程池,调用invokeAll执行批量任务。

TAG2.2 Scheduler.scheduleJob

这里调用quartz定时任务处理器的类,执行前述的定时恢复任务。

到这里,TCC-client端的事务恢复就执行完毕。

5.2 TCC-Server的启动(todo)

基本同tcc-server,等随后贴源码过程

posted @ 2023-06-12 18:33  LeasonXue  阅读(54)  评论(0编辑  收藏  举报