大家好,又见面了,我是你们的朋友全栈君。
假设现在有一个电商系统,里面有一个支付订单的场景,那对一个订单支付之后,我们需要做下面的步骤
更改订单的状态为 “已支付”
扣减商品库存
给会员增加积分
创建销售出库单通知仓库发货
业务场景有了,现在要更进一步,实现一个 TCC 分布式事务的效果,也就是说,订单服务 – 修改订单状态,库存服务 – 扣减库存,积分服务 – 增加积分,仓储服务 – 创建销售出库单,上述这几个步骤,要么一起成功,要么一起失败,必须是一个整体性的事务
举个例子,现在订单的状态都修改为 “已支付” 了,结果库存服务扣减库存失败。那个商品的库存原来是 100 件,现在卖掉了 2 件,本来应该是 98 件了。由于库存服务操作数据库异常,导致库存数量还是 100。当然不能允许这种情况发生了
但是如果不用 TCC 分布式事务方案的话,就用个 Spring Cloud 开发这么一个微服务系统,很有可能会出现这种情况
有必要使用 TCC 分布式事务机制来保证各个服务形成一个整体性的事务。上面那几个步骤,要么全部成功,如果任何一个服务的操作失败了,就全部一起回滚,撤销已经完成的操作
到底要如何来实现一个 TCC 分布式事务,使得各个服务,要么一起成功?要么一起失败呢?
1、TCC 实现阶段一:Try
首先,订单服务的代码大致来说应该是这样子的
首先,上面那个订单服务先把自己的状态修改为:OrderStatus.UPDATING,在 pay() 那个方法里,你别直接把订单状态修改为已支付啊!你先把订单状态修改为 UPDATING,也就是修改中的意思。这个状态是个没有任何含义的这么一个状态,代表有人正在修改这个状态罢了
然后呢,库存服务直接提供的那个 reduceStock() 接口里,也别直接扣减库存啊,你可以是冻结掉库存,举个例子,本来你的库存数量是 100,你别直接 100 – 2 = 98,扣减这个库存!你可以把可销售的库存:100 – 2 = 98,设置为 98 没问题,然后在一个单独的冻结库存的字段里,设置一个 2。也就是说,有 2 个库存是给冻结了
积分服务的 addCredit() 接口也是同理,别直接给用户增加会员积分。你可以先在积分表里的一个预增加积分字段加入积分。比如:用户积分原本是 1190,现在要增加 10 个积分,别直接 1190 + 10 = 1200 个积分,你可以保持积分为 1190 不变,在一个预增加字段里,比如说 prepare_add_credit 字段,设置一个 10,表示有 10 个积分准备增加
仓储服务的 saleDelivery() 接口也是同理啊,你可以先创建一个销售出库单,但是这个销售出库单的状态是 “UNKNOWN”。也就是说,刚刚创建这个销售出库单,此时还不确定他的状态是什么
上面这套改造接口的过程,其实就是所谓的 TCC 分布式事务中的第一个 T 字母代表的阶段,也就是 Try 阶段
总结上述过程,如果你要实现一个 TCC 分布式事务,首先你的业务的主流程以及各个接口提供的业务含义,不是说直接完成那个业务操作,而是完成一个 Try 的操作,这个操作,一般都是锁定某个资源,设置一个预备类的状态,冻结部分数据,大概都是这类操作
2、TCC 实现阶段二:Confirm
这里分成两种情况了,第一种情况是比较理想的,那就是各个服务执行自己的那个 Try 操作,都执行成功了,这个时候,就需要依靠 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 实现阶段三: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 分布式事务框架在运行。然后你原本的一个接口,要改造为 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 都是可以成功的
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/124722.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...