TCC総務原則

オリジナル:https://yq.aliyun.com/articles/682871

本論文では、TCCの原理を説明し、コードを実装する方法の観点から分析;例は、特定の使用を対象とされていません。この分析は、githubのオープンソースプロジェクトTCC-トランザクションコード、アドレスにあります。https://github.com/changmingxie/tcc-transactionは、当然のことながら、githubの上のより多くのTCCプロジェクトがありますが、その原則は似ていますが、より多くのように導入、ソースコード自体を読んで興味を持っジュニアパートナー。 TCCアーキテクチャ

1つの  アーキテクチャ

 

f81a815047d761053273ff2e5f33b9e603e4f713

図に示すように:

 - 部品の数からビジネス・サービスおよびビジネスサービスのマスタによる完全なビジネス活動。

 - 主なビジネスサービスは、全体的な事業活動を開始し、完了するための責任があります。

 - ビジネスサービスからTCC型事業を提供します。

 - ビジネス・アクティビティ・マネージャ事業登録営業活動、事業活動の一貫性を制御し、かつ行わ事業活動の提出時の動作を確認するための事業活動がキャンセルした場合のアクションをキャンセル

TCCなど2PC / 3PCが、TCCのトランザクション制御ビジネスコードのすべての側面、および2PC / 3PCは、リソースレベルです。

2   仕様の各段階

TCCの取引は、実際には2つの主要な段階で構成されています。舞台をキャンセル/確認し、舞台をお試しください。

私たちは、あなたが最大の確認に成功した実行段階を確保することができますので、確認の段階利用可能なリソースがあることを確認するために、ステージ脇に置きリソースをチェックしてみてください、TCCの核となるアイデアは、TCCの論理モデルから見ることができます。

1)    ビジネスを実行しようと試行

すべてのビジネスチェックの完了(一貫性)

ビジネスリソースを確保しなければなりません(準アイソレーション)

2)    業務の実行を確認confirm-

ビジネスの実際の実装

これは、どのようなビジネスの点検を行うことはありません

唯一のビジネスリソース予約段階を使用してみてください

冪等を確認する必要があります動作を確認 

3)   業務の実行をキャンセル、キャンセル

経営資源の予約ステージを解放するようにしてください

冪等を確認する必要があり、操作をキャンセル

 

ジカルボン 一例

TCCトランザクションを例に以下に説明されます。トム・トレイシーは、TCCは、このトランザクションを解決使用した場合、どのようにそれを行うには$ 10オンにする必要がありますか?

1   直面する主な問題

私たちは、転写プロセスが直面する問題を考えてみます。

 - 10以上の元トム口座の残高を確認する必要があります。

 - 口座残高の正確さを確保する必要性、例えば:トムはわずか10ドルであると仮定しますが、同時にトム・トレイシー、角度転送10元に、他の人にトムの転送は、この時もアカウント、他の人のお金のターンを受け取ることができますバランスは(トレイシーアカウントでも同様の問題に直面している)混乱の中では存在できません

 - ときよりも大きく、同時、パフォーマンスを確保することができるようにします。

 

2つの  TCCの問題を解決するためのアイデア

5e77c9b1bbdfa607393644d3e2b3427db93b1c08

分散トランザクションを解決するためのTCCのアイデアが解体小さいトランザクションに大きな取引です。

 

3   TCC処理ロジック

場合TCCを使用して、トランザクションが、擬似コードは以下の通り:

@Compensable(confirmMethod = "transferConfirm", cancelMethod = "transferCancel")
@Transactional
public void transferTry(long fromAccountId, long toAccountId, int amount) {
    //检查Tom账户
    //锁定Tom账户
    //锁定Tracy账户
}

@Transactional
public void transferConfirm(long fromAccountId, long toAccountId, int amount) {
    //tom账户-10元
    //tracy账户+10元
}

@Transactional
public void transferCancel(long fromAccountId, long toAccountId, int amount) {
    //解除Tom账户锁定
    //接触Tracy账户锁定
}

逻辑如下图所示:

e3ff945eedac9e35269a47aa2d0efd4a9db844a1

在Try逻辑中需要确保Tom账户的余额足够,并锁定需要使用的资源(Tom、Tracy账户);如果这一步操作执行成功(没有出现异常),那么将执行Confirm方法,如果执行失败,那么将执行Cancel方法。注意Confirm、Cancel需要做好幂等。

 

 原理分析

在上面的TCC事务中,转账操作其实涉及六次操作,实际项目中,在任何一个步骤都可能失败,那么当任何一个步骤失败时,TCC框架是如何做到数据一致性的呢?

