首页 > 编程知识 正文

分布式事务seata原理,基于mq实现分布式事务

时间:2023-05-06 09:03:23 阅读:48957 作者:2381

我以前在网上看到很多写分布式事务的文章,很多都是简单介绍分布式事务的各种技术方案。 很多朋友看不懂分布式事务在做什么,项目中是怎么使用的。

因此,本文采用白话手工绘图,结合EC系统的案例分析,阐明了什么是TCC分布式事务。

##一、业务场景介绍

先看看业务场景吧。 假设你现在有电子商务系统,其中有支付订单的场景。

它对一个订单支付后,我们需要进行下一步。

将订单状态更改为“已支付”以扣除商品库存,并向会员添加积分制作销售发票,通知仓库发货

这是一系列比较现实的步骤,不管你是否做过电商系统,都应该可以理解。

##二、多考虑

是的,有商业场景。 现在,我们更进一步,实现TCC分布式事务的效果。 什么意思? 也就是说,[1]修改订单服务-订单状态,[2]库存服务-扣除库存,[3]增加积分服务-积分,[4]制作仓库服务-销售发票。

这些步骤必须是一起成功,一起失败,或者是整体事务。

举个例子,现在订单的状态更改为“已支付”,库存服务扣除库存失败。 那个商品的库存本来是100件,现在卖了2件,本来应该是98件了。

结果呢? 由于库存服务运营数据库异常,库存数量仍为100。 这不是在陷害人吗,当然不能发生这种情况!

但是,如果不使用TCC分布式事务方式,很可能在Spring Cloud上开发这样的微服务系统来进行这种动作。

请看下图。 直观地表达了上述过程。

这意味着,必须使用TCC分布式事务机制来确保每个服务形成一个整体事务。

上述几个步骤都成功,或者如果其中一个服务操作失败,则全部回滚并取消完成的操作。

例如,如果库存服务的库存扣减失败,订单服务必须取消更正订单状态的操作,然后停止增加积分和通知发放这两个操作。

说得多,老规矩,给大家一张图,让大家按照图直观感受一下:

##三、落地实现TCC分布式事务

那么,如何实现TCC分布式事务,使各项服务成功,还是一起成功? 一起失败吗?

####3.1 TCC实现阶段1:try

首先,在订单服务方面,其代码应该大致是这样的。

公共类订单服务{//库存服务@ autowiredprivateinventoryserviceinventoryserviceinventoryservice; //积分服务@ autowiredprivatecreditservicecreditservice; //仓库服务@ autowiredprivatewmsservicewmsservice; //已为此订单支付的public void pay () /将本地订单数据库的订单状态更改为“已支付”的order Dao.updatestatus (order status.payed //库存服务扣减库存inventoryService.reduceStock (; //调用积分服务以增加积分的creditService.addCredit (; //调用仓库服务通知发货WMS服务. sale delivery (; }但是,光靠这段代码还不足以实现TCC分布式事务,对吧? 各位,请不要着急。 我们可以修改这个订单服务的代码吗?

首先,上面的订单服务首先将自己的状态修改为OrderStatus.UPDATING。

这是什么意思? 也就是说,pay ) )的方法中,请不要将订单状态修改为已直接支付。 请先将订单状态修改为UPDATING,即修改中的含义。

这种状态是没有任何意义的这种状态,表示有人在修正这种状态。

然后,请不要在库存服务直接提供的reduceStock ) )界面中直接扣减库存。 你可以冻结库存。

举个例子,本来你的库存量是100。 请扣除这个库存,而不是直接100 - 2=98。

可销售库存: 100 - 2=98设定为98也没关系。 然后,将另一个冻结库存的字段设置为2。 也就是说,有两个库存被冻结了。

