04_柔性事务-TCC

gong_yz大约 8 分钟分布式事务

柔性事务-TCC 事务补偿型方案

刚性事务:遵循 ACID 原则,强一致性。

柔性事务:遵循 BASE 理论,最终一致性;

与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。TCC是Try-Confirm-Cancel的缩写,它是一种分布式事务解决方案,采用了基于业务逻辑的补偿机制,将整个分布式事务分解为若干个子事务,每个子事务都有一个try、confirm和cancel三个操作,通过这些操作来实现分布式事务的执行和回滚

img
img

具体来说,TCC事务包括以下三个步骤:

  1. Try:在try阶段,参与者尝试执行本地事务,并对全局事务预留资源。如果try阶段执行成功,参与者会返回一个成功标识,否则会返回一个失败标识。
  2. Confirm:如果所有参与者的try阶段都执行成功,则协调者通知所有参与者提交事务,那么就要执行confirm阶段,这时候参与者将在本地提交事务,并释放全局事务的资源。
  3. Cancel:如果任何一个参与者在try阶段执行失败,则协调者通知所有参与者回滚事务。那么就要执行cancel阶段,还有就是,如果某个参与者在try阶段执行成功,但是在confirm阶段执行失败,则需要执行cancel操作,将之前预留的资源释放掉。

以下是一个简单的TCC事务的例子,假设有一个转账服务,需要从A账户中转移到B账户中100元、C账户中200元:

  1. Try阶段:转账服务首先尝试将A账户的金额冻结300元。
  2. Confirm阶段::如果所有的try操作都执行成功,转账服务将尝试执行解冻并转账,将金额转到B账户和C账户中。
  3. Cancel阶段:如果try过程中,某个转账事务执行失败。那么将执行解冻,将300元解冻。如果在confirm过程中,A->C的转账成功,但是A->B的转账失败,则再操作一次C->A的转账,将钱退回去。

TCC这种事务方案有以下优缺点

优点:

  1. 灵活性:TCC适用于不同类型的业务场景,例如账户转账、库存扣减等,能够根据业务逻辑实现精细的事务控制。
  2. 高可用性:TCC使用分布式锁来保证分布式事务的一致性,即使其中一个节点出现故障,也不会影响整个系统的运行。
  3. 可扩展性:TCC采用分阶段提交的方式,支持横向扩展,可以适应更多的并发访问和业务场景。
  4. 性能:TCC相对于2PC来说,具有更好的性能表现

缺点:

  1. 实现复杂:TCC需要实现Try、Confirm和Cancel三个操作,每个操作都需要实现正确的业务逻辑和补偿机制,代码实现比较复杂。
  2. 存在悬挂事务问题:TCC的实现方式存在悬挂事务的问题,即在执行过程中可能会有部分子事务成功,而其他子事务失败,导致整个事务无法回滚或提交。
  3. 空回滚问题:TCC中的Try过程中,有的参与者成功了,有的参与者失败了,这时候就需要所有参与者都执行Cancel,这时候,对于那些没有Try成功的参与者来说,本次回滚就是一次空回滚。需要在业务中做好对空回滚的识别和处理,否则就会出现异常报错的情况,甚至可能导致Cancel一直失败,最终导致整个分布式事务失败。
  4. 业务代码侵入性:TCC需要将事务操作拆分为Try、Confirm和Cancel三个步骤,对业务代码有一定的侵入性,需要针对不同的业务场景进行实现。

但是其实,在实际的应用过程中,很多公司用的TCC并不是完全严格的按照上述的过程的。有的时候是这么用的:Try的时候预占用资源,如果成功了,就执行Confirm,失败了就执行Cancel,但是如果Confirm失败了或者Cancel失败了,则进行重试,直至成功。

之所以可以这么做,主要是因为在Try的过程中已经锁定了资源,那么在Confirm的时候,大概率是可以成功,而如果Confirm失败就执行Cancel,就会导致可能只是因为网络原因导致的时候就使得整个事务都Cancel了,而且这时候如果Cancel再失败怎么办呢?整个方案就会变得更加复杂了。


什么是柔性事务?

概念

柔性事务,是业内解决分布式事务的主要方案。所谓柔性事务,相比较与数据库事务中的ACID这种刚性事务来说,柔性事务保证的是“基本可用,最终一致”。这其实就是基于BASE理论,保证数据的最终一致性。

虽然柔性事务并不像刚性事务那样完全遵循ACID,但是,也是部分遵循ACID的,简单看一下关于ACID四个属性,柔性事务的支撑程度:

  • 原子性:严格遵循
  • 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽
  • 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽
  • 持久性:严格遵循

在业内,关于柔性事务,最主要的有以下三种类型:异步确保型补偿型最大努力通知型

实现基础

可查询操作

可查询操作,几乎是所有的分布式解决方案都需要的。

举一个常见的分布式场景的例子,如订单处理这一功能:

/**支付订单处理 **/
public void completeOrder(){
	orderDao.update();//订单服务本地更新订单状态
	accountservice.update();//调用资金账户服务给资金帐户加款
	pointService.update();//调用积分服务给积分帐户增加积分
	accountingService.insert();//调用会计服务向会计系统写入会计原始凭证
	merchantNotifyService.notify();//调用商户通知服务向商户发送支付结果通知
}

以上这个支付订单处理的例子中,除了订单服务本地更新订单状态以外的所有操作,都需要调用RPC接口来执行,这种情况单纯的本地事务就无法保证数据的一致性了。就需要引入分布式事务。

在分布式事务执行过程中,如果某一个步骤执行出错,就需要明确的知道其他几个操作的处理情况,这就需要其他的服务都能够提供查询接口,保证可以通过查询来判断操作的处理情况。

为了保证操作的可查询,需要对于每一个服务的每一次调用都有一个全局唯一的标识,可以是业务单据号(如订单号)、也可以是系统分配的操作流水号(如支付记录流水号)。除此之外,操作的时间信息也要有完整的记录。

幂等操作

幂等性,其实是一个数学概念。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数,如:

f(f(x) = f(x)

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。也就是说,同个方法,使用同样的参数,调用多次产生的业务结果与调用一次产生的业务结果相同。

这一个要求其实也比较好理解,因为要保证数据的最终一致性,很多解决防范都会有很多重试的操作,如果一个方法不保证幂等,那么将无法被重试。

幂等操作的实现方式有多种,如在系统中缓存所有的请求与处理结果、检测到重复操作后,直接返回上一次的处理结果等。

可补偿操作

提到事务,为了保证原子性,就可能发生commit和rollback,那么在分布式事务中,要想进行rolback就需要提供可补偿操作。

比如上面的订单处理的例子中,在调用积分服务给积分帐户增加积分操作执行之后,经过分布式事务协调,最终决定回滚整个事务,那么就需要提供一个调用积分服务给积分帐户扣减积分的操作。

并且,补偿操作同时也需要满足幂等性。

TCC操作

TCC 即 Try-Confirm-Cancel

Try: 尝试执行业务

  • 完成所有业务检查(一致性)预留必须业务资源(准隔离性)

Confirm:确认执行业务

  • 真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confrm操作要满足幂等性

Cancel: 取消执行业务

  • 释放Try阶段预留的业务资源,Cancel操作要满足幂等性
  • 这种类型和可补偿操作类似,就是提供一种提交和回滚的机制。是一种典型的两阶段类型的操作。这里说的两阶段类型操作并不是指2PC,他和2PC还是有区别的。