我一直觉得网上讲分布式事务的理论很多,案例代码很少,所以咱们今天就整一个例子,一起来把这个捋一捋。,相比于上篇文章所聊的 AT 模式,TCC(Try-Confirm-Cancel) 模式就带一点手动的感觉了,它也是两阶段提交的演化,但是和 AT 又不太一样,我们来看下流程。,官网上有一张 TCC 的流程图,我们来看下:,可以看到,TCC 也是分为两阶段:,第一阶段是 prepare,在这个阶段主要是做资源的检测和预留工作,例如银行转账,这个阶段就先去检查下用户的钱够不够,不够就直接抛异常,够就先给冻结上。,第二阶段是 commit 或 rollback,这个主要是等各个分支事务的一阶段都执行完毕,都执行完毕后各自将自己的情况报告给 TC,TC 一统计,发现各个分支事务都没有异常,那么就通知大家一起提交;如果 TC 发现有分支事务发生异常了,那么就通知大家回滚。,那么小伙伴可能也发现了,上面这个流程中,一共涉及到了三个方法,prepare、commit 以及 rollback,这三个方法都完全是用户自定义的方法,都是需要我们自己来实现的,所以我一开始就说 TCC 是一种手动的模式。,和 AT 相比,大家发现 TCC 这种模式其实是不依赖于底层数据库的事务支持的,也就是说,哪怕你底层数据库不支持事务也没关系,反正 prepare、commit 以及 rollback 三个方法都是开发者自己写的,我们自己将这三个方法对应的流程捋顺就行了。,在上篇文章的中,我们讲 AT 模式,每个数据库都需要有一个 undo log 表,这个表用来记录一条数据更改之前和更改之后的状态(前镜像和后镜像),如果所有分支事务最终都提交成功,那么记录在 undo log 表中的数据就会自动删除;如果有一个分支事务执行失败,导致所有事务都需要回滚,那么就会以 undo log 表中的数据会依据,生成反向补偿语句,利用反向补偿语句将数据复原,执行完成后也会删除 undo log 表中的记录。,在这个流程中,大家看到,undo log 表扮演了非常重要的角色。TCC 和 AT 最大的区别在于,TCC 中的提交和回滚逻辑都是开发者自己写的,而 AT 都是框架自动完成的。,为了方便大家理解,本文我就不重新搞案例了,咱们还用上篇文章那个下订单的案例来演示。,这是一个商品下单的案例,一共有五个服务,我来和大家稍微解释下:,这个案例讲了一个什么事呢?,当用户想要下单的时候,调用了 bussiness 中的接口,bussiness 中的接口又调用了它自己的 service,在 service 中,首先开启了全局分布式事务,然后通过 feign 调用 storage 中的接口去扣库存,然后再通过 feign 调用 order 中的接口去创建订单(order 在创建订单的时候,不仅会创建订单,还会扣除用户账户的余额),在这个过程中,如果有任何一个环节出错了(余额不足、库存不足等导致的问题),就会触发整体的事务回滚。,本案例具体架构如下图:,这个案例就是一个典型的分布式事务问题,storage、order 以及 account 中的事务分属于不同的微服务,但是我们希望他们同时成功或者同时失败。,这个案例的基本架构我这里就不重复搭建了,小伙伴们可以参考上篇文章,这里我们主要来看 TCC 事务如何添加进来。,首先我们将上篇文章中的数据库来重新设计一下,方便我们本文的使用。,账户表增加一个冻结金额的字段,如下:,订单表和前文保持一致,不变。,库存表也增加一个冻结库存数量的字段,如下:,另外,由于我们这里不再使用 AT 模式,所以可以删除之前的 undo_log 表了(可能有小伙伴删除 undo_log 表之后,会报错,那是因为你 TCC 模式使用不对,注意看松哥后面的讲解哦)。,相关的数据库脚本小伙伴们可以在文末下载,这里我就不列出来了。,在 TCC 模式中,我们的 Feign 换一种方式来配置。,小伙伴们都知道,在上篇文章的案例中,我们有一个 common 模块,用来存放一些公共内容(实际上我们只是存储了 RespBean),现在我们把这里涉及到的 OpenFeign 接口也存储进来,一共是三个 OpenFeign 接口,因为还要用到 seata 中的注解,所以我们在 common 中引入 OpenFeign 和 seata 的依赖,如下:,然后在这里定义 OpenFeign 的三个接口,如下:,这里一共有三个接口,但是只要大家搞懂其中一个,另外两个都很好懂了。我这里就以 AccountServiceApi 为例来和大家讲解吧。,这是 AccountServiceApi,另外两个接口的设计也是大同小异,这里我就不再赘述。,接下来看接口的实现。,首先我们来看看 Account 服务。AccountController 实现 AccountServiceApi。,我们来看下 AccountController 的定义:,因为接口的路径都定义在 AccountServiceApi 中了,所以这里只需要简单实现即可,核心的处理逻辑在 AccountService 中,我们来看下 AccountService:,好了,这就是从账户扣钱的两阶段操作,数据库操作比较简单,我这里就不列出来了,文末可以下载源码。,再来看订单服务。,由于我们是在 order 中调用 account 完成账户扣款的,所以需要先在 order 中加入 account 的 OpenFeign 调用,如下:,这应该没啥好解释的。,接下来我们来看 OrderController:,这个跟 AccountService 也基本一致,实现了 OrderServiceApi 接口,接口地址啥的都定义在 OrderServiceApi 中,这个类重点还是在 OrderService 中,如下:,跟之前的 AccountService 一样,这里也是三个核心方法:,好了,这就是下单的操作。,最后我们再来看看扣库存的操作,这个跟扣款比较像,一起来看下:,核心逻辑在 StorageService 中,如下:,这个跟 AccountService 的逻辑基本上是一样的,我就不多做解释了。,最后再来看看调用的入口 Business。Business 中要调用 storage 和 order,所以先把这两个的 OpenFeign 整进来:,然后看下接口调用:,BusinessService 中通过 RootContext 获取全局事务 ID,然后构造一个 BusinessActionContext 对象,开始整个流程的调用。,好啦,大功告成。,最后再来个简单测试,成功的测试:,调用失败的测试:,
© 版权声明
文章版权归作者所有,未经允许请勿转载。