TCC
TCC 是“Try-Confirm-Cancel”三个单词的缩写 最核心的思想:就是在应用层将一个完整的事务操作分为三个阶段。在某种程度上讲,TCC 是一种资源,实现了 Try、Confirm、Cancel 三个操作接口。与传统的两阶段提交协议不同的是,TCC 是一种在应用层实现的两阶段提交协议,在 TCC 分布式事务中,对每个业务操作都会分为 Try、Confirm 和 Cancel 三个阶段,每个阶段所关注的重点不同。
- Try:尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好全部需用到的业务资源(保障隔离性)。
- Confirm:确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。Confirm 阶段可能会重复执行,因此本阶段所执行的操作需要具备幂等性。
- Cancel: 取消执行阶段,释放 Try 阶段预留的业务资源。Cancel 阶段可能会重复执行,也需要满足幂等性。
凤凰架构)
一、TCC执行过程(来自- 最终用户向 Fenix's Bookstore 发送交易请求:购买一本价值 100 元的《深入理解 Java 虚拟机》。
- 创建事务,生成事务 ID,记录在活动日志中,进入 Try 阶段:
- 用户服务:检查业务可行性,可行的话,将该用户的 100 元设置为“冻结”状态,通知下一步进入 Confirm 阶段;不可行的话,通知下一步进入 Cancel 阶段。
- 仓库服务:检查业务可行性,可行的话,将该仓库的 1 本《深入理解 Java 虚拟机》设置为“冻结”状态,通知下一步进入 Confirm 阶段;不可行的话,通知下一步进入 Cancel 阶段。
- 商家服务:检查业务可行性,不需要冻结资源。
- 如果第 2 步所有业务均反馈业务可行,将活动日志中的状态记录为 Confirm,进入 Confirm 阶段:
- 用户服务:完成业务操作(扣减那被冻结的 100 元)。
- 仓库服务:完成业务操作(标记那 1 本冻结的书为出库状态,扣减相应库存)。
- 商家服务:完成业务操作(收款 100 元)。
- 第 3 步如果全部完成,事务宣告正常结束,如果第 3 步中任何一方出现异常,不论是业务异常或者网络异常,都将根据活动日志中的记录,重复执行该服务的 Confirm 操作,即进行最大努力交付。
- 如果第 2 步有任意一方反馈业务不可行,或任意一方超时,将活动日志的状态记录为 Cancel,进入 Cancel 阶段:
- 用户服务:取消业务操作(释放被冻结的 100 元)。
- 仓库服务:取消业务操作(释放被冻结的 1 本书)。
- 商家服务:取消业务操作(大哭一场后安慰商家谋生不易)。
- 第 5 步如果全部完成,事务宣告以失败回滚结束,如果第 5 步中任何一方出现异常,不论是业务异常或者网络异常,都将根据活动日志中的记录,重复执行该服务的 Cancel 操作,即进行最大努力交付。
二、优缺点
- 优点:
- 整体过程类似2PC,但却是位于代码层面,有较高的灵活性,可以根据需要设计资源锁定粒度,具有很高的性能
- 有较强的的隔离性
- 缺点:
- 代码侵入性强,开发成本高
- 非强一致性,属于补偿事务,实现最终一致
三、存在的三个问题
1.空回滚
当某分支事务的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则需要空回滚。
2.悬挂
对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂
业务悬挂情况: 假设在上方的基础上,下方分支的阻塞畅通了,此时他执行1.4去锁定资源(try),但整个事务都已经回滚结束了,所以他不会执行第二阶段,但冻结了资源,这种情况应该进行避免。
解决:需要在try操作之前查看当前分支是否已经回滚过,如果已经回滚过则不能在执行try命令。
3.幂等性
Confim、Cancel阶段不论网络原因或者业务异常,都会根据日志记录重复执行操作,所以需要保持接口的幂等性,也就是最多执行一次
四、Seata-TCC实现
DEMO详情见 : https://github.com/seata/seata-samples
Seata文档详情见: https://seata.io/zh-cn/docs/overview/what-is-seata.html
接口准备:
- 接口上使用@LocalTCC注解
- Try方法上使用 @TwoPhaseBusinessAction注解, 配置好Try、Confirm、Cancel方法
- 需要传递的参数使用@BusinessActionContextParameter注解配置
- Confirm、Cancel方法需要boolean返回值。
- 实现该接口,编写业务逻辑即可 (事务ID获取方法:
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);
}