5.3.2 TCC

可靠消息队列虽然能保证最终结果的相对可靠性,过程也足够简单,但整个过程完全没有隔离性可言,如果你的业务需要隔离,那你就应该考虑笔者下面介绍的适合强隔离性的分布式事务 -- TCC(“Try-Confirm-Cancel”三个单词的缩写)。

TCC 事务模型最早出现在 2007 年数据库专家 Pat Helland 发表了一篇名为 “Life beyond Distributed Transactions:an Apostate’s Opinion” 的论文中[1],不过该论文中 TCC 还是以 Tentative-Confirmation-Cancellation 作为名称,在国内经历阿里程立博士的传道之后,TCC 逐渐被大家广为了解并接受。

TCC 是一种对业务侵入性较强的事务方案,要求业务处理过程中必须拆分为”预留业务资源“、”确认/释放消费资源“两个子过程,两个子过程细化为如下三个阶段:

  • Try 阶段:尝试执行阶段,完成所有业务可执行检查(预留必须业务资源,保障隔离性)。
  • Confirm 阶段:如果所有分支的 Try 都成功了,则走到 Confirm 阶段。Confirm 真正执行业务,使用 Try 阶段预留的业务资源来完成处理。
  • Cancel 阶段:如果所有分支的 Try 有一个失败了,则走到 Cancel 阶段,释放 Try 阶段预留的业务资源。

按照 TCC 的协议规定,Confirm 和 Cancel 阶段只返回成功,不会返回失败,如果由于网络问题或者服务器临时故障,那么事务管理器会进行重试直至最终成功,所以这两个阶段的操作要求具备幂等性。笔者继续以前篇下单为案例,说明 TCC 事务处理流程,不过我们稍微简化下逻辑,案例中去除不那么重要的积分服务,只保留支付、仓库服务。

  1. 用户向商店发送一个交易请求,譬如购买一支价值 ¥100 的钢笔。
  2. 创建事务,生成事务 ID,记录在活动日志中,进入 Try 阶段。
    • 支付服务:检查业务可行性,若可行,将用户的 100 元设置为冻结状态,通知下一步进行 Confirm 阶段;若不可行,通知下一步进入 Cancel 阶段。
    • 仓库服务:检查业务可行性,若可行,将仓库该商品的其中一条库存设置为冻结状态,通知下一步进行 Confirm 阶段;若不可行,通知下一步进入 Cancel 阶段。
  3. 如果以上所有业务反馈业务可行,将活动日志中的状态记录为 Confirm,进入 Confirm 阶段。
    • 支付服务:完成业务操作,扣减之前冻结的 100 元。
    • 仓库服务:完成业务操作,标记之前冻结的库存为出库状态,并扣减库存。
  4. 如果第 3 步全部正常完成,则整个事务顺利结束,如果第 3 步任意一方出现异常,将根据活动日志中的记录,重复执行异常方的 Confirm 阶段,进行最大努力交付尝试。
  5. 如果第 2 步有任意业务方反馈业务处理失败,则将活动日志状态记为 Cancel,进入 Cancel 阶段。
    • 支付服务:取消业务操作,释放被冻结的 100 元。
    • 仓库服务:取消业务操作,释放被冻结的库存。
  6. 如果第 5 步全部完成,事务宣告以失败回滚结束,如果第 5 步中有任何异常,将根据活动日志中的记录,重复执行异常方的 Cancel 阶段,进行最大努力交付尝试。

由上述操作过程可见,TCC 其实有点类似 2PC 的准备阶段和提交阶段,但 TCC 位于用户代码层面,而不是在基础设施层面,这为它的实现带来了较高的灵活性,可以根据需要设计资源锁定的粒度。不过呢,感知各个阶段的执行情况以及推进执行下一个阶段需要编写大量的逻辑代码,不只是调用一下 Confirm/Cancel 那么简单。通常的情况,我们不需要靠裸编码来实现 TCC,而是引入某些分布式事务中间件(譬如 Seata、ByteTCC)来降低编码工作,提升开发效率。


  1. 参见 http://adrianmarriott.net/logosroot/papers/LifeBeyondTxns.pdf ↩︎

总字数:1118
Last Updated:
Contributors: isno