5

探秘分布式解决方案: 分布式事务——微服务架构下的主流解决方案之TCC

 3 years ago
source link: https://www.skypyb.com/2019/11/jishu/1171/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

探秘分布式解决方案: 分布式事务——微服务架构下的主流解决方案之TCC

说完了分布式事务最核心的思想(2PC) –> [探秘分布式解决方案: 分布式事务——从核心思想之2PC(两阶段提交)开始]    http://skypyb.com/2019/11/jishu/1149/

那么现在进入到更加复杂的场景。像这种跨库调用之类的,一线互联网公司早就不玩这一套东西了。这都9102年了,上来就是微服务架构。

我这么多服务,你整个啥跨库调用呢?一个服务可能同时调用多个其他的服务。这多个其他的服务中都要执行SQL语句,修改落实到服务所对应的数据库之中。

微服务下的服务调用

还是以一个订单支付的流程为例,只不过由之前讲解2PC的单机多库示例改为了微服务示例。

微服务架构下,用户下单的流程将会变成这个样子:

微服务下的订单服务

这里边每一个 “服务” 都是一个系统,都是会单独部署的。

可以见得这个复杂度上升的非常大。比如说我用户在下单购买,调用了订单服务的接口,此时订单接口的方法内逻辑不单单只是订单服务自己操作自己的数据库。

订单还得去调用其他的服务的接口。比如库存、积分、仓储 等。

那么,在微服务架构下,服务之间互相通过接口进行通信,如何才能在一个 “事务” 中保证数据一致性?

如果出现这种情况   比如订单支付状态已经修改完毕、库存扣除完毕,但是加分却失败了, 这样子咋回滚?

那么就轮到新的解决方案出场了。那就是 TCC (Try-Confirm-Cancel)

TCC全名为 两阶段补偿型方案。是现在的互联网大厂主流的分布式事务解决方案之一。而TCC,就是脱胎于2PC,是作为2PC思想在微服务领域的子集被使用着。

所以说为啥2PC是核心思想,不懂2PC,那TCC是肯定明白不了的。 要是懂了TCC的原理,那么前置条件必然是已经懂了2PC。

TCC原理思想

而TCC的原理相比于2PC,有这样一种行为上的变更:

2PC的行为是:  1、预提交 2、实际提交

TCC将其改为了: 1、预留资源 2、确认资源

什么叫做预留资源?什么叫做确认资源?

还是以上面我画的的订单活动图为例子,预留的资源,那就是 订单状态、库存数、积分数、出库单

那我们在TCC的方案下进行事物的话,第一阶段就是将这几个资源给他锁定住。然后在第二阶段进行一个资源的确认,即提交/回滚

你如果知道2PC的思想,我想这完全不难以理解。下面是TCC方案的状态图:

TCC-STATE

在TCC方案下,在第一阶段中的修改,修改的并不是实际期望的结果,而是期望结果之前的一个中间值。

所以,每个服务都要提供两个接口,一个用于第一阶段的资源预留行为,另一个用于确认/回滚。

(其实你也可以就搞一个接口,然后参数传个对象,由对象中的属性来决定做什么操作也不是不行)

比如更改订单,我不可能一下就将这个订单给改成已支付。毕竟现在这个方法还跑到一半,不能完全确定之后的业务是否正常进行。

所以,在此取了个中间态,将订单改为 “更新中” 。

库存、积分等服务也和订单相同。 当所有的接口均调用完成后,即所有的资源预留操作均完成。则在这时进行确认行为。

如果说所有的操作都返回了成功,那么就将订单状态改为已支付,然后将冻结的库存真正减掉、积分真正加上、草稿出库单改为生效出库单。 (confirm)

而反之,若是其中有任意一个接口失败了,那么就对应执行 calcel 操作,将所有改变的状态通通回滚。

如何预留资源

而有的人可能会好奇,什么冻结库存之类的玩意如何实现?

这个其实也很简单,比如你库存表本来只有一个库存数字段。但你现在要用这种TCC方案了,那么就给他再加上一个冻结库存数字段

本来来说,你某个商品库存数是10 , 那么用户再下单完成支付的这个流程中,假设买了1个商品,那么原来的逻辑就得改一下。

正常你买1个商品,那库存就是10-1=9 咯,所以将库存数改为9。而在TCC下,你就不将他改为9,而是在冻结库存数上+1,即数据库状态变为 库存数10,冻结库存数1

那么在其他的用户购买时,你返回给前端的库存数应该就是 库存数10 减去 冻结库存数1 等于现在只有9个库存能够被使用。

用户他不知道冻结不冻结,但是你的程序里边是知道的。用这种逻辑那是一点没有问题。

在你支付接口里边所有的预留操作成功之后,那么就如上所述,将库存字段真正减掉,也就是将10库存真正减1,将冻结库存还原,这是第二阶段该做的事情。

说到这里,总没人问第二阶段要是某环节错了怎么办吧? 要是有这疑问的那肯定是没看我上一篇分布式事务文章。

这里也简单的讲一句:本身失败的概率就极小,要是操作失败了肯定是会重试、并且记录事务日志的。状态一个不对劲就由人肉团队、定时任务来进行补偿。

TCC讲解完毕,那么到底TCC和2PC有着哪些区别呢?

TCC其实说白了,其实也和两阶段提交差不多。就是要开发新的接口。

毕竟是在微服务架构下,复杂度上升很多,要在这种架构下来进行分布式事务本身就要比单机应用考虑的方面更广。

而TCC和2PC之间最主要还有这么一个区别:

在很复杂的、跨了很多数据库的业务场景下,2PC的锁粒度非常大。因为所有的数据库锁均被应用所持有,直到事务完成。性能会很低,根本顶不住大并发。

而TCC的话,从整个系统上来看,吞吐量会好非常多,因为在TCC方案下的的复杂调用链之中,实际上每个数据库操作,都是commit了的,换而言之就是锁释放了。并不是一个加在全局的大锁,而是将锁粒度控制在每一个系统的每一个接口里。嘿,有没有想到ConcurrentHashMap。

如果将TCC这种解决方案称之为 “术” 。那么2PC就是所谓的”道”

只要你学会了”道”   那么一切基于此道的术相信都难不倒你了。

当然,像这种TCC的实现肯定是有现成的给你用。毕竟自己来搞这么一个框架也是蛮复杂的,不推荐自个造轮子。

业界开源实现也挺多比如ByteTCC、seata、tcc-transaction等,使用方面就不贴了,基本都是配置。

而除了TCC,其实还有像是最大努力通知型方案可靠消息最终一致性方案这些基于最终一致性的分布式事务解决方案。也可以网上搜搜了解一下。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK