TCC 事务 ======== 可靠消息队列缺少隔离性的问题:: 在线书店的场景事例中,如果缺乏了隔离性,就会带来一个显而易见的问题:超售。 在书店的业务场景下,很有可能会出现这样的情况: 两个客户在短时间内都成功购买了同一件商品 而且他们各自购买的数量都不超过目前的库存,但他们购买的数量之和,却超过了库存 TCC: Try-Confirm-Cancel:: 除可靠消息队列以外的另一种常见的分布式事务机制 它天生适合用于需要强隔离性的分布式事务中。 .. note:: 在具体实现上,TCC 的操作其实有点儿麻烦和复杂,它是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为 “预留业务资源” 和 “确认 / 释放消费资源” 两个子过程。 TCC 的实现过程分为了三个阶段:: Try 尝试执行阶段,完成所有业务可执行性的检查(保障一致性),并且预留好事务需要用到的所有业务资源(保障隔离性) Confirm 确认执行阶段,不进行任何业务检查,直接使用 Try 阶段准备的资源来完成业务处理。 注意,Confirm 阶段可能会重复执行,因此需要满足幂等性。 Cancel: 取消执行阶段,释放 Try 阶段预留的业务资源。 注意,Cancel 阶段也可能会重复执行,因此也需要满足幂等性。 .. figure:: 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 操作,即进行 “最大努力交付” .. note:: TCC 其实有点类似于 2PC 的准备阶段和提交阶段,但 TCC 是位于用户代码层面,而不是在基础设施层面,这就为它的实现带来了较高的灵活性,我们可以根据需要设计资源锁定的粒度。另外,TCC 在业务执行的时候,只操作预留资源,几乎不会涉及到锁和资源的争用,所以它具有很高的性能潜力 .. note:: 由于 TCC 的业务侵入性比较高,需要开发编码配合,在一定程度上增加了不少工作量。所以,通常我们并不会完全靠裸编码来实现 TCC,而是会基于某些分布式事务中间件(如阿里开源的 Seata)来完成,以尽量减轻一些编码工作量。 TCC缺点:: TCC 最主要的限制是它的业务侵入性很强,带来它要求有很强的技术可控性 比如有一个服务是调用支付宝的支付,我们没法让支付宝配合做「资金冻结」之类的操作 这种情况只能考虑采用另外一种柔性事务方案:SAGA 事务