文章目录
前言
io.seata.tm.api.TransactionalTemplate
的execute
方法,是Seata客户端事务模型的体现:
本篇主要介绍客户端开启事务,执行事务,提交/回滚与服务端的交互。
一、beginTransaction
beginTransaction
是开启事务的方法,其中tx.begin(txInfo.getTimeOut(), txInfo.getName());
是真正和服务端通信,开启事务的方法,而triggerBeforeBegin();
和triggerAfterBegin();
则是TransactionHookManager
的两个钩子方法,作为开启事务前后的一些处理:
1.1、扩展点:TransactionHook
事务钩子存放在TransactionHookManager
的LOCAL_HOOKS
中,和当前线程是绑定的。
如果需要自定义,需要实现TransactionHook
接口,重写其中的方法:
public class MyTransactionHook implements TransactionHook {
@Override
public void beforeBegin() {
System.out.println("Global Transaction is about to begin...");
}
@Override
public void afterBegin() {
System.out.println("Transaction started.");
}
@Override
public void beforeCommit() {
System.out.println("Transaction commit about to happen...");
}
@Override
public void afterCommit() {
System.out.println("Transaction committed.");
}
@Override
public void beforeRollback() {
System.out.println("Transaction rollback about to happen...");
}
@Override
public void afterRollback() {
System.out.println("Transaction rolled back.");
}
}
并且还要将其注册到TransactionHookManager
中:(调用io.seata.tm.api.transaction.TransactionHookManager#registerHook),并且@GlobalTransactional方法执行时,Hook必须已在同一个线程中注册好。
可以通过Spring aop扩展点实现:
@Aspect
@Component
public class SeataHookRegisterAspect {
@Autowired
private MyTransactionHook myTransactionHook;
@Pointcut("@annotation(io.seata.spring.annotation.GlobalTransactional)")
public void pt() {}
@Around("pt()")
public Object registerHookAndProceed(ProceedingJoinPoint pjp) throws Throwable {
// 注册 Hook(在当前线程)
TransactionHookManager.registerHook(myTransactionHook);
try {
return pjp.proceed();
} finally {
// 清理 Hook,防止线程复用污染
TransactionHookManager.clear();
}
}
}
1.2、begin
在begin
方法中,大致可以分为三个步骤:
- 从上下文中获取XID。如果能获取到,就抛出异常(有XID表示当前线程已经存在事务)。
- 与服务端交互,开启事务,获得服务端返回的XID。
- 将XID绑定到当前线程中。
为什么从上下文中获取XID。如果能获取到,就直接抛出异常?不需要考虑事务传播级别吗?
关于这个问题,首先来看一下,下图是Spring事务的传播行为:
下图是Seata事务的传播行为:
仔细观察,有没有发现Seata的少了一个NESTED
嵌套事务?NESTED
嵌套事务是基于数据库的save point实现的** Seata 本身是基于“全局唯一事务上下文(XID)”来控制整个分布式事务流程的,它并不像数据库那样支持隔离级别配置或嵌套事务,Seata 的设计是每个线程中同时只能有一个全局事务。**一旦发现当前线程的 RootContext 中已经有 XID,意味着这个线程已经处于某个全局事务中,不允许再嵌套开启另一个全局事务。
在begin方法中,会去请求Seata服务端,开启事务并拿到XID。如果服务端返回的是失败,则直接抛出异常。
二、commitTransaction
事务的提交,分为两种情况,第一种是正常提交,第二种是业务执行的过程中出现了异常。但是异常不在处理范围内,体现在completeTransactionAfterThrowing
方法中:
在提交事务之前,首先还会判断是否超时(默认60000ms),如果超时了一样会回滚。
在真正执行事务提交之前和之后,还会有两个钩子方法,作为扩展点。
2.1、tx.commit()
事务提交之前,首先会去判断当前的角色,是否是事务的发起者TM,如果是事务的参与者RM,则不能进行提交。
事务的提交加入了重试机制。
commit
方法同样是去向Seata服务端发起请求,最终得到一个状态。
最后还会调用suspend
方法清理当前线程的事务上下文:
事务提交后,还会进行一系列的判断。
首先拿到afterCommitStatus
,这个afterCommitStatus
实际上就是服务端返回的状态,是DefaultGlobalTransaction
的status
属性:
然后将全局事务的最终状态映射为TransactionalExecutor.Code
类型,供业务层判断事务的最终执行结果:
TimeoutRollbacking → Rollbacking
:当前全局事务已经超时,并正在被TC触发自动回滚中。此时commit()发起失败,TC 决定自动回滚。TimeoutRollbacked → RollbackDone
:当前事务已经因为超时而自动回滚成功,业务提交之前事务已经终止。Finished → CommitFailure
:意味着事务已经终止,但不是正常提交成功的状态,可能是本地以为提交成功,但 TC 可能并未完成事务提交,或提交失败。default
:默认为提交成功。
最终会对异常状态进行处理:
- 两阶段提交发生了不一致,事务已经终止,但不是正常提交成功的状态。
- 业务还未提交前,TC 已经因为超时将其回滚。
2.2、rollbackTransaction
当执行目标逻辑发生了异常,并且异常类型可以被处理,就会执行rollbackTransaction
回滚的逻辑。
在rollbackTransaction
中,首先还是在真正执行回滚的前后,插入钩子方法。然后根据服务端返回的状态,去映射为TransactionalExecutor.Code
类型,最终会抛出异常。
tx.rollback();
的关键点,和tx.commit()
是相同的。
总结
Seata 客户端(微服务端)在事务处理上遵循与传统事务编程模型一致的理念,这与 Spring 的编程风格保持高度契合。不同之处在于:事务的开启、提交以及回滚并非本地直接完成,而是通过与 Seata 事务协调器(TC)进行通信,由服务端统一调度管理。
在整个事务生命周期中,XID(全局事务ID) 是唯一标识符,作为贯穿始终的上下文信息,它驱动各个参与的微服务执行具体的分支事务逻辑。微服务在完成本地事务操作后,需将处理结果(如分支提交或回滚)上报至 TC,由 TC 汇总所有分支状态并决定最终的全局事务走向。
此外,Seata 在事务处理流程中提供了相应的钩子方法(如 beforeBegin、afterCommit、afterRollback 等),允许开发者通过子类或扩展方式注入自定义逻辑,增强事务控制的灵活性与可扩展性。