1  整体流程图

以下为TCC的处理流程图,他可以确保不管是在try阶段,还是在confirm/cancel阶段都可以确保数据的一致性。

b5b927eb63c1c76388a213f6a2825b7ae93c3a3b

从流程图上可以看到,TCC依赖于一条事务处理记录,在开始TCC事务前标记创建此记录,然后在TCC的每个环节持续更新此记录的状态,这样就可以知道事务执行到那个环节了,当一次执行失败,进行重试时同样根据此数据来确定当前阶段,并判断应该执行什么操作。

因为存在失败重试的逻辑,所以cancel、commit方法必须实现幂等。其实在分布式开发中,凡是涉及到写操作的地方都应该实现幂等。

 

2  TCC核心处理逻辑

因为使用了@Compensable注解,所以当调用transferTry方法前,首先进入代理类中。在TCC中有两个Interceptor会对@Compensable标注的方法生效,他们分别是:CompensableTransactionInterceptor(TCC主要逻辑在此Interceptor中完成)、ResourceCoordinatorInterceptor(处理资源相关的事宜)。

CompensableTransactionInterceptor#interceptCompensableMethod是TCC的核心处理逻辑。interceptCompensableMethod封装请求数据,为TCC事务做准备,源码如下:

public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable {
    Method method = CompensableMethodUtils.getCompensableMethod(pjp);
    Compensable compensable = method.getAnnotation(Compensable.class);
    Propagation propagation = compensable.propagation();
    TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs());
    boolean asyncConfirm = compensable.asyncConfirm();
    boolean asyncCancel = compensable.asyncCancel();
    boolean isTransactionActive = transactionManager.isTransactionActive();
    if (!TransactionUtils.isLegalTransactionContext(isTransactionActive, propagation, transactionContext)) {
        throw new SystemException("no active compensable transaction while propagation is mandatory for method " + method.getName());
    }
    MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);
    switch (methodType) {
        case ROOT:
            return rootMethodProceed(pjp, asyncConfirm, asyncCancel);
        case PROVIDER:
            return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel);
        default:
            return pjp.proceed();
    }
}

rootMethodProceed是TCC和核心处理逻辑,实现了对Try、Confirm、Cancel的执行,源码如下,重点注意标红加粗部分:

private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable {
    Object returnValue = null;
    Transaction transaction = null;
    try {
        transaction = transactionManager.begin();
        try {
           returnValue = pjp.proceed();
        } catch (Throwable tryingException) {
            if (isDelayCancelException(tryingException)) {
               transactionManager.syncTransaction();
            } else {
               logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException);
               transactionManager.rollback(asyncCancel);
            }
            throw tryingException;
        }
       transactionManager.commit(asyncConfirm);
    } finally {
        transactionManager.cleanAfterCompletion(transaction);
    }
    return returnValue;
}

在这个方法中我们看到,首先执行的是@Compensable注解标注的方法(try),如果抛出异常,那么执行rollback方法(cancel),否则执行commit方法(cancel)。

 

3  异常处理流程

考虑到在try、cancel、confirm过程中都可能发生异常,所以在任何一步失败时,系统都能够要么回到最初(未转账)状态,要么到达最终(已转账)状态。下面讨论一下TCC代码层面是如何保证一致性的。

1)  Begin

在前面的代码中,可以看到执行try之前,TCC通过transactionManager.begin()开启了一个事务,这个begin方法的核心是:

 - 创建一个记录,用于记录事务执行到那个环节了。

 - 注册当前事务到TransactionManager中,在confirm、cancel过程中可以使用此Transaction来commit或者rollback。

TransactionManager#begin方法

public Transaction begin() {
    Transaction transaction = new Transaction(TransactionType.ROOT);
   transactionRepository.create(transaction);
    registerTransaction(transaction);
    return transaction;
}

CachableTransactionRepository#create创建一个用于标识事务执行环节的记录,然后将transaction放到缓存中区。代码如下:

@Override
public int create(Transaction transaction) {
    int result = doCreate(transaction);
    if (result > 0) {
        putToCache(transaction);
    }
    return result;
}

CachableTransactionRepository有多个子类(FileSystemTransactionRepository、JdbcTransactionRepository、RedisTransactionRepository、ZooKeeperTransactionRepository),通过这些类可以实现记录db、file、redis、zk等的解决方案。

 

2)  Commit/rollback

在commit、rollback中,都有这样一行代码,用于更新事务状态:

transactionRepository.update(transaction);

