TCC型分布式事务原理和实现之:TransactionManager

本文分享了TCC分布式事务框架的设计思想。该框架由作者在阅读开源代码和JTA设计思想后修改而来。介绍了事务管理器的核心作用,包括事务的提交和回滚,还阐述了其多个方法的功能,如begin、commit等,同时提到用Compensable注解标记TCC事务方法及相关切面。

前言
目前,还没有一款商用成熟的开源TCC框架,所以很多人基于不同思想实现了TCC的很多版本,本文所分析的TCC框架,是笔者在读了一些TCC开源代码、JTA设计思想之后逐步修改而来,由于代码还在逐步优化中,但是大体已经成型,所以将TCC框架的一点设计思想分享出来。

核心类图

事务管理器(TransactionManager)作为TCC分布式事务的核心组件,负责事务的管理工作(包括事务的提交和回滚)。 每一个事务的参与者(Participant)中都会有一个单例的事务管理器负责本地事务的管理工作。在TCC的try阶段,根事务(ROOT,表示事务的发起方)参与者中的事务管理器会负责创建根事务并持久化事务日志,同时会将创建的事务保存在ThreadLocal类型的队列中,处于分支事务参与者中的事务管理器会直接从事务上下文中传播(propagation)一个新的分支事务,同时也会持久化事务日志并将事务加入ThreadLocal队列中。在根事务端,try阶段结束后,TCC框架会根据try阶段是否有异常分别自动调用根事务管理器的commit和rollback方法,以commit为例,根事务管理器的commit又会调用根事务(transaction)的commit方法,根事务(transaction)的commit方法会遍历所有的事务参与者(Participant)的commit方法,这里的参与者一共有两种,分别为根事务参与者和分支事务参与者,这里的所谓的“根”和“分支”都只是逻辑上角色的区分,表示主动发起和被动参与的区别,它们在代码层面完全是共用一套逻辑的,比如,一个分支事务同样可以发起根事务,这样就是典型的嵌套型事务了。但是,整个事务的提交和回滚一定是由最顶层事务管理器发起的,其他的分支事务(包括多层嵌套事务)都是在最顶层根事务管理器的协调下完成自身的提交和回滚。至此,一个完整的分布式事务就结束了,当然,这只是一个大概的过程描述,还有很多细节没有提及,主要是目前的讨论还没有限制SOA框架,所以无法谈论具体的远程事务细节,等到后面专门讨论远程事务的文章(SOA为dubbo)时,会详细讨论根事务参与者和分支事务参与者。

事务管理器
事务管理器的属性只有transactionRepository和CURRENT,其中transactionRepository用于持久化事务日志,CURRENT用于保存该事务管理器上活动的事务,它是一个ThreadLocal队列。

  事务管理器具有较多的方法,下面分表分析:

begin()
begin()表示开始一个事务,会在根事务(ROOT)的try方法中被调用。

public Transaction begin() {
    // 创建事务,事务类型为根事务ROOT
    Transaction transaction = new Transaction(TransactionType.ROOT);
    // 事务日志持久化
    transactionRepository.create(transaction);
    // 注册事务,就是加到线程局部变量的队列中
    registerTransaction(transaction);
    return transaction;
}

registerTransaction()
private void registerTransaction(Transaction transaction) {
// 如果队列还没有创建就先创建一个
if (CURRENT.get() == null) {
CURRENT.set(new LinkedList());
}
// 加入事务队列
CURRENT.get().push(transaction);
}
propagationNewBegin()
propagationNewBegin用于从一个事务上下文中传播一个新事务,通常会在分支事务(比如dubbo中的provider端)的try阶段被调用,此处的事务上下文可以使用方法传参,也可以使用特定SOA框架的隐式传参(比如dubbo)。

