分布式事务详解

1 引言

一般像平时我们在单机上写 demo 时,所有逻辑直接在一个数据库中完成,那么本地事务就可以很简单胜任这份工作。但现在毕竟是微服务架构的时代,一个业务逻辑很可能需要多个服务来完成,而且需要依靠不同的服务操作不同的数据库,由于本地事务不能跨库操作,很显然是无法适合这样的场景的。

可以跨库的事务属于分布式事务。所谓分布式事务,便是把分布式系统中两个相关操作看成是一个单元,比如创建订单和修改库存的操作,该单元要么一起成功,要么一起失败,没有别的选择。

2 数据库事务

在介绍分布式事务之前,我们先来讲解一下数据库事务。

事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。

事务的四大特征 ACID 为:

  1. 原子性(Atomicity):事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。
  2. 一致性(Consistency):事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。
  3. 隔离性(Isolation):关于事务的隔离性数据库提供了多种隔离级别:一个事务的执行不能干扰其它事务。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
  4. 持久性(Durability):事务完成之后,它对于数据库中的数据改变是永久性的。该修改即使出现系统故障也将一直保持。

3 CAP 定理

CAP 定理是加州大学伯克利分校一位教授提出的,这个理论指出 WEB 服务无法同时满足以下3个属性:

  1. 一致性(Consistency) : 在分布式系统中数据一旦更新,所有数据变动都是同步的
  2. 可用性(Availability) : 好的响应性能,每个操作都必须有预期的响应结束
  3. 分区容错性(Partition tolerance) : 在网络分区的情况下,即使出现单个节点无法可用,系统依然正常对外提供服务

具体地讲,在分布式系统中,在任何数据库设计中,一个 WEB 应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。

4 BASE 理论

根据 CAP 定理,我们知道在一致性与可用性之间我们只能二选一。但在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢?前人已经给我们提出了另外一个理论,那就是 BASE 理论,它是用来对 CAP 定理进行进一步扩充的。BASE 理论如下:

  1. 基本可用(Basically Available):分布式系统在出现不可预知的故障的时候,允许损失部分可用性——但这绝不等价于系统不可用
  2. 软状态(Soft state):和硬状态对应,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统不同节点的数据副本之间进行数据同步的过程存在延时
  3. 最终一致性(Eventually consistent):系统所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要保证系统数据的强一致性。

BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

5 两阶段提交(2PC)

实现分布式事务有好几种方式,我们首先来介绍两阶段提交。两阶段提交中有两个很重要的角色,分别叫做事务协调者和事务参与者。

在第一阶段,事务协调者会向所有事务参与者发送 Prepare 请求,在接到 Prepare 请求之后,每一个事务参与者会各自执行与事务有关的数据更新,写入 Undo Log 和 Redo Log。如果参与者执行成功,暂时不提交事务,而是向协调者返回完成消息。当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

第二阶段,如果事务协调者在之前所收到的都是正确返回,那么它将会向所有事务参与者发出 Commit 请求。接到 Commit 请求之后,事务参与者会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回完成消息。当事务协调者接收到所有事务参与者的完成反馈,整个分布式事务完成。

很完美的方案!确实,一帆风顺的时候确实完美,但如果发生失败,又会怎样呢?

在第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。于是在第二阶段,事务协调者向所有的事务参与者发送 Abort 请求。接收到 Abort 请求之后,各个事务参与者需要在本地进行事务的回滚操作,回滚操作依照 Undo Log 来进行。

总结一下,两阶段提交固然有很多优点,例如尽量保证了数据的强一致(牺牲了一部分可用性来换取的一致性,其实也不能100%保证强一致),但它的缺点也不少:

  1. 性能问题:协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
  2. 协调者单点故障问题:事务协调者是整个模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
  3. 丢失消息导致的不一致问题:在协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

6 三阶段提交(3PC)

三阶段提交在两阶段提交的基础上增加了 CanCommit 阶段,并且引入了超时机制。一旦事务参与者迟迟没有接到协调者的 commit 请求,会自动进行本地 commit。这样有效解决了协调者单点故障的问题,但是性能问题和不一致的问题仍然没有根本解决。

7 补偿事务(TCC)

TCC 的核心思想为针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC 一共分为三个阶段:

  1. Try:对业务系统做检测及资源预留
  2. Confirm:对业务系统做确认提交,只要 Try 成功,Confirm 一定成功
  3. Cancel:业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放

TCC 与 2PC 相比,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些。而且 TCC 在 2,3 步中都有可能失败,TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码。

8 本地消息表(异步确保)

本地消息表的核心思想是将分布式事务拆分成本地事务进行处理。在这里插入图片描述
其思路如下:

  1. 消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过 MQ 发送到消息的消费方。如果消息发送失败,会进行重试发送。
  2. 消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。
  3. 生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

这种方案遵循 BASE 理论,采用的是最终一致性,比较适合实际业务场景,不会出现像 2PC 那样复杂的实现(当调用链很长的时候,2PC 的可用性是非常低的),也不会像 TCC 那样可能出现确认或者回滚不了的情况。

该方式的优点为避免了分布式事务,实现了最终一致性,但它的缺点为消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

9 MQ 事务消息

有部分 MQ 支持事务消息,例如 RocketMQ,方式与二阶段提交相类似,但也有部分 MQ 是不支持的,例如 RabbitMQ 和 Kafka。

其思路如下:

  1. 第一阶段:Prepared 消息,会拿到消息的地址
  2. 第二阶段:执行本地事务
  3. 第三阶段:通过第一阶段拿到的地址去访问消息,并修改状态

在这里插入图片描述
在业务方法内要向消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了,RocketMQ 会定期扫描消息集群中的事务消息,这时候发现了 Prepared 消息,它会向消息发送者确认,所以生产方需要实现一个 check 接口。

RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

10 总结

分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的 MQ 事务消息、TCC 模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。

参考:分布式事务(一)
聊聊分布式事务,再说说解决方案
漫画:什么是分布式事务?
24.分库分表导致的分布式事务及其解决方案

发布了113 篇原创文章 · 获赞 206 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/103964136