一篇搞懂分布式事务与分布式事务常见的解决方案

23 篇文章 0 订阅
3 篇文章 0 订阅

1、前言

现在我们找工作大家的标配就是微服务、分布式系统,而系统中肯定少不了用事务,那其统带来的分布式事务 问题也成了标配了,如果你不会,面试官心中os:你也真敢在简历中写! 下面来聊聊这个分布式事务是个什么东西?在系统中我们如何来解决这个问题?

2、分布式事务

2.1 是什么?

分布式系统是什么?

多个服务/多个系统,它们部署在不同结点上,通过网络交互的方式来协同完成工作

例如下面的这个主业务逻辑,有两个微服务(系统),每个微服务维护了自己的数据库

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fl2eXXJc-1640841208499)(C:\Users\Administrator\Desktop\sx\photo\shiwu_1.png)]

事务是什么?

事务可以看作是一组操作,这组操作要么都成功,要么都失败。事务具备ACID四大特性,分别是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

  • 原子性(atomicity):事务由多个操作组成,这些操作要么全部执行成功,要么全部执行失败。
  • 一致性(consistency):事务执行会将数据从一个正确的状态转换到另一个正确的状态,状态变化前后数据都是完整的。
  • 隔离性(isolation):在某事务执行的过程中,对任何数据的改变只会存在于该事务之中,不会影响到其他事务。有 读未提交 读已提交 可重复读 串行化 这四种隔离级别。
  • 持久性:事务完成后对数据的改变会永久性的存储起来,即使断电啥的也不会消失。

本地事务是什么?

本地事务我们一般见的或使用多的就是单体应用,使用数据库来实现事务,单体应用我们一般使用单个数据库进行数据的存储,本地事务一般都具备ACID特性。

分布式事务是什么?

分布式事务用于在分布式系统中保证不同节点之间的数据一致性,在分布式事务上我们涉及的就不是ACID特性,而是CAP理论,也就是 一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)。这三个要素最多只能同时实现两点,不可能三者兼顾。

  • 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  • 可用性(A):保证每个请求不管成功或者失败都有响应。
  • 分区容忍性(P):系统中任意信息的丢失或失败不会影响系统的继续运作

在分布式系统中,CAP原则的精髓就是要么AP(放弃强一致性),要么CP(放弃可用性),但是不存在CAP

2.2 用在哪?

举个例子:

还是这个例子,很常见的一个场景。主业务逻辑就是下单,那我们需要调用库存服务来减少库存,需要调用订单服务创建订单记录,分别维护自己的数据库,每个数据库拥有ACID特性

在这里插入图片描述

如果没有用分布式事务?

(理想状态)两个服务都执行成功了,也就是数据库相关数据都改变了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tej1Wu5J-1640841208501)(C:\Users\Administrator\Desktop\sx\photo\shuwu_2.png)]

(非理想状态)有一个服务执行失败了。例如库存减了,但是订单记录没有,这怎么办?这就导致了数据不一致性

在这里插入图片描述

如果用了分布式事务?

在非理想状态下,我们可以对已经成功的操作进行回滚来保证数据的一致性

在这里插入图片描述
在这里插入图片描述

使用场景有哪些?

电商系统的下单

银行的转账、充值

xxxxxx

3、分布式事务常见的解决方案

知晓了分布式事务,那聊聊如何进行分布式事务的实现,主要有以下 5 种方案:

  • XA 方案
  • TCC 方案
  • 本地消息表
  • 可靠消息最终一致性方案
  • 最大努力通知方案

逐一说明一下

3.1 XA 方案

XA方案中有两个非常重要的角色:事务协调者事务参与者,主要有两中 2PC 与 3PC

3.1.1 二阶段提交(2PC)

二阶段提交,二阶段分别指的是准备(Prepare)提交(Commit)两个阶段,事务协调者的角色来协调管理各事务参与者(也可称之为各本地资源)的事务提交和回滚操作。

流程图:

提交/回滚事务之前就Prepare询问一下数据库是否准备好了,然后协调者等待所有参与者的回复

(理想状态)如果都准备好了,所有参与者都返回准备成功,那就需要协调者分别对参与者发起提交事务,两个数据库收到消息分别执行本地事务。等待所有事务都提交成功之后,返回事务执行成功

(非理想状态)如果有一方没有准备好,那就需要协调者分别对参与者发起回滚事务,进行事务的回滚,返回事务执行失败

在这里插入图片描述

2PC具备的特性:

2PC 是一种强一致性设计,有一方参与者没有准备好,那就需要分别向参与者发起事务回滚操作。

2PC 是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到某参与者的响应或某参与者挂了,那么超时后就会判断事务失败,向所有参与者发送回滚命令。