public Transaction propagationNewBegin(TransactionContext transactionContext) {
// 从transactionContext创建一个事务
Transaction transaction = new Transaction(transactionContext);
// 事务日志持久化
transactionRepository.create(transaction);
// 注册事务到事务管理器
registerTransaction(transaction);
return transaction;
}
propagationExistBegin()
propagationExistBegin用于从事务上下文中传播一个已存在的事务,通常会在分支事务(比如dubbo中的provider端)的confirm阶段和cancel阶段被调用,此处的事务上下文可以使用方法传参,也可以使用特定SOA框架的隐式传参(比如dubbo)。

public Transaction propagationExistBegin(TransactionContext transactionContext) throws NoExistedTransactionException {
// 从持久化日志中根据事务id拿到事务
Transaction transaction = transactionRepository.findByXid(transactionContext.getXid());
// 如果找到了事物
if (transaction != null) {
// 更改事务状态为transactionContext中的状态
transaction.changeStatus(TransactionStatus.valueOf(transactionContext.getStatus()));
// 注册事务
registerTransaction(transaction);
return transaction;
} else {
throw new NoExistedTransactionException();
}
}
commit()
commit会在事务try阶段没有异常的情况下,由TCC框架自动调用。它首先从ThreadLocal队列中取出当前要处理的事务(但不从队列中删除这个事务),然后将事务状态改为CONFIRMING状态,更新事务日志。随后调用事务的commit方法进行事务提交处理,如果事务提交成功(没有抛出任何异常),那么就从事务日志仓库中删除这个事务日志。如果在事务commit过程中抛出了异常,那么这个事物日志此时不会被删除(稍后会被recovery任务处理),同时,框架会将异常全部转为ConfirmingException向业务层抛出。

public void commit() {
// 获取本地线程上事务队列中的时间最久的事务
Transaction transaction = getCurrentTransaction();
if(transaction == null){
return;
}
// 更改事务状态为CONFIRMING
transaction.changeStatus(TransactionStatus.CONFIRMING);
// 更新事务持久化
transactionRepository.update(transaction);

    try {
        // 调用事务的commit
        transaction.commit();
        // 如果上面的commit没有抛出任何异常就说明事务成功,就从事务日志中删除这个事务
        transactionRepository.delete(transaction);
    } catch (Throwable commitException) {
        // 事务commit过程抛出了异常
        logger.error("compensable transaction confirm failed.", commitException);
        // 转为抛出ConfirmingException异常,这样会导致事务在事务日志中不被删除,recovery会去处理长时间没有被删除的事务
        throw new ConfirmingException(commitException);
    }
}

getCurrentTransaction()
获取当前要处理的事务,此处要注意的是,队列的peek只是取出队列头部元素,但是不会将其删除。

public Transaction getCurrentTransaction() {
    if (isTransactionActive()) {
        // 拿到队列头的事务(但是不从队列中删除,删除是在cleanAfterCompletion中进行)
        return CURRENT.get().peek();
    }
    return null;
}

isTransactionActive()
判断当前事务管理中是否还有活动的事务。

public boolean isTransactionActive() {
Deque transactions = CURRENT.get();
return transactions != null && !transactions.isEmpty();
}
rollback()
rollback和commit相对,当try阶段抛出了任何异常,TCC框架会自动调用。它首先从事务管理器中取出当前活动的事务,更改事务状态为CANCELLING,并更新事务日志。然后调用事务的rollback进行事务回滚(事务的rollback会遍历所有参与者,并分别调用参与者的rollback,通常,根事务端的参与者包含根事务参与者和分支事务参与者,而分支事务参与者通常只有一个本地的事务参与者,除非它也发起了TCC分布式事务)。如果rollback成功,事务会被从事务日志持久化仓库中删除,否则直接向业务层代码抛出CancellingException异常,残留的事务日志稍后会被recovery任务处理(可选、可配置)。

