主页

索引

模块索引

搜索页面

TCC 事务

可靠消息队列缺少隔离性的问题:

在线书店的场景事例中,如果缺乏了隔离性,就会带来一个显而易见的问题:超售。
在书店的业务场景下,很有可能会出现这样的情况:
    两个客户在短时间内都成功购买了同一件商品
    而且他们各自购买的数量都不超过目前的库存,但他们购买的数量之和,却超过了库存

TCC: Try-Confirm-Cancel:

除可靠消息队列以外的另一种常见的分布式事务机制
它天生适合用于需要强隔离性的分布式事务中。

备注

在具体实现上,TCC 的操作其实有点儿麻烦和复杂,它是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为 “预留业务资源” 和 “确认 / 释放消费资源” 两个子过程。

TCC 的实现过程分为了三个阶段:

Try
    尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好事务需要用到的所有业务资源(保障隔离性)
Confirm
    确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。
    注意,Confirm 阶段可能会重复执行,因此需要满足幂等性。
Cancel:
    取消执行阶段,释放 Try 阶段预留的业务资源。
    注意,Cancel 阶段也可能会重复执行,因此也需要满足幂等性。
https://img.zhaoweiguo.com/knowledge/images/architectures/distributes/transaction6.jpg

TCC 事务操作时序

实例演示TCC 的执行过程:

1. 最终用户向 Fenix's Bookstore 发送交易请求
   购买一本价值 100 元的《深入理解 Java 虚拟机》

2. 创建事务,生成事务 ID,记录在活动日志中,进入 Try 阶段
    用户服务:检查业务可行性,
        可行的话,把该用户的 100 元设置为 “冻结” 状态,通知下一步进入 Confirm 阶段
        不可行的话,通知下一步进入 Cancel 阶段
    仓库服务:检查业务可行性
        可行的话,将该仓库的 1 本《深入理解 Java 虚拟机》设置为 “冻结” 状态,通知下一步进入 Confirm 阶段
        不可行的话,通知下一步进入 Cancel 阶段
    商家服务:检查业务可行性,不需要冻结资源

3. 如果第二步中所有业务都反馈业务可行,就将活动日志中的状态记录为 Confirm,进入 Confirm 阶段
    用户服务:完成业务操作(扣减被冻结的 100 元)
    仓库服务:完成业务操作(标记那 1 本冻结的书为出库状态,扣减相应库存)
    商家服务:完成业务操作(收款 100 元)

4. 如果第三步的操作全部完成了,事务就会宣告正常结束

异常处理:
    1. 如果第三步中的任何一方出现了异常,不论是业务异常还是网络异常
        将会根据活动日志中的记录,来重复执行该服务的 Confirm 操作,即进行 “最大努力交付”
    2. 如果是在第二步,有任意一方反馈业务不可行,或是任意一方出现了超时
       将活动日志的状态记录为 Cancel,进入 Cancel 阶段:
        用户服务:取消业务操作(释放被冻结的 100 元)
        仓库服务:取消业务操作(释放被冻结的 1 本书)
        商家服务:取消业务操作(大哭一场后安慰商家谋生不易)
    3. 如果上一步全部完成了,事务就会宣告以失败回滚结束。
       而如果第五步中的任何一方出现了异常,不论是业务异常还是网络异常
       也都将会根据活动日志中的记录,来重复执行该服务的 Cancel 操作,即进行 “最大努力交付”

备注

TCC 其实有点类似于 2PC 的准备阶段和提交阶段,但 TCC 是位于用户代码层面,而不是在基础设施层面,这就为它的实现带来了较高的灵活性,我们可以根据需要设计资源锁定的粒度。另外,TCC 在业务执行的时候,只操作预留资源,几乎不会涉及到锁和资源的争用,所以它具有很高的性能潜力

备注

由于 TCC 的业务侵入性比较高,需要开发编码配合,在一定程度上增加了不少工作量。所以,通常我们并不会完全靠裸编码来实现 TCC,而是会基于某些分布式事务中间件(如阿里开源的 Seata)来完成,以尽量减轻一些编码工作量。

TCC缺点:

TCC 最主要的限制是它的业务侵入性很强,带来它要求有很强的技术可控性
比如有一个服务是调用支付宝的支付,我们没法让支付宝配合做「资金冻结」之类的操作

这种情况只能考虑采用另外一种柔性事务方案:SAGA 事务

主页

索引

模块索引

搜索页面