本文主要介绍TCC的原理,以及从代码的角度上分析如何实现的;不涉及具体使用示例。本文分析的是github中开源项目tcc-transaction。当然github上有多个tcc项目,但是他们原理相近,所以不过多介绍,有兴趣的小伙伴自行阅读源码。

1 TCC架构

1.1 原理

  • 一个完整的业务活动由一个主业务服务与若干从业务服务组成。
  • 主业务服务负责发起并完成整个业务活动。
  • 从业务服务提供TCC型业务操作。
  • 业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动提交时进行confirm操作,在业务活动取消时进行cancel操作。

TCC和2PC/3PC很像,不过TCC的事务控制都是业务代码层面的,而2PC/3PC则是资源层面的。

1.2 各阶段规范

TCC事务其实主要包含两个阶段:Try阶段、Confirm/Cancel阶段。
从TCC的逻辑模型上我们可以看到,TCC的核心思想是,try阶段检查并预留资源,确保在confirm阶段有资源可用,这样可以最大程度的确保confirm阶段能够执行成功。

1.2.1 try:尝试执行业务

  • 完成所有业务检查(一致性)
  • 预留必须业务资源(准隔离性)

1.2.2 confirm:确认执行业务

  • 真正执行业务
  • 不作任何业务检查
  • 只使用Try阶段预留的业务资源
  • Confirm操作必须保证幂等性

1.2.2 cancel:取消执行业务

  • 释放Try阶段预留的业务资源
  • Cancel操作必须保证幂等性

2 TCC源码分析

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

2.1 整体流程图

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

从流程图上可以看到,TCC依赖于一条事务处理记录,在开始TCC事务前标记创建此记录,然后在TCC的每个环节持续更新此记录的状态,这样就可以知道事务执行到那个环节了,当一次执行失败,进行重试时同样根据此数据来确定当前阶段,并判断应该执行什么操作。
因为存在失败重试的逻辑,所以cancel、commit方法必须实现幂等。其实在分布式开发中,凡是涉及到写操作的地方都应该实现幂等。

2.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)。

2.3 异常处理流程

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

2.3.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.3.2 Commit/rollback

在commit、rollback中,都有这样一行代码,用于更新事务状态:
transactionRepository.update(transaction);
这行代码将当前事务的状态标记为commit/rollback,如果失败会抛出异常,不会执行后续的confirm/cancel方法;如果成功,才会执行confirm/cancel方法。

2.3.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);}}}
}

3 TCC优缺点

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

3.1 TCC的主要优点:

因为Try阶段检查并预留了资源,所以confirm阶段一般都可以执行成功。
资源锁定都是在业务代码中完成,不会block住DB,可以做到对db性能无影响。
TCC的实时性较高,所有的DB写操作都集中在confirm中,写操作的结果实时返回(失败时因为定时程序执行时间的关系,略有延迟)。

3.2 TCC的主要缺点:

从源码分析中可以看到,因为事务状态管理,将产生多次DB操作,这将损耗一定的性能,并使得整个TCC事务时间拉长。
事务涉及方越多,Try、Confirm、Cancel中的代码就越复杂,可复用性就越底(这一点主要是相对最终一致性方案而言的)。另外涉及方越多,这几个阶段的处理时间越长,失败的可能性也越高。

4 相关文档

tcc-transaction

Seata 原理

Seata-AT模式 原理

Seata-TCC模式 原理

Seata-Saga模式 原理

Seata-XA模式 原理

TCC-Transaction原理

