hi ! 我是小小,我们又见面了,在本篇中,小小将会详细的介绍关于分布式事物的问题,看完了这篇文章,多年来的关于分布式事物的问题全都会了。

基础知识

工欲善其事,必先利其器。所以需要先有一定的基础知识。

事物

事物指的是对应用程序进行的严密的操作,添加了事物以后,所有的操作都必须完成,否则已经发生的事物进行撤销,即,事物具有四个特点,原子性,一致性,隔离性,持久性,这是个属性称之为ACID

具体应用场景

在大型电商系统中,下单接口通常会扣减库存,减去优惠,生成订单id,订单服务与库存,优惠,订单id都是不同的服务,下单接口的成功与否,不仅仅取决于本地的db操作,还依赖于第三方的结果,这些时候就需要使用强分布式事物,达到最终的一致性。

CAP

CAP原则称之为CAP定理,指在一个分布式系统中,一致性,可用性,分区容错性,三者不能同时顾及到。

一致性

在分布式系统的所有数据,在同一时刻是否有相同的值。

可用性

在集群一部分节点故障以后,集群是否还能响应客户端的读写请求。

分区容错性

在分布式系统中,各个集群分为好几区域,每个区域之间应该是互通的。

核心

对于CAP来说,只能满足其中两个,三个不可能全部满足,即
明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图

BASE 理论

BASE理论为基本可用,软状态,最终一致性。解释如下

基本可用

当系统出现不可预知的故障的时候,还需要能用,相对于正常的系统而言,需要有
1. 相应时间上的损失,响应时间会相应的延长。
2. 功能上的失去,对于电商网站来说,正常可以完成每一笔订单,但是大促的时候,会引导到一个系统繁忙页面。

软状态

允许系统存在于中间状态,例如当用户下单的时候,允许在同一时刻,该分布式系统,可以有中间状态,即库存还没有扣减的状态,但是要求最终都要达到一致性。只是数据同步出现了延迟。

最终一致性

相对于如软状态,不可能一直都是软状态,必须有一个时间期限,在这个期限过完以后,应当保证所有的数据副本都将会保证数据的一致性。

柔性事物

柔性事物是基于BASE理论的概念,通过柔性事物来达到最终的一致性。
例如数据库的读写分离,写库同步到读库,会有一个延时,该延时造成数据不一致,这种状态称之为乱状态,在一定期限,达到同步,称之为最终一致性。这个事物,称之为柔性事物。

幂等操作

幂等性指的是,接口无论被调用多少次,其接口的返回值都应该是相同的,不会产生其他的变化。例如第三方支付用于回调的地址,告知某个订单支付成功,该回调地址,无论调用多少次,返回的结果都应该是一致的,

使用场景

转账

用户A使用银行app发起一笔跨行转账给用户B,银行系统会先扣掉用户A的钱,在增加用户B的余额,此时需要使用分布式事物,达到这个操作具有原子性。

下单扣库存

在电商系统中下单扣库存分为两个部分,分别为下单,扣库存,这两个步骤同样需要原子性,需要分布式事物保证其原子性。

同步超时

以电商系统为例子,在微服务订单里,一个接口对应于多个接口,如下图所示。
明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图(1)
多个接口也需要实现其原子性,在这里也需要使用分布式事物。

解决方案

两节点提交

明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图(2)
明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图(3)
第一阶段,事物管理器会向所有本地资源管理器发起请求,询问是否是ready状态,所有参与者都将会把本事物能否成功的信息反馈给协调者。
第二阶段: 事物管理器根据所有的本地资源管理器的反馈,通知所有本地资源管理器,步调一致地在所有分支上提交和回滚。

存在问题

  1. 同步阻塞,当参与事物者存在占用公共资源的情况,其中一个占用了资源,其他事物参与者就只能阻塞等待资源释放,处于阻塞状态。

单点故障

当事物管理器出现故障的以后,怎个系统将会不可用

TCC

TCC(Try Confirm Cancel)
Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
Confirm 阶段:确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作满足幂等性。要求具备幂等设计,Confirm 失败后需要进行重试。
Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源 Cancel 操作满足幂等性 Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致。
在 Try 阶段,是对业务系统进行检查及资源预览,比如订单和存储操作,需要检查库存剩余数量是否够用,并进行预留,预留操作的话就是新建一个可用库存数量字段,Try 阶段操作是对这个可用库存数量进行操作。
基于 TCC 实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为 Try、Confirm、Cancel 三个接口,所以代码实现复杂度相对较高。

本地消息表

明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图(4)
1. 当系统 A 被其他系统调用发生数据库表更操作,首先会更新数据库的业务表,其次会往相同数据库的消息表中插入一条数据,两个操作发生在同一个事务中
2. 系统 A 的脚本定期轮询本地消息往 mq 中写入一条消息,如果消息发送失败会进行重试
3. 系统 B 消费 mq 中的消息,并处理业务逻辑。如果本地事务处理失败,会在继续消费 mq 中的消息进行重试,如果业务上的失败,可以通知系统 A 进行回滚操作

可靠消息最终一致性

对于可靠消息来说,需要实现最终的一致性
明了 | 看了这篇文章,多年不能理解的分布式事物,终于看懂了!插图(5)
1. A 系统先向 mq 发送一条 prepare 消息,如果 prepare 消息发送失败,则直接取消操作
2. 如果消息发送成功,则执行本地事务
3. 如果本地事务执行成功,则想 mq 发送一条 confirm 消息,如果发送失败,则发送回滚消息
4. B 系统定期消费 mq 中的 confirm 消息,执行本地事务,并发送 ack 消息。如果 B 系统中的本地事务失败,会一直不断重试,如果是业务失败,会向 A 系统发起回滚请求
5. mq 会定期轮询所有 prepared 消息调用系统 A 提供的接口查询消息的处理情况,如果该 prepare 消息本地事务处理成功,则重新发送 confirm 消息,否则直接回滚该消息

尽最大努力通知

最大努力通知是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果。

这个方案的大致意思就是:

  1. 系统 A 本地事务执行完之后,发送个消息到 MQ;
  2. 这里会有个专门消费 MQ 的服务,这个服务会消费 MQ 并调用系统 B 的接口;
  3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B, 反复 N 次,最后还是不行就放弃。

关于作者

一个生活在一线,生于二线城市的程序猿,我是小小,一个双鱼座的程序猿,我们下期再见。