public void rollback() {
// 回滚事务
Transaction transaction = getCurrentTransaction();
// 更改事务状态为CANCELLING
transaction.changeStatus(TransactionStatus.CANCELLING);
// 更新事务持久化日志
transactionRepository.update(transaction);

    try {
        // 调用事务的rollback
        transaction.rollback();
        // 没有异常,就从事务日志中删除这个事务
        transactionRepository.delete(transaction);
    } catch (Throwable rollbackException) {
        logger.error("compensable transaction rollback failed.", rollbackException);
        // 否则事务异常,抛出CancellingException
        throw new CancellingException(rollbackException);
    }
}

cleanAfterCompletion()
还记得前文所说的,每次从事务管理器获取当前活动事务的时候,都不会从队列中将其删除,那么这些事务会在什么时候删除呢,这就是cleanAfterCompletion的作用。在每次事务处理结束时,TCC框架都会调用cleanAfterCompletion进行事务的清理操作。清理之前要比对要清理的事务是不是当前事务。

public void cleanAfterCompletion(Transaction transaction) {
if (isTransactionActive() && transaction != null) {
Transaction currentTransaction = getCurrentTransaction();
if (currentTransaction == transaction) {
CURRENT.get().pop();
} else {
throw new SystemException(“Illegal transaction when clean after completion”);
}
}
}
enlistParticipant()
enlistParticipant用于向事务中添加一个事务参与者,同上,这里的参与者包含了本地参与者和远程参与者,添加参与者之后必须更新事务日志。enlistParticipant会在添加到TCC事务方法的切面中被调用。

public void enlistParticipant(Participant participant) {
Transaction transaction = this.getCurrentTransaction();
transaction.enlistParticipant(participant);//将参与者加入事务的参与者列表中
transactionRepository.update(transaction);// 更新事务日志
}
标记TCC事务方法
本文多次提到了TCC事务方法和切面逻辑,那么什么是一个TCC事务方法呢?给一个方法添加标记肯定要使用注解了,TCC框架中,使用Compensable注解来表示一个方法是一个TCC事务方法,同时TCC框架针对标记了Compensable注解的方法提供了两个切面:CompensableTransactionAspect和ResourceCoordinatorAspect。其中CompensableTransactionAspect用于封装事务逻辑(事务开启、提交和回滚等),而ResourceCoordinatorAspect切面用于封装一个参与者并添加到事务中(上文提及)。由于还有专门的文章介绍这两个切面,所以本文暂时不作深入讨论。这里需要注意的是,这两个切面是有优先级排序的,CompensableTransactionAspect优先级高于ResourceCoordinatorAspect,这个只要实现spring框架中的Ordered接口就可以了。

相关文章

