TCC事务机制介绍

关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。在该论文中,TCC还是以Tentative-Confirmation-Cancellation作为名称;正式以Try-Confirm-Cancel作为名称的,可能是Atomikos(Gregor Hohpe所著书籍《Enterprise Integration Patterns》中收录了关于TCC的介绍,提到了Atomikos的Try-Confirm-Cancel,并认为二者是相似的概念)。

TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。

对于业务系统中一个特定的业务逻辑S,其对外提供服务时,必须接受一些不确定性,即对业务逻辑执行的一次调用仅是一个临时性操作,调用它的消费方服务M保留了后续的取消权。如果M认为全局事务应该rollback,它会要求取消之前的临时性操作,这将对应S的一个取消操作;而当M认为全局事务应该commit时,它会放弃之前临时性操作的取消权,这对应S的一个确认操作。

每一个初步操作,最终都会被确认或取消。因此,针对一个具体的业务服务,TCC事务机制需要业务系统提供三段业务逻辑:初步操作Try、确认操作Confirm、取消操作Cancel。

1、初步操作(Try)

TCC事务机制中的业务逻辑(Try),从执行阶段来看,与传统事务机制中业务逻辑相同。但从业务角度来看,却不一样。TCC机制中的Try仅是一个初步操作,它和后续的确认一起才能真正构成一个完整的业务逻辑。可以认为:

传统事务机制的业务逻辑 = TCC事务机制的初步操作(Try) + TCC事务机制的确认逻辑(Confirm)。

TCC机制将传统事务机制中的业务逻辑一分为二,拆分后保留的部分即为初步操作(Try);而分离出的部分即为确认操作(Confirm),被延迟到事务提交阶段执行。

TCC事务机制以初步操作(Try)为中心的,确认操作(Confirm)和取消操作(Cancel)都是围绕初步操作(Try)而展开。因此,Try阶段中的操作,其保障性是最好的,即使失败,仍然有取消操作(Cancel)可以将其不良影响进行回撤。

2、确认操作(Confirm)

确认操作(Confirm)是对初步操作(Try)的一个补充。当TCC事务管理器决定commit全局事务时,就会逐个执行初步操作(Try)指定的确认操作(Confirm),将初步操作(Try)未完成的事项最终完成。

3、取消操作(Cancel)

取消操作(Cancel)是对初步操作(Try)的一个回撤。当TCC事务管理器决定rollback全局事务时,就会逐个执行初步操作(Try)指定的取消操作(Cancel),将初步操作(Try)已完成的事项全部撤回。

TCC举例:

随着用户量越来越大,对系统的可用性要求越来越高,将一个单体应用拆分为微服务组成的应用的需求应运而生。其实,微服务听起来很美好,但是其中还有很多坑的。

在原来的单体应用中,订单模块想要调取库存和支付,只要调用相关类或者接口就可以了,用的是一个数据库,轻轻松松就可以把所有操作放到一个事务中,保证不会出现扣了库存但是支付失败的情况。

但是当系统成了分布式的时候,原来进程间的调用变成了跨越网络的HTTP调用,这数据库也从单个数据库变成多个独立的数据库,原来的事务起不到作用了。

当我们构建分布式系统的时候,可能会不由自主的做一些假设,这些假设从长期来看,都会导致大麻烦,比如:

  • 1、网络是可靠的

  • 2、调用没有延迟

  • 3、网络是安全的

但是网络调用失败的可能性是非常高的,很有可能出现扣减了库存,但是没有支付的情况。

怎么样让扣减库存和支付服务能在一个类似数据库事务中完成,要么都做,要么都不做呢?

其实这个分布式事务的原理很简单,它的精华就是冻结资源和幂等性。幂等性一个操作不管是执行一千次,一万次,效果和执行一次是一样的。冻结资源怎么去理解呢?

举个例子吧,订单服务要调用库存服务扣减库存(假设数量为2),还要调用支付服务从用户余额扣钱(假设为100), 那订单服务第一步就告诉库存服务,给我冻结2个库存; 告诉支付服务,给我冻结100块钱。在这一步,两个服务要做业务检查,看看库存余额够不够,如果足够,就冻结他们,防止其他调用也进行了扣减操作,导致本次调用余额不足。 这一步,我们称之为尝试(Try)。 
库存服务和支付服务操作的都是自己的表,冻结操作可以放到一个本地事务中,保证原子性。
这一步如果成功完成,订单服务就可以进入第二步,通知这两个服务真正执行扣减操作,这一步叫做Confirm。
如果调用支付服务进行Confirm时出错了怎么办??那就告诉库存服务和订单服务都进行Cancel操作,把冻结的数量进行恢复。

上述这套机制叫做Try-Confirm-Cancel,简称TCC。对于每个微服务来讲,都要try,confirm,cancel这三个接口,此外每个微服务也都有一个专门用来管理TCC的组件。

  • 异常场景

1、库存服务的Try操作完成, 支付服务的Try操作没有完成, 怎么办?

订单服务可以尝试去调用库存的Cancel操作(这应该是个幂等操作,可以多次调用),把冻结的库存释放。

如果出现网络问题,订单服务无法联系库存服务了呢?

库存服务的TCC组件能够发现冻结的时间已经超时,会自动把冻结的库存给释放。

2、 两个微服务的Try操作都完成, 然后发生网络故障,导致两个Confirm都无法进行?

和第一种情况一下,TCC组件会发现超时,释放冻结的资源, 当然,冻结的这部分资源在释放前的一段时间内不可以被使用。

可是,如果库存服务所在的机器已经挂掉了呢?怎么计算超时?

TCC系统必须得记录日志,把那些没有完成的事情记录下来,持久化到硬盘上。这样下次重启就可以接着执行了。

3、Try操作都已完成,资源已经冻结,在第三步中库存服务Confirm成功,库存做了扣减, 但是支付服务挂掉了,余额还处于冻结状态, 怎么办?

那可以多尝试几次, 让支付服务做Confirm操作(很明显,这个Confirm操作必须得是一个幂等操作才行)。如果实在是无法成功,那就可以让库存服务做Cancel操作。 如果还是不行,只有让人工介入了。

除了以上的异常情况,还存在实现层面的问题:

(1) 就是对于try (冻结资源), confrim , cancel(恢复资源)这样的操作都需要程序员去写代码实现。

比如对于支付服务, 至少的实现三个方法:

tryPayment(......) 

confirmPayment(......) 

cancelPayment(......)  

这样TCC框架才可以去调用。

(2) 还得自己搞个TCC框架。

TCC框架倒是有一些现成的,比如Atomikos,tcc-transaction,Hmily等, 但是那些try,confrim, cancel是业务方法,程序员必须得写, 跑不掉的。也可以尝试下另外一个最终一致性的模型:BASE。

参考地址:

https://blog.csdn.net/weixin_39800144/article/details/86238305

https://blog.csdn.net/xybelieve1990/article/details/84939770

猜你喜欢

转载自www.cnblogs.com/jlutiger/p/11276236.html
今日推荐