这行代码将当前事务的状态标记为commit/rollback,如果失败会抛出异常,不会执行后续的confirm/cancel方法;如果成功,才会执行confirm/cancel方法。

 

3)  Scheduler

如果在try/commit/rollback过程中失败了,请求(transferTry方法)将会立即返回,TCC在这里引入了重试机制,即通过定时程序查询执行失败的任务,然后进行补偿操作。具体见:

TransactionRecovery#startRecover查询所有异常事务,然后逐个进行处理。注意重试操作有一个最大重试次数的限制,如果超过最大重试次数,此事务将会被忽略。

public void startRecover() {
    List<Transaction> transactions = loadErrorTransactions();
   recoverErrorTransactions(transactions);
}

private List<Transaction> loadErrorTransactions() {
    long currentTimeInMillis = Calendar.getInstance().getTimeInMillis();
    TransactionRepository transactionRepository = transactionConfigurator.getTransactionRepository();
    RecoverConfig recoverConfig = transactionConfigurator.getRecoverConfig();
    return transactionRepository.findAllUnmodifiedSince(new Date(currentTimeInMillis - recoverConfig.getRecoverDuration() * 1000));
}

private void recoverErrorTransactions(List<Transaction> transactions) {
    for (Transaction transaction : transactions) {
        if (transaction.getRetriedCount() > transactionConfigurator.getRecoverConfig().getMaxRetryCount()) {
           logger.error(String.format("recover failed with max retry count,will not try again. txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)));
            continue;
        }
        if (transaction.getTransactionType().equals(TransactionType.BRANCH)
                && (transaction.getCreateTime().getTime() +
               transactionConfigurator.getRecoverConfig().getMaxRetryCount() *
                       transactionConfigurator.getRecoverConfig().getRecoverDuration() * 1000
                > System.currentTimeMillis())) {
            continue;
        }
        try {
           transaction.addRetriedCount();
            if (transaction.getStatus().equals(TransactionStatus.CONFIRMING)) {
               transaction.changeStatus(TransactionStatus.CONFIRMING);
                transactionConfigurator.getTransactionRepository().update(transaction);
                transaction.commit();
               transactionConfigurator.getTransactionRepository().delete(transaction);
            } else if (transaction.getStatus().equals(TransactionStatus.CANCELLING)
                    || transaction.getTransactionType().equals(TransactionType.ROOT)) {
               transaction.changeStatus(TransactionStatus.CANCELLING);
               transactionConfigurator.getTransactionRepository().update(transaction);
                transaction.rollback();
               transactionConfigurator.getTransactionRepository().delete(transaction);
            }
        } catch (Throwable throwable) {
            if (throwable instanceof OptimisticLockException
                    || ExceptionUtils.getRootCause(throwable) instanceof OptimisticLockException) {
               logger.warn(String.format("optimisticLockException happened while recover. txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable);
            } else {
               logger.error(String.format("recover failed, txid:%s, status:%s,retried count:%d,transaction content:%s", transaction.getXid(), transaction.getStatus().getId(), transaction.getRetriedCount(), JSON.toJSONString(transaction)), throwable);
            }
        }
    }
}

 

 TCC优缺点

目前解决分布式事务的方案中,最稳定可靠的方案有:TCC、2PC/3PC、最终一致性。这三种方案各有优劣,有自己的适用场景。下面我们简单讨论一下TCC主要的优缺点。

1  TCC的主要优点有

因为Try阶段检查并预留了资源,所以confirm阶段一般都可以执行成功。

リソースのロックは、ビジネスコードで行われるライブDBをブロックしない、DBのパフォーマンスには影響しないことができます。

リアルタイム高TCCは、すべての書き込み操作は、DB確認に(時間の実行のタイミング、わずかな遅延故障ため)リアルタイムリターンの書き込み動作の結果を濃縮します。

2   TCCの主な欠点であります

ソースは、トランザクション状態管理するので、操作は、性能の若干の損失となり、全体の取引TCCている時間を長くするために、複数のDBを生成します、分析から見ることができます。

側に向け複数のトランザクションは、より複雑な試行は、より大きな再利用性の底(これは主に最終溶液の相対的な整合性の点である)、コードをキャンセルし、確認します。また、より多くの当事者が高く、故障の可能性をこれらの段階の長い処理時間を関与します。

 

ファイブ 関連文書

TCC-トランザクションおよびソースドキュメントリファレンスの使用:https://github.com/changmingxie/tcc-transaction

最終的一貫性ソリューションは、「を参照してくださいRocketMQの練習

おすすめ

転載: www.cnblogs.com/smileIce/p/11221610.html
おすすめ