分布式-事务学习(一)——产生背景/基本思想Base/CAP理论/TCC原理

分布式解决方案有哪些:

  • 分布式——Session一致性;
  • 分布式——全局ID生产方案;
  • 分布式——事务解决方案;
  • 分布式——任务调度平台;
  • 分布式——配置中心;
  • 分布式——锁解决方案;
  • 分布式——日志收集系统;
  • 分布式——解决网站跨域问题;
  • 分布式——限流解决方案;

 

一、分布式事务产生的背景

       在微服务(分布式)环境下,因为会根据不同的业务会拆分成不同的服务,每个服务都有自己独立的数据库,并且是独立运行,互不影响。服务与服务之间通讯采用RPC远程调用技术,但是每个服务中都有自己独立的数据源,即自己独立的本地事务。两个服务相互通讯的时候,两个本地事务互不影响,从而出现分布式事务产生的原因。

案例1:下单扣库存——在电商系统中,下单和扣库存如何保持一致?

        比如:用户先下单后,扣库存失败,那么将会导致超卖;如果下单不成功,扣库存成功,那么会导致少卖。

画图订单服务和库存服务演示:

场景1:订单服务调用完库存服务后报错——属于分布式事务

因为订单服务和库存服务不在一个JVM中,每个服务数据源都是独立的,每个独立数据源中都有自己独立事务,该事务称为本地事务(本地事务的作用范围:当前同等的jdbc连接中或者同一个事务管理器里)

思考问题:在一个项目中有两个不同jdbc,连接的是相同的数据库,他们的事务是关联的吗?  答案:无相关的,因为不同jdbc

场景2:订单服务在调用库存服务时,库存服务报错(那是被调用方的问题,订单服务可以回滚解决)——不属于分布式事务

二、解决分布式事务基本思想Base和CAP理论

事务四个特性ACID:原子性、一致性、隔离性、持久性

强一致性、弱一致性、最终一致性:

强一致性:某数据被更新后,后续对该数据的读取操作都是得到更新后的值(比如关系型数据库更新操作)

弱一致性:某数据被更新后,后续对该数据的读取操作可能得到更新后的值也可能得到更新前的值。但过了“不一致窗口时间”后读取的都是更新后的值

最终一致性:弱一致性的特殊形式,存储系统保证在没有新的更新情况下,最终所有访问都是更新的值(比如NoSQL)

(1)CAP理论(帽子原理)

CAP三个元素(一致性、可用性、分区容忍性):

       一致性:所有数据备份在同一时刻具有同样的值,所有节点在同一时刻读取的数据都是最新的数据副本

       可用性:在任何故障模型下,服务都会在有限的时间内处理完成并进行响应

       分区容忍性:尽管有部分消息丢失系统仍可用

CAP原理:这三个要素最多只能同时实现两点不能三者兼顾,因此在做架构设计时必须做取舍,而分区容忍性必要的,所以在一致性和可用性取平衡,当前主流的是牺牲强一致性换区高可用性只需保留最终一致性即可

(2)BASE理论

BASE三要素(基本可用、软状态、最终一致性):

       基本可用:分布式系统在出现故障时允许损失部分可用性,保证核心可用(比如服务降级、搜索延时)

       软状态:允许系统系统中存在中间状态,且该中间状态不会影响系统整体可用性

      最终一致性:不需要实时数据一致,能最终达到一致,通过牺牲强一致性来获得可用性

BASE原理是指基本可用、软状态/柔性事务、最终一致性,由CAP理论演化而来,是对CAP中一致性和可用性权衡的结果。核心思想:及时无法做到强一致性,但每个业务根据自身的特点,采用适当的方式来使系统达到最终一致性

案例1:支付项目,支付中.......

        A(项目)调用————>B(支付宝),支付宝使用类似HttpClient技术向A发送处理结果通知,当通知接口出现延迟或者异常情况下,支付宝会发生自动重试。重试过程中,A应该注意幂等性问题,该过程属于暂时不一致,发生了短暂不一致问题。

同步回调地址:支付完成后,支付宝采用浏览器方式重定向回调方

异步回调地址:支付完成后,采用后台方式,也就是HttpClient进行调用A接口通知支付接口

柔性事务与刚性事务区别

柔性事务:满足BASE理论(基本可用、最终一致)

        分为两阶段型、补偿型、异步确保型、最大努力通知型

刚性事务:满足ACID理论

三、TCC分布式事务原理

  • TCC 实现阶段⼀:Try
  • TCC 实现阶段⼆:Confirm
  • TCC 实现阶段三:Cancel

