还不懂分布式事务:带你深入剖析TCC实现原理

TCC概念

常见的分布式事务实现方案有以下几种:两阶段提交(2PC)、三阶段提交(3PC)、TCC(Try-Confirm-Cancel)、补偿事务(Saga)、MQ事务消息等。

由于2PC资源锁定时间较长,性能较差,难以扩展。TCC旨在解决这些问题,TCC是一种补偿型事务模式,通过将每个事务操作分为三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel),不仅优化了事务的执行流程,还提高了系统的整体效率和弹性。

TCC流程

TCC的核心思想在于将事务的每个操作拆分为三个明确的阶段:尝试(Try)、确认(Confirm)和取消(Cancel)。这三个阶段各自承担不同的职责,确保事务在分布式环境中能够安全且一致地执行。

  • Try 阶段

在这个阶段,事务参与者将执行所有必要的检查,并预留必需的资源来保证事务可以顺利完成。例如,在电商场景,当用户尝试购买商品时,系统将在Try阶段检查商品库存,预留所需数量的商品。这个阶段是整个TCC流程中的准备步骤,它不会做任何实际的业务处理,只是确保后续的Confirm阶段可以无障碍地执行。

  • Confirm 阶段

只有当所有参与者的Try阶段成功完成后,才会进入Confirm阶段。在这个阶段,系统将正式执行业务操作,使用在Try阶段预留的资源。继续举例电商场景,如果用户的支付成功,系统将在Confirm阶段正式从库存中扣除商品,完成交易。这一步骤确保了事务的最终一致性和数据的正确性。

  • Cancel 阶段

如果在Try阶段或Confirm阶段中的任何一个环节发生错误,或者某些业务条件未得到满足,整个事务将进入Cancel阶段。在这个阶段,所有已经预留的资源将被释放,所有在Try阶段所做的准备工作将被撤销。例如,如果用户决定取消购买,或者支付未成功,预留的商品库存将被释放,以便其他用户购买。

电商场景介绍

举一个常见的电商场景,用户购买商品,点击支付后。这时候交易系统需要做的操作是:

  1. 更新订单状态为"已支付"
  2. 扣减库存
  3. 增加用户积分

电商系统一般采用微服务架构,上述三个操作分别需要调用三个服务完成。流程如下:

  1. 订单服务,更新订单状态为"已支付"
  2. 库存服务,扣减库存
  3. 积分服务,增加用户积分

如果这三个操作在一个服务中,就可以使用本地事务保证订单、库存、积分数据的一致性,但是现在需要调用三个服务,就无法保证数据的一致性了。 如果更新订单状态为"已支付"成功了,但是扣减库存失败了,其他用户就可以继续购买商品,可能会出现超卖问题,这时候就需要引入分布式事务,用来保证分布式系统中数据的一致性。

TCC落地电商场景

把上述电商场景引入TCC事务之后,就变成以下情况。

  • Try阶段实现

Try阶段负责资源的预检查和预留,分别调用三个服务冻结资源:

  1. 订单服务,更新订单状态为"支付中"
  2. 库存服务,冻结库存(库存表中,新增一个冻结库存的字段)
  3. 积分服务,预增加用户积分(积分表,新增一个预增加积分的字段)

库存表中,新增了一个冻结库存的字段。在商品详情页面展示剩余库存数量的时候,需要减去冻结库存,防止超卖。

  • Confirm 阶段

当Try阶段都执行成功的时候,就进入Confirm阶段,负责确认资源。流程如下:

  1. 订单服务,更新订单状态为"已支付"
  2. 库存服务,把冻结库存扣减到剩余库存上(update 剩余库存=剩余库存-冻结库存,冻结库存=0)
  3. 积分服务,把预增加积分加到总积分上(udpate 总积分=总积分+预增加积分,预增加积分=0)
  • Cancel阶段

当Try阶段任何一个操作失败,就进入Cancel阶段,负责回滚资源。流程如下:

  1. 订单服务,更新订单状态为"已取消"
  2. 库存服务,释放冻结的库存
  3. 积分服务,释放预增加积分

TCC问题

上述只是理想情况,在实践的过程中会遇到以下三大问题,影响TCC事务的安全性。

  1. Confirm/Cancel操作失败问题
  2. 空回滚问题
  3. 悬挂问题

针对每个问题,我们思考一下相应的解决方案。

1. Confirm/Cancel操作失败问题

在执行完Try阶段之后,进入到Confirm或者Cancel阶段,可能由于服务问题或者网络问题,Confirm或者Cancel操作不一定能成功,通常采用的办法是不断重试,要求Confirm和Cancel接口要支持幂等。

2. 空回滚问题

空回滚问题是指Try阶段某个服务的Try操作没有执行成功或者没有执行,进入Cancel阶段,就执行Cancel操作回滚资源,导致数据错乱。 常用的解决方案是Cancel操作前增加事务状态检查。

  1. 在 Try 操作开始时,设置事务状态为 TRYING。
  2. 如果 Try 操作成功,更新状态为 TRIED。
  3. 在 Cancel 操作执行前,检查状态。如果状态不是 TRIED,就不执行 Cancel 操作。

3. 悬挂问题

悬挂问题是指 Cancel 操作比 Try 操作先完成。通常出现的原因是由于网络延迟,例如:由于调用Try操作耗时较长,出现网络超时,导致Try操作调用失败,就进入Cancel阶段,执行Cancel操作,而此时Try操作还在执行,最后结果是Cancel操作执行成功后,Try操作又执行成功,出现"悬挂"问题。

解决方案有以下几种:

  1. 与空回滚解决方案类似,Cancel操作前增加事务状态检查。
    1. 在 Try 操作开始时,设置事务状态为 TRYING。
    2. 如果 Try 操作成功,更新状态为 TRIED。
    3. 在 Cancel 操作执行前,检查状态。如果状态不是 TRIED,就不执行 Cancel 操作或者延迟执行。
  2. Try操作引入重试机制

如果调用Try操作超时,可以进行有限次重试,而不是立即执行Cancel操作,可以减少因为网络超时而导致的悬挂问题。这里要求Try接口也要支持幂等操作。

  1. 增加同步机制

可以使用分布式锁来控制Try和Cancel操作的执行顺序。

总结

TCC模型将事务分为Try、Confirm和Cancel三个阶段,使得事务处理更加灵活和可控,保证了数据的一致性,减少了2PC资源锁定时间过长的问题。但是也引入了一些新问题:

  1. 代码侵入严重。Try、Confirm和Cancel三个阶段的事务操作都要耦合在业务逻辑中,耦合性较高。
  2. 设计复杂,开发成本较高。对于TCC引入的幂等操作、空回滚问题、悬挂问题,都需要架构设计的时候考虑相应的解决方案。

需要开发人员自行评估使用成本。

相关推荐
一只叫煤球的猫2 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9652 小时前
tcp/ip 中的多路复用
后端
bobz9653 小时前
tls ingress 简单记录
后端
皮皮林5514 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友4 小时前
什么是OpenSSL
后端·安全·程序员
bobz9654 小时前
mcp 直接操作浏览器
后端
前端小张同学6 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook7 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康7 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在8 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net