该系列参考:
https://www.jianshu.com/p/962271bbf4ea
https://blog.csdn.net/a745233700/article/details/122402303
https://cloud.tencent.com/developer/article/2048776
https://icyfenix.cn/
TCC 是“Try-Confirm-Cancel”三个单词的缩写
最核心的思想:就是在应用层将一个完整的事务操作分为三个阶段。在某种程度上讲,TCC 是一种资源,实现了 Try、Confirm、Cancel 三个操作接口。与传统的两阶段提交协议不同的是,TCC 是一种在应用层实现的两阶段提交协议,在 TCC 分布式事务中,对每个业务操作都会分为 Try、Confirm 和 Cancel 三个阶段,每个阶段所关注的重点不同。
(来自凤凰架构)

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

空回滚情况: 上方调用分支按照TCC流程正常执行,此时下方调用分支因为某种原因而阻塞了,由于长时间没有执行,这个分支发生了超时错误,由TM经过2.1步骤发送超时错误,回滚全局事务的指令给TC,TC检查分支状态2.2,发现确实有一只分支超时,发送2.3回滚指令到各分支的RM,由RM执行2.4 cancel操作。 此时对于第一个分支而言,执行cancel没有问题,因为流程正常。但对于第二个分支而言,他并没有执行第一步的try,所以此时第二个分支不能真正的执行cancel,需要执行空回滚,也就是说返回一个正常状态,且不报错。
解决:需要在cancel之前查看是否有前置的try,如果没有执行try则需要空回滚。
对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂

业务悬挂情况: 假设在上方的基础上,下方分支的阻塞畅通了,此时他执行1.4去锁定资源(try),但整个事务都已经回滚结束了,所以他不会执行第二阶段,但冻结了资源,这种情况应该进行避免。
解决:需要在try操作之前查看当前分支是否已经回滚过,如果已经回滚过则不能在执行try命令。
Confim、Cancel阶段不论网络原因或者业务异常,都会根据日志记录重复执行操作,所以需要保持接口的幂等性,也就是最多执行一次
DEMO详情见 : https://github.com/seata/seata-samples
Seata文档详情见: https://seata.io/zh-cn/docs/overview/what-is-seata.html
接口准备:
String xid = RootContext.getXID())@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
public void deduct(
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money") int money
);
public boolean confirm(BusinessActionContext context);
public boolean cancel(BusinessActionContext context);
}