2PC存在的问题:

性能问题。第一阶段协调者会等待所有参与者响应才会进行下一步操作。同步阻塞那效率自然不行。

**协调者单点故障问题。**事务协调者是一个单点,那么肯定会存在单点故障问题。可能会有以下问题:

  • 事务协调者在**发送准备命令之前(执行1之前)**挂了。那事务还没开始,不会影响数据。
  • 事务协调者在**发送准备命令之后(执行1之后)**挂了。事务协调者发送prepare之后需要等待各个参与者的回应,如果这时候挂了,那不仅事务不能正常执行不下去,还可能会因为锁定了一些公共资源而阻塞系统。
  • 事务协调者在**发送提交事务命令之前(执行3之前)****挂了。所有参与者都准备好了,协调者挂了,那不是都等着它。
  • 事务协调者在**发送提交事务命令之后(执行3之后)**挂了。这个至少把命令发出去了,很大概率都会提交成功,然后释放资源,但是如果参与者因为网络问题收不到,可能就存在阻塞问题。
  • 事务协调者在**发送回滚事务命令之前(执行3之前)**挂了。事务肯定也是不能正常执行,那些准备成功的参与者都会阻塞着。
  • 事务协调者在**发送回滚事务命令之后(执行3之后)**挂了。这个至少把命令发出去了,很大概率都会回滚成功,然后释放资源,但是如果参与者因为网络问题收不到,可能就存在阻塞问题。

丢失消息导致的不一致问题。在第二个阶段,如果因为局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

针对这些问题,有3PC的出现来尝试对其处理,往下看。

3.1.2 三阶段提交(3PC)

3PC在2PC提交的基础上增加了预提交(CanCommit )阶段,并且引入了超时机制

流程图:

在这里插入图片描述

增加了预提交(CanCommit )阶段

  • 准备阶段。这个阶段就说询问影响参与者是否能够连接这个事务,而不是像2PC,准备成功就进行后续操作了。
  • 预提交阶段。预提交阶段就像一道栅栏,参与者能进入预提交阶段说明它们都准备好了,就可以进行提交操作了。
  • 提交阶段。提交阶段就把预提交阶段成功的参与者进行提交。

增加了超时机制

意思就是事务参与等待事务协调者消息的时候,如果到达一定时间,就超时不等了,直接进行操作,而不像2PC那样一直傻傻的等。这个能够有效解决 事务协调者的单点故障问题

  • 如果事务参与者是等待事务协调者提交命令超时,那么参与者就会提交事务了,因为加了预提交阶段,到提交阶段,按理来说都是进会正常进行提交操作了,所以直接提交事务,但是还是有可能执行的是回滚操作,这样一来数据就不一致了
  • 如果是等待预提交命令超时,本来啥都没干,没有关系

3PC存在的问题:

性能问题。增加了一个阶段,会涉及到到一个交互,那性能还是会差一些。

**协调者超时问题。**事务协调者虽然引用了超时机制,知识减少故障恢复时候的复杂性

丢失消息导致的不一致问题。根据上面的那个分析,如果事务参与者等待提交命令时候超时了,事务参与者默认执行的是提交事务操作,但是有可能执行的是回滚操作,这样一来数据就不一致了

2PC 和 3PC 都是数据库层面的解决办法,下面看一个业务层面的分布式事务,TCC

3.2 TCC 方案

TCC事务是Try、Commit、Cancel三种指令的缩写,其逻辑模式类似于XA两阶段提交,实际上就是将一个完整的业务进行拆分。(需要自己编写业务代码进行实现)

  • Try 主要是对资源的预留和锁定,不是真正的执行。
  • Confirm 真正的执行任务。
  • Cancel 取消执行,将Try阶段锁定的资源给释放掉

流程图:

在这里插入图片描述

说明:

  • TCC其实也是分为两个阶段:第一阶段Try来对资源进行准备,第二阶段Confirm/Cancel对Try阶段操作进行确认或者回滚。
  • 对待每一个业务操作,都要分别对应这三个操作:Try - Confirm - Cancel
  • TCC 对业务的侵入性较大,但是可以其可以跨不同的数据库、不同的业务系统来保证分布式事务
  • 在第二个阶段,确认/取消都可能进行重试,而TCC又是业务层面的,所以要业务方自己保证幂等

不管是2PC还是3PC,还是TCC,都保证了数据的强一致性,对于金额这种不容数据错误的业务操作,采取这种方式还是比较合适的

3.3 本地消息表

这个解决办法的思想,其实就也类似于通过维护一个消息表,维护消息表数据的状态来保证数据的一致性。

流程图:

在这里插入图片描述