TCC-Transaction原理相关推荐

  1. 《深入理解分布式事务》第八章 TCC 分布式事务原理

    <深入理解分布式事务>第八章 TCC 分布式事务原理 文章目录 <深入理解分布式事务>第八章 TCC 分布式事务原理 一.TCC 核心思想 二.TCC 实现原理 1.TCC 核 ...

  2. 手写基于Spring Cloud的TCC分布式事务框架

    如何简单实现TCC分布式事务框架 最近听到很多其他公司的小伙伴谈分布式事务的问题,各种业务场景都有,可能就是这两年很多公司都在往微服务发展,现在各个子系统都拆分.建设的差不多了,实现了模块化开发,但是 ...

  3. 分布式事务专题(四):分布式事务解决方案之TCC

    目录: 基础概念 分布式事务理论 分布式事务解决方案之2pc 分布式事务解决方案之TCC(本章) 分布式事务解决方案之可靠消息最终一致性 分布式事务解决方案之最大努力通知 分布式事务综合案例分析 4. ...

  4. 分布式事务(4)之TCC解决方案

    目录​​​​​ 一.什么是TCC事务 二.TCC解决方案 2.1空回滚 2.2幂等 2.3悬挂 三.Hmily实现TCC分布式事务 3.1业务说明 3.2程序组成部分 3.3 创建数据库 3.5dis ...

  5. Seata聚合 AT、TCC、SAGA 、 XA事务模式打造一站式的分布式事务解决方案

    Seata Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一 ...

  6. 【分布式事务】tcc-transaction分布式TCC型事务框架搭建与实战案例(基于Dubbo/Dubbox)...

    一.背景 有一定分布式开发经验的朋友都知道,产品/项目/系统最初为了能够快速迭代上线,往往不太注重产品/项目/系统的高可靠性.高性能与高扩展性,采用单体应用和单实例数据库的架构方式快速迭代开发:当产品 ...

  7. 分布式事务(三):分布式事务解决方案之TCC(Try、Confirm、Cancel)

    什么是TCC TCC是Try.Contirm.Cancel三个词语的缩写,TCC要求每个 分支事务实现三个操作:预处理Try.确认Contirm.撤销Cancel.Try操作业务检查以及资源预留,Co ...

  8. 如何选择分布式事务解决方案?

    简介: 分布式事务中涉及的参与者分布在异步网络中,参与者通过网络通信来达到分布式一致性,网络通信不可避免出现失败.超时的情况,因此分布式事务的实现比本地事务面临更多的困难.本文归纳总结五种分布式事务解 ...

  9. 鸟瞰 MySQL,唬住面试官!

    导读:本文从MySQL架构.MySQL日志.MySQL的MVCC.MySQL索引.MySQL语法分析及优化.执行计划和慢查询日志.主从备份.分布式事务等方面进行了体系化的讲述. 今晚20:30-21: ...

最新文章

  1. 大厂常见笔试题 滑动窗口内数的和
  2. ModbusPoll及ModbusSlave安装及使用指南
  3. ITK:过滤器Filter和ParallelizeImageRegion比较
  4. 2.12linux csf 防火墙 防止少量的ddos cc攻击
  5. python初学小甲鱼_Python零基础入门学习 作者:小甲鱼
  6. python设函数解方程_如何在Python函数最小化中获得速度,求椭球方程解
  7. CF1427F Boring Card Game
  8. Docker容器运行
  9. npm install报错的一天
  10. 英特尔发布第三代全新可扩展处理器,加速5G网络转型
  11. mysql 5.7.15-winx64_mysql 5.7.15 winx64安装配置方法
  12. 前端html网页,点击按钮或超链接 弹出 一个登陆的div窗口或者对话框
  13. scroll-view的描点跳转
  14. 动易安全开发手册 完整版
  15. ubuntu 外接显示器设置
  16. OJ每日一练——奥运奖牌计数
  17. 软考信息系统项目管理师_历年真题_2021上半年错题集_前35道选择题---软考高级之信息系统项目管理师035
  18. SQ01SQ02相关处理与增强
  19. 2023年软考成绩什么时候出?软考成绩公布时间间隔多久
  20. WebDAV之葫芦儿•派盘+Notability

热门文章

  1. Solr笔记--转载
  2. String之contains方法
  3. 观赛S11的正确打开方式,三星玄龙骑士已经为你准备好了
  4. hadoop java move_Hadoop上Data Locality的详解
  5. 什么是政府采购?政府采购需要具备哪些资格条件?
  6. 基于VMware的虚拟机资源池实现(下)-运营资源池
  7. vmware资源管理-2
  8. linux一键安装php环境
  9. 淘宝爬虫不可信先要登录
  10. antd 表格添加合计行