Seata TCC 模式中的一个小问题及其解决方法
2023-12-09 03:30:34
在最近的一篇文章中,我探讨了如何使用 Seata TCC 模式在 Spring Cloud 中实现分布式事务。在该过程中,我偶然发现了一个小问题,并向社区提交了一个 issue。在本文中,我将通过源码分析来深入研究这个问题及其解决方案。
问题
在 TCC 模式中,TCC 资源管理器负责协调分布式事务的参与者。在 Seata 中,TCC 资源管理器通过执行 prepare()、commit() 和 rollback() 方法来实现。在特定情况下,当 commit() 方法返回 null 时,Seata 可能会将事务标记为回滚。
源码分析
为了了解这个问题的根本原因,我们深入研究了 Seata TCC 资源管理器的源码。在 Seata 2.0.1 版本中,TCCResourceManagerImpl 类的 commit() 方法如下所示:
public TCCResult commit(BranchType branchType, Object branchId, Object context) throws Throwable {
Branch branch = manager.getBranch(branchId);
if (branch == null) {
throw new InvalidBranchIdException("No branch with branchId " + branchId);
}
TccAction action = (TccAction)branch.getAction();
try {
Object result = action.commit(context);
// Seata 事务回滚条件:commit() 返回 null 时,标记事务为回滚
if (result == null) {
return new TCCResult(true, null);
}
return new TCCResult(false, result);
} catch (Throwable ex) {
return new TCCResult(true, ex);
}
}
从这段代码中,我们可以看到,当 commit() 方法返回 null 时,TCCResourceManagerImpl 会将事务标记为回滚(即第一个 if 语句)。这正是导致问题的根源。
解决方案
为了解决这个问题,Seata 社区在 2.1.0 版本中引入了修复。修复的内容是将 commit() 方法返回 null 时的处理逻辑从回滚修改为不标记为回滚(即取消第一个 if 语句)。
修改后的 commit() 方法如下所示:
public TCCResult commit(BranchType branchType, Object branchId, Object context) throws Throwable {
Branch branch = manager.getBranch(branchId);
if (branch == null) {
throw new InvalidBranchIdException("No branch with branchId " + branchId);
}
TccAction action = (TccAction)branch.getAction();
try {
Object result = action.commit(context);
// 移除 commit() 返回 null 时标记事务为回滚的逻辑
return new TCCResult(false, result);
} catch (Throwable ex) {
return new TCCResult(true, ex);
}
}
通过移除 commit() 返回 null 时标记事务为回滚的逻辑,Seata 可以正确地处理 commit() 返回 null 的情况,而不会将事务标记为回滚。
结论
Seata TCC 模式中存在的这个问题是由 commit() 方法返回 null 时错误地将事务标记为回滚造成的。Seata 社区在 2.1.0 版本中通过修改 commit() 方法返回 null 时的处理逻辑解决了这个问题。通过了解这个问题的根源及其解决方案,我们可以更好地理解 Seata TCC 模式的内部机制,并避免在分布式事务处理中遇到类似的问题。