例如现在有两个系统,系统之间进行交互,总体的思想:

  1. A 系统操作本地事务,操作同时向消息表插入一条数据;
  2. 然后A 系统将这个消息发送到消息中间件中去中去;消息中间件收到消息通知系统B
  3. B 系统接收到这个消息之后,在这个事务里,往自己本地消息表里插入一条数据,同时执行其他的业务操作(需要注意重复消费问题,在同一个事务也是保证同时回滚)
  4. 若B系统业务操作执行成功,其就会更新自己本地消息表的状态以及 A 系统消息表的状态;
  5. 若B系统业务操作执行失败,那么就不会更新消息表状态,这时候消息也被消费了,这时候为了保证一致性,需要开启定时任务定时扫描A 系统的消息表,将未处理的消息重新再次发送到消息中间件中,然后B再次收到后可以进行再次处理。所以,哪怕B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止,保证了数据的最终一致性

这种方式严重依赖于消息表来管理事务的提交与回滚来保证数据的最终一致性,高并发场景下其实是这种方式很难保证数据的一致性的,所以一般很少用。

3.4 可靠消息最终一致性方案

基于可靠性消息的最终一致性是比较常用的一种解决方案,其实跟上面的本地消息表的思路很类似,其实就是直接基于消息中间件来实现事务,用消息中间件来代替本地消息表,主要利用消息中间件(Kafka、RocketMQ或者RabbitMQ)的可靠性机制来实现数据一致性的投递。

流程图:

在这里插入图片描述

说明:

  1. 消息生产者(以下简称系统A)先发送一个准备消息**(1.发送消息)**到 mq,如果这个消息发送失败那么后面就没有后续了,成功了则进行下一步
  2. 系统A成功发送消息后就执行本地事务(2. 执行本地事务),如果事务执行成功就向 mq 发送确认消息**(3.Commit),如果失败就告诉 mq 回滚消息(3.Rollback)**;
    1. 如果这时候,mq一直没收到系统A本地事务执行的结果(也就是没有收到3)其会自动定时轮询 回查来确定系统A本地执行事务的状态**(4. 未收到步骤3的确认时,定时回查事务状态)**
    2. 系统A收到回查消息后,就去查询自己本地事务的执行结果**(5.检查本地事务执行结果)**
    3. 系统A在查询到自己本地事务的执行结果后,就告诉mq**(6.根据回查状态Commit/Rollback)**,事务执行成功就向 mq 发送Commit消息,如果失败就向mq 发送Rollback消息;
  3. 如果mq收到的是Commit消息。那么就会涉及到消息消费方(以下简称系统B),系统B就需要对该条消息进行消费**(7. Commit消费消息)**
    1. 若系统B消息该条消息成功,则告诉mq 该消息消费成功**(8.消息签收确认)**
    2. 若系统B消息该条消息失败,因为消息队列的可靠性投递机制,没有收到签收确认,其会重复投递。那么系统B就能进行重试,一直到重试成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如系统B本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。这时候系统B要注意幂等处理。

这个方案重要的两点:

  • 消息中间件的回查机制。保证了消息生产者投递消息的
  • 消息中间件的可靠性投递机制。保证了消息消费者消费消息

另外一种情况。由于网络通信的存在,服务之间的远程通信除成功和失败两种结果外,还存在一种未知状态,比如网络超时。这时候,服务提供者可以提供一个查询接口向外部输出操作的执行状态,服务调用方可以通过调用该接口得知之前操作的结果并进行相应处理。

一般在公司中都是采用这种机制来保证分布式事务的,这种方案能够保证数据最终一致性

3.5 最大努力通知方案

最大努力通知方案与基于可靠性消息的最终一致性方案的实现非常类似,但是它是一种柔性事务解决方案,适用于对数据一致性要求不高的场景。

其思想如下:

  1. 系统 A 本地事务执行完之后,发送个消息到 MQ;
  2. 这里会有个专门消费 MQ 的 最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
  3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。

4、总结

XA方案。2PC 和 3PC 是一种在数据库层面上使用,可以保证数据强一致性的解决方案,不过还是有数据不一致,单点故障/超时等一些风险

TCC 是一种在业务层面上使用,也可保证数据强一致性的解决方案。其适用的范围更广,但是对业务的侵入性较大,每一个操作都需要实现对应的三个方法。

本地消息、事务消息和最大努力通知都是保证数据的最终一致性,因为需要向消息中间件投递消息、消息中间件向消费方投递消息,都不是很实时的操作,所以这些方案也适用于一些对时间不敏感的业务

选择:

根据不同的场景选择不同的解决方案。如果是像资金这些严格不能错的场景,那么选择强一致性的解决方案肯定最好,像一些数据没有像资金这么敏感,可以用可靠消息最终一致性方案。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值