当前位置: 首页 > 科技观察

不就是一个TCC分布式事务,有那么难吗?_0

时间:2023-03-18 15:44:49 科技观察

写的之前在网上看了很多关于分布式事务的文章,但大多都是简单介绍一下分布式事务的各种技术方案。很多朋友看了很多文章,还是不知道分布式事务是怎么回事,在项目中如何使用。所以在这篇文章中,我们用大白话+手绘图的方式,结合某电商系统的案例实践,给大家讲解一下什么是TCC分布式事务。业务场景介绍我们先来看看业务场景。假设您现在有一个带有支付订单场景的电子商务系统。订单付款后,我们需要做以下步骤:将订单状态更改为“已付款”。扣除商品存货。给会员加分。创建销售出库单通知仓库发货。这是一系列比较实在的步骤,不管你有没有做过电商系统,应该都能看懂。再往下想,我们有了业务场景,现在要更进一步,实现一个TCC分布式事务的效果。这意味着什么?即订单服务-修改订单状态,库存服务-扣除库存,积分服务-增加积分,仓储服务-创建销售发货单。以上步骤,要么一起成功,要么一起失败,一定是一个整体事务。比如现在订单的状态变成了“已付款”,导致库存服务无法扣除库存。那个东西的库存本来是100件,现在卖了2件,应该是98件。结果呢?由于库存服务数据库异常,库存数量还是100,这不是作弊吗,当然不能允许这样的事情发生!但是如果不使用TCC分布式事务方案,而使用SpringCloud来开发这样的微服务系统,就很有可能会出现这种事情。我们来看下图,直观的表达了上面的过程。因此,我们有必要使用TCC分布式事务机制来保证每一个服务形成一个整体的事务。上述步骤要么全部成功,如果有任何一个服务操作失败,它们将一起回滚以撤消已完成的操作。比如库存服务扣除库存失败,那么订单服务就得取消修改订单状态的操作,然后停止加分和通知仓库这两个操作。说了这么多,老规矩,我给大家看一张图,大家可以顺着图直观感受一下。落地实现TCC分布式事务那么现在如何实现一个TCC分布式事务,让所有服务一起成功呢?还是一起失败?大家不要心急,我们一步一步来分析。下面以一个SpringCloud开发系统为背景进行说明。1、TCC实现阶段1:尝试首先是订单服务,他的代码大致应该是这样的:如果你之前看过SpringCloud架构原理那篇文章,对SpringCloud有一定的了解,你应该可以理解的是以上代码。实际上,订单服务完成本地数据库操作后,通过SpringCloud的Feign调用其他服务。但是光靠这段代码还不足以实现TCC分布式事务吗?!兄弟们别着急,我们来修改一下这个订单服务的代码好吗?首先,上面的订单服务首先修改状态为:OrderStatus.UPDATING。这是什么意思?也就是说,在pay()方法中,不要直接将订单状态改为已支付!您先将订单状态修改为UPDATING,表示正在修改中。这个状态就是这样一个没有任何意义的状态,也就是说有人在修改这个状态。然后在库存服务直接提供的reduceStock()接口中,不要直接扣除库存,可以冻结库存。比如本来你的存货数量是100,不要直接扣100-2=98,扣这个存货!可以设置可售库存:100-2=98,设置成98是没有问题的,然后在单独的冻结库存字段设置一个2。也就是说,有2只股票被冻结了。积分服务的addCredit()接口也是如此,不要直接给用户添加会员积分。您可以先在credits表中预先添加的credits字段中添加credits。例如:用户积分本来是1190,现在需要加10分,不要只加1190+10=1200分!可以保持credits为1190不变,在prepare_add_credit字段等pre-increment字段中设置值为10,表示准备添加10个credits。仓储服务的saleDelivery()接口也是如此。您可以先创建销售交货单,但销售交货单的状态为“UNKNOWN”。也就是说销售出库单刚刚创建,状态还不确定!上面修改接口的过程其实就是所谓的TCC分布式事务中第一个T字母所代表的阶段,即Try阶段。总结上面的流程,如果你要实现一个TCC分布式事务,首先你的业务的主要流程和各个接口提供的业务含义不是直接完成业务操作,而是完成一个Try操作.这个操作一般是锁定某个资源,设置一个预备状态,冻结一些数据等等,大概都是这样的操作。我们来看下图,结合上面的文字,再过一遍整个过程。2.TCC实现阶段2:Confirm然后分为两种情况。第一种情况比较理想,即每个服务执行自己的Try操作,全部执行成功,bingo!这时候就需要依赖TCC分布式事务框架来推动后续的执行。这里简单提一下,想要玩转TCC分布式事务,必须要介绍一个TCC分布式事务框架,比如国内开源的ByteTCC、himly、tcc-transaction。否则无法实现每个阶段的执行,并通过手工推动下一阶段的执行,过于复杂。如果在每个服务中引入TCC分布式事务框架,订单服务中嵌入的TCC分布式事务框架可以感知到每个服务的Try操作成功。此时TCC分布式事务框架将控制并进入TCC的下一阶段,第一个C阶段,也就是Confirm阶段。为了实现这个阶段,你需要在每个服务中添加一些更多的代码。比如在订单服务中,可以添加一个Confirm逻辑,就是正式将订单的状态设置为“已付款”,大概类似下面这样:库存服务类似,可以有一个InventoryServiceConfirm类,其中提供了一个reduceStock()接口的Confirm逻辑,这里是将之前冻结库存字段中的2个库存减为0。此时之前的可售库存变成了98,现在冻结的2个库存没有了,那么去库存正式完成。信用服务也类似。可以在信用服务中提供一个CreditServiceConfirm类,里面包含一个addCredit()接口的Confirm逻辑,即从预加字段中扣除10个信用,然后添加到实际会员信用字段中,从1190开始变成1120。仓储服务也类似。可以在仓储服务中提供一个WmsServiceConfirm类,提供一个saleDelivery()接口的Confirm逻辑,将销售发货单的状态正式修改为“已创建”,供仓库管理员查看和使用。而不是停留在之前的中间状态“UNKNOWN”。好了,上面各个服务的Confirm逻辑已经实现了。订单服务中的TCC分布式事务框架一旦感知到各个服务的Try阶段成功,就会执行各个服务的Confirm逻辑。订单服务中的TCC交易框架会负责与其他服务中的TCC交易框架通信,依次调用各个服务的Confirm逻辑。至此,各个服务的所有业务逻辑的执行就正式完成了。同样的,这里给大家放一张图,一起来看看整个过程吧。3.TCC执行阶段3:取消好吧,这是比较正常的情况,但是如果是异常情况呢?比如:在Try阶段,比如点服务,他执行出错了,这时候会发生什么?订单服务中的TCC事务框架是可以感知的,然后他会决定回滚整个TCC分布式事务。也就是说,每个服务的第二个C阶段,Cancel阶段,都会被执行。同样,为了实现Cancel阶段,还得在每个服务中添加一些代码。首先,对于订单服务,他要提供一个OrderServiceCancel类,里面有pay()接口的取消逻辑,也就是订单的状态可以设置为“CANCELLED”,即状态订单被取消。库存服务也是如此,可以提供reduceStock()的取消逻辑,即从冻结的库存中减去2,加回可售库存,98+2=100。积分服务也需要提供addCredit()接口的Cancel逻辑,会在预加积分字段中扣除10分。仓储服务还需要提供saleDelivery()接口的Cancel逻辑,将销售发货单的状态改为“CANCELLED”,设置为取消。那么此时只要订单服务的TCC分布式事务框架感知到任意一个服务的Try逻辑失败,就会在各个服务中与TCC分布式事务框架通信,然后调用各个服务的Cancel逻辑.看看下面的图片,会有一个直观的感受。总结与思考好了,兄弟们,说到这里,基本上大家应该知道TCC分布式事务是怎么回事了吧!综上所述,想要玩转TCC分布式事务:首先需要选择某个TCC分布式事务框架,每个服务都会运行这个TCC分布式事务框架。那么你原来的一个接口需要转化成三种逻辑,Try-Confirm-Cancel。首先,服务调用环节依次执行Try逻辑。如果一切正常,TCC分布式事务框架会推进Confirm逻辑的执行,完成整个事务。如果某个服务的Try逻辑有问题,TCC分布式事务框架感知到后会提前执行各个服务的Cancel逻辑,撤销之前执行的各种操作。这就是所谓的TCC分布式事务。TCC分布式事务的核心思想,说白了就是当遇到以下几种情况时。服务的数据库已关闭。一个服务自己挂了。该服务的redis、elasticsearch、MQ等基础设施出现故障。有些资源不足,比如库存不足。先试试吧,业务逻辑不要完成,先试一下,看每个服务能不能基本正常运行,能不能先冻结我需要的资源。如果Tryok,也就是说底层数据库、redis、elasticsearch、MQ都可以写入数据,你预留了一些需要使用的资源(比如冻结了一部分库存)。然后,执行各个服务的Confirm逻辑。基本上,Confirm可以保证一个分布式事务大概率完成。那么如果一个服务在Try阶段失败了,比如底层数据库宕机,或者redis宕机等等。这时候自动执行各个服务的Cancel逻辑,回滚之前的Try逻辑。所有服务都不执行任何设计的业务逻辑。保证大家要么一起成功,要么一起失败。写到这里,这篇文章就差不多结束了。等一下,你想到一个问题了吗?如果出现一些意外情况,比如订单服务突然挂掉然后又重启了,那么TCC分布式事务框架是如何保证之前未完成的分布式事务继续执行的呢?因此,TCC事务框架需要记录一些分布式事务的活动日志,可以记录在磁盘上的日志文件中,也可以记录在数据库中。保存了分布式事务运行的各个阶段和状态。问题还没完,如果某个服务的Cancel或者Confirm逻辑执行一直失败怎么办?那也很简单,TCC事务框架会通过活动日志记录每一个服务的状态。比如发现某个服务的Cancel或者Confirm没有成功,就会不断重试调用它的Cancel或者Confirm逻辑,一定要成功!当然,如果你的代码没有bug,有足够的测试,在Try阶段基本都试过了,那么其实Confirm和Cancel是可以成功的!最后再给大家看一张图,看看我们的业务,加上分布式事务之后的整个执行流程:很多大公司其实都是自己开发TCC分布式事务框架,专门在公司内部使用,就像我们一样。但是,如果你的公司不开发TCC分布式事务框架,一般会选择开源框架。这里笔者给大家推荐几个不错的框架,都是我们在国内开源的:ByteTCC、tcc-transaction、himly。只要将这些框架集成到自己的系统中,就可以轻松实现上面精彩的TCC分布式事务效果。