积分服务的addCredit (接口也一样,不要直接给用户增加会员积分。 可以首先将积分添加到积分表的预添加点字段中。

例如,用户积分本来是1190,现在增加10个百分点,所以不要就这样定为1190 10=1200个百分点。

如果将点保留为1190,并将预先添加的字段(例如prepare_add_credit字段)设置为10,则表示已准备添加10个点。

仓库服务的saleDelivery ()接口也是如此

理啊,你可以先创建一个销售出库单,但是这个销售出库单的状态是“UNKNOWN”。

也就是说,刚刚创建这个销售出库单,此时还不确定它的状态是什么呢!

上面这套改造接口的过程,其实就是所谓的 TCC 分布式事务中的第一个 T 字母代表的阶段,也就是 Try 阶段。

总结上述过程,如果你要实现一个 TCC 分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操作,而是完成一个 Try 的操作。

这个操作,一般都是锁定某个资源,设置一个预备类的状态,冻结部分数据,等等,大概都是这类操作。

咱们来一起看看下面这张图,结合上面的文字,再来捋一捋整个过程:

####3.2 TCC 实现阶段二:Confirm

然后就分成两种情况了,第一种情况是比较理想的,那就是各个服务执行自己的那个 Try 操作,都执行成功了,Bingo!

这个时候,就需要依靠 TCC 分布式事务框架来推动后续的执行了。这里简单提一句,如果你要玩儿 TCC 分布式事务,必须引入一款 TCC 分布式事务框架,比如国内开源的 ByteTCC、Himly、TCC-transaction。

否则的话,感知各个阶段的执行情况以及推进执行下一个阶段的这些事情,不太可能自己手写实现,太复杂了。

如果你在各个服务里引入了一个 TCC 分布式事务的框架,订单服务里内嵌的那个 TCC 分布式事务框架可以感知到,各个服务的 Try 操作都成功了。

此时,TCC 分布式事务框架会控制进入 TCC 下一个阶段,第一个 C 阶段,也就是 Confirm 阶段。

为了实现这个阶段,你需要在各个服务里再加入一些代码。比如说,订单服务里,你可以加入一个 Confirm 的逻辑,就是正式把订单的状态设置为“已支付”了,大概是类似下面这样子:

public class OrderServiceConfirm { public void pay(){ orderDao.updateStatus(OrderStatus.PAYED); }}

库存服务也是类似的,你可以有一个 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.3 TCC 实现阶段三:Cancel
好,这是比较正常的一种情况,那如果是异常的一种情况呢?

举个例子:在 Try 阶段,比如积分服务吧,它执行出错了,此时会怎么样?

那订单服务内的 TCC 事务框架是可以感知到的,然后它会决定对整个 TCC 分布式事务进行回滚。

也就是说,会执行各个服务的第二个 C 阶段,Cancel 阶段。同样,为了实现这个 Cancel 阶段,各个服务还得加一些代码。

首先订单服务,它得提供一个 OrderServiceCancel 的类,欢喜的黑猫有一个 pay() 接口的 Cancel 逻辑,就是可以将订单的状态设置为“CANCELED”,也就是这个订单的状态是已取消。

库存服务也是同理,可以提供 reduceStock() 的 Cancel 逻辑,就是将冻结库存扣减掉 2,加回到可销售库存里去,98 + 2 = 100。

积分服务也需要提供 addCredit() 接口的 Cancel 逻辑,将预增加积分字段的 10 个积分扣减掉。

仓储服务也需要提供一个 saleDelivery() 接口的 Cancel 逻辑,将销售出库单的状态修改为“CANCELED”设置为已取消。

然后这个时候,订单服务的 TCC 分布式事务框架只要感知到了任何一个服务的 Try 逻辑失败了,就会跟各个服务内的 TCC 分布式事务框架进行通信,然后调用各个服务的 Cancel 逻辑。

大家看看下面的图,直观的感受一下:

###总结与思考
好了,兄弟们,聊到这儿,基本上大家应该都知道 TCC 分布式事务具体是怎么回事了!

总结一下,你要玩儿 TCC 分布式事务的话:首先需要选择某种 TCC 分布式事务框架,各个服务里就会有这个 TCC 分布式事务框架在运行。

然后你原本的一个接口,要改造为 3 个逻辑,Try-Confirm-Cancel:

先是服务调用链路依次执行 Try 逻辑。如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务。如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作。

这就是所谓的 TCC 分布式事务。TCC 分布式事务的核心思想,说白了,就是当遇到下面这些情况时:

某个服务的数据库宕机了。某个服务自己挂了。那个服务的 Redis、Elasticsearch、MQ 等基础设施故障了。某些资源不足了,比如说库存不够这些。

先来 Try 一下,不要把业务逻辑完成,先试试看,看各个服务能不能基本正常运转,能不能先冻结我需要的资源。

如果 Try 都 OK,也就是说,底层的数据库、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。

原文链接(https://www.jianshu.com/p/c4d6735429e0)

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。