场景:

假设现在有⼀个电商系统,⾥⾯有⼀个⽀付订单的场景,那对⼀个订单⽀付之后,我们需要做下⾯的步骤:

  1. 更改订单的状态为 “已⽀付” ;
  2. 扣减商品库存 ;
  3. 给会员增加积分;
  4. 创建销售出库单通知仓库发货

订单服务 - 修改订单状态,库存服务 - 扣减库存,积分服务 - 增加积 分,仓储服务 - 创建销售出库单。要么⼀起成功,要么⼀起失败,必须是⼀个整体性的事务,这是一个TCC分布式事务效果。

现在订单的状态都修改为 “已⽀付” 了,结果库存服务扣减库存失败。那个商品的库 存原来是 100 件,现在卖掉了 2 件,本来应该是 98 件了。现在还是100件。这就错了。

上⾯那⼏个步骤,要么全部成功,如果任何⼀个服务的操作失败了,就全部⼀起回滚,撤销已 经完成的操作

阶段一:Try

支付:在 pay() 支付那⽅法⾥,不能直接把订单状态修改为已⽀付!你先 把订单状态修改为 UPDATING(修改中);

减库存:库存服务直接提供的那个 reduceStock() 接⼝⾥,也别直接扣减库存,你可以是冻结库存。在⼀个单独的冻结库存的字段 ⾥,设置⼀个 2。也就是说,有 2 个库存是给冻结了;

加积分:的 addCredit() 接⼝也是同理,别直接给⽤户增加会员积分。你可以先在积分表⾥的⼀ 个预增加积分字段加⼊积分。例如,你可以保持积分为 1190 不变,在⼀个预增加字段⾥,⽐如说 prepare_add_credit 字段,设置⼀ 个 10,表示有 10 个积分准备增加;

创建销售出库单:仓储服务的 saleDelivery() 接⼝也是同理啊,你可以先创建⼀个销售出库单,但是这个销售出库 单的状态是 “UNKNOWN”。也就是说,刚刚创建这个销售出库单,此时还不确定他的状态是什么;

总结上述过程,如果你要实现⼀个 TCC 分布式事务,⾸先你的业务的主流程以及各个接⼝提供 的业务含义,不是说直接完成那个业务操作,⽽是完成⼀个 Try 的操作,这个操作,⼀般都是锁定某个资源,设置⼀个预备类的状态,冻结部分数据。

阶段二:Confirm

然后就分成两种情况了,第⼀种情况是⽐较理想的,那就是各个服务执⾏⾃⼰的那个 Try 操 作,都执⾏成功了,就需要依靠 TCC 分布式事务框架(国内开源的 ByteTCC、himly、tcc-transaction)来推动后续的执⾏了。

否则的话,感知各个阶段的执⾏情况以及推进执⾏下⼀个阶段的这些事情,不太可能⾃⼰⼿写实现,太复杂了。 如果你在各个服务⾥引⼊了⼀个 TCC 分布式事务的框架,订单服务⾥内嵌的那个 TCC 分布式事 务框架可以感知到,各个服务的 Try 操作都成功了,此时进入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 逻辑。然后,正式完成各个服务的所有业务逻辑的执⾏。

阶段三: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 分布式事务框架在运⾏。 然后你原本的⼀个接⼝,要改造为 3 个逻辑,Try-Confirm-Cancel。

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

说白了就是遇到类似下边几种情况时:

  • 某个服务的数据库宕机了
  • 某个服务⾃⼰挂了
  • 某个服务的 redis、elasticsearch、MQ 等基础设施故障了
  • 某些资源不⾜了,⽐如说库存不够这些

疑问:

(1)如果有⼀些意外的情况发⽣了,⽐如说订单服务突然挂了,然后再次重启,TCC 分布式事务框 架是如何保证之前没执⾏完的分布式事务继续执⾏的呢?

TCC 事务框架都是要记录⼀些分布式事务的活动⽇志的,可以在磁盘上的⽇志⽂件⾥记 录,也可以在数据库⾥记录。保存下来分布式事务运⾏的各个阶段和状态

(2)万⼀某个服务的 Cancel 或者 Confirm 逻辑执⾏⼀直失败怎么办呢?

TCC 事务框架会通过活动⽇志记录各个服务的状态。举个例⼦,⽐如发现某个服务的 Cancel 或者 Confirm ⼀直没成功,会不停的重试调⽤他的 Cancel 或者 Confirm 逻辑,务必要他成功!

 

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值