<think>好的,我现在需要回答用户关于Spring框架中本地事务分布式事务的区别及实现方法的问题。首先,我得理清这两个概念的基本定义它们在Spring中的处理方式。 本地事务通常涉及单一数据库,遵循ACID特性,而分布式事务则跨多个资源,比如多个数据库或微服务。用户可能想知道它们的主要区别,比如作用范围、一致性的保证方式,以及实现时的不同机制。 接下来,我需要回顾Spring中的事务管理机制。记得Spring通过PlatformTransactionManager接口抽象事务管理,对于本地事务,可能是使用DataSourceTransactionManager,而分布式事务可能需要JtaTransactionManager。这里要确保引用的资料是否正确,比如引用[4]提到Spring委托事务管理给Hibernate或JTA,所以需要正确引用。 然后,分布式事务实现方式部分,用户可能对XA模式、AT模式、TCC等感兴趣。根据用户提供的引用,比如引用[3]解释了XA模式的两个阶段,引用[5]提到Seata支持多种模式,需要将这些内容整合进去,说明不同模式的实现原理适用场景。 在比较部分,需要明确本地分布式事务在资源数量、一致性、性能等方面的差异。比如本地事务性能更好,而分布式事务因为协调开销性能较差。同时,需要提到不同的解决方案,如两阶段提交、Seata等,结合引用[2]中的内容,说明选择依据。 最后,生成相关问题时要确保覆盖用户可能进一步询问的方向,比如如何选择事务模式、XA与AT的区别,以及Spring Cloud集成Seata的步骤等。检查引用是否恰当,比如引用[1][5]都涉及Seata,需要正确关联。 现在需要确保回答结构清晰,分点说明,并且正确应用LaTeX格式。比如,提到ACID时用行内公式$ACID$,独立公式用$$...$$,但可能这里不需要太多数学公式,主要注意术语的格式。同时,代码块部分如果有的话要正确使用语法,但用户的问题可能不需要代码示例,所以可能不需要添加。 最后检查引用标识是否与内容对应,比如引用[3]对应XA模式的阶段,引用[5]对应Seata的不同模式。确保每个引用都自然衔接,没有错误。回答完成后,生成的相关问题要围绕主题,帮助用户深入理解。</think>### Spring框架中本地事务与分布式事务的区别及实现方法 #### 一、本地事务与分布式事务的核心区别 1. **作用范围** - **本地事务**:基于单一数据库实例,确保事务的$ACID$特性(原子性、一致性、隔离性、持久性),仅涉及单个资源[^4]。 - **分布式事务**:跨多个数据库或微服务,需要协调多个资源(如多个数据库、消息队列等)的一致性[^1][^2]。 2. **一致性实现方式** - **本地事务**:直接依赖数据库的事务管理机制(如MySQL的InnoDB引擎)。 - **分布式事务**:需引入分布式事务协议(如两阶段提交、三阶段提交)或框架(如Seata)来协调多节点操作[^3][^5]。 3. **性能与复杂度** - 本地事务性能高、实现简单;分布式事务因需跨网络协调,存在延迟复杂度高的问题。 --- #### 二、本地事务的实现方法(Spring框架支持) 1. **声明式事务管理** 通过`@Transactional`注解实现,底层依赖`PlatformTransactionManager`接口。 ```java @Service public class UserService { @Transactional public void updateUser(User user) { // 数据库操作 } } ``` - **核心组件**: - `DataSourceTransactionManager`:用于JDBC或MyBatis等基于单一数据源的事务管理[^4]。 2. **编程式事务管理** 手动控制事务边界,适用于复杂逻辑场景。 ```java @Autowired private PlatformTransactionManager transactionManager; public void executeWithTransaction() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // 业务逻辑 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); } } ``` --- #### 三、分布式事务实现方法 1. **基于XA协议的两阶段提交(2PC)** - **原理**: - **一阶段(准备)**:所有参与者执行操作但不提交,返回就绪状态。 - **二阶段(提交/回滚)**:协调者根据参与者状态决定全局提交或回滚。 - **Spring集成**:通过`JtaTransactionManager`支持(如Atomikos、Narayana)。 2. **Seata框架(AT/TCC/Saga模式)** - **AT模式(自动补偿)**: 通过全局锁机制实现数据快照,自动生成反向SQL补偿操作[^5]。 ```java @GlobalTransactional public void distributedOperation() { serviceA.update(); serviceB.update(); } ``` - **TCC模式(手动补偿)**: 需开发者实现`Try`、`Confirm`、`Cancel`接口,适用于高一致性要求的场景。 3. **消息最终一致性(异步补偿)** - 通过消息中间件(如RocketMQ)实现异步通知,确保最终一致性。 --- #### 四、对比与选建议 | **维度** | **本地事务** | **分布式事务** | |------------------|----------------------------|---------------------------| | 适用场景 | 单数据库操作 | 跨服务、跨数据库操作 | | 性能 | 高(无网络协调) | 低(需多节点通信) | | 一致性强度 | 强一致性 | 最终一致性或弱一致性[^2] | | 实现复杂度 | 低 | 高(需引入框架或协议) | **选建议**: - 单数据库场景优先使用本地事务。 - 分布式场景中,高一致性需求选XA或TCC,性能敏感场景选AT或消息队列[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值