写点什么

分布式事务详解、理论分析、及强一致性 (2PC、3PC) 剖析

作者:C++后台开发
  • 2022-11-14
    湖南
  • 本文字数:6379 字

    阅读完需:约 21 分钟

分布式事务详解、理论分析、及强一致性(2PC、3PC)剖析

一. 简介

1. 什么是本地事务?

 基于关系型数据库的事务,叫做本地事务,也叫做数据库事务。 本地事务通常是应用和数据库在一个服务器上,利用数据库本身的事务特性,从而实现本地事务。

数据库事务的特性:ACID。

 (1). 原子性(Atomicity):指一个事务内的所有操作要么都执行,要么都不执行。

 (2). 一致性(Consistency):指数据是满足完整性约束的,也就是不会存在中间状态的数据。

 (3). 隔离性(Isolation),指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。

 (4). 持久性(Durability),指的是一个事务完成了之后数据就被永远保存下来,之后的其他操作或故障都不会对事务的结果产生影响。

PS: Redis 中的事务不支持回滚,这是一个特殊情况。 下面是官网解释,详见 https://www.cnblogs.com/yaopengfei/p/13922295.html

2. 什么是分布式事务?

(1). 含义:在分布式系统中,不同服务之间需要通过网络协作来完成的事务,叫做分布式事务。比如下单业务:先创建订单→扣减库存。

 A. 如果是本地事务:借助数据库就能完成,比如请求接口 POrder,里面的业务如下:

begin transaction;    1. 访问订单表,创建订单    2. 访问库存表:扣减库存 (以上两个表是同一个DB)commit transation;
复制代码

 B. 如果是分布式事务:存在订单微服务和库存微服务,我们请求订单微服务中 POrder 接口,该接口除了需要调用自身 DB 创建订单外,还需要调用库存微服务的接口,这就存在网络通信,传统的数据库事务无法满足。

begin transaction;    1. 订单微服务,创建订单 (本地DB)    2. 通过网络调用库存微服务中接口,进行扣减库存commit transation;
复制代码

(2). 产生分布式事务的几种情况

 A. 跨进程通信(典型微服务架构,多服务对应多 DB)

 比如,典型的下单流程: 客户端请求订单微服务中的接口,该接口中除了要向订单 DB 中插入数据,还要调用库存微服务中的接口,这就形成分布式事务。

 B. 单服务对应多 DB

 比如,单应用由于数据较多,需要数据库分库现象,比如用户管理系统中存在多个 DB,用户 DB、订单 DB、材料 DB 等等,每个 DB 都对应一个不同的数据库连接,也是分布式事务

 C. 多服务对应单 DB

比如,有些情况下,虽然服务分多个了,但还是 1 个 DB,订单微服务和库存微服务都访问同一个数据库,下单的时候,同样道理,存在网络通信,跨进程,不同的微服务对应不同的 DB 连接,也是分布式事务。


二. 理论分析

1. CAP 理论分析

(分布式事务需要 CAP 理论支持)

(1). 什么是 CAP?

 CAP( Consistency、Availability、Partition Tolerance),分别表示一致性、可用性、分区容忍性。

下面用 1 个增加、查询商品的流程来解释什么是 CAP。


 A. 增加商品,向主数据库中插入数据。

 B. 主数据库写入成功,需要把数据同步给从数据库。

 C.查询商品,访问从数据库读取。

① C-Consistency

一致性是指写操作后的读操作可以读取到最新的数据状态,当数据分布在多个节点上,从任意结点读取到的数据都是最新的状态

上图中,商品信息的读写要满足一致性就是要实现如下目标:

  1. 商品服务写入主数据库成功,则向从数据库查询新数据也成功。

  2. 商品服务写入主数据库失败,则向从数据库查询新数据也失败。

如何实现一致性?

  1. 写入主数据库后要将数据同步到从数据库。

  2. 写入主数据库后,在向从数据库同步期间要将从数据库锁定,待同步完成后再释放锁,以免在新数据写入成功后,向从数据库查询到旧的数据。

分布式系统一致性的特点:

  1. 由于存在数据同步的过程,写操作的响应会有一定的延迟。

  2. 为了保证数据一致性会对资源暂时锁定,待数据同步完成释放锁定资源。

  3. 如果请求数据同步失败的结点则会返回错误信息,一定不会返回旧数据。

② A-Availability

可用性是指任何事务操作都可以立即得到响应结果,且不会出现响应超时或响应错误

上图中,商品信息读取满足可用性就是要实现如下目标:

  1. 数据库接收到数据查询的请求则立即能够响应数据查询结果

  2. 数据库不允许出现响应超时或响应错误。

如何实现可用性?

  1. 写入主数据库后要将数据同步到从数据库。

  2. 由于要保证从数据库的可用性,不可将从数据库中的资源进行锁定。

  3. 即使数据还没有同步过来,从数据库也要返回要查询的数据,哪怕是旧数据,如果连旧数据也没有则可以按照约定返回一个默认信息,但不能返回错误或响应超时。

分布式系统可用性的特点:所有请求都有响应,且不会出现响应超时或响应错误

③ P-Partition Tolerance

通常分布式系统的各结点部署在不同的子网,这就是网络分区,不可避免的会出现由于网络问题而导致结点之间通信失败,此时仍可对外提供服务,这叫分区容忍性。(P 是一定存在的!!!

上图中,商品信息读写满足分区容忍性就是要实现如下目标:

  1. 主数据库向从数据库同步数据失败不影响读写操作。

  2. 其一个结点挂掉不影响另一个结点对外提供服务。

如何实现分区容忍性?

  1. 尽量使用异步取代同步操作,例如使用异步方式将数据从主数据库同步到从数据,这样结点之间能有效的实现松耦合。

  2. 添加从数据库结点,其中一个从结点挂掉其它从结点提供服务。

分布式分区容忍性的特点:分区容忍性分是布式系统具备的基本能力

总结:在所有分布式事务场景中不会同时具备 CAP 三个特性,因为在具备了 P 的前提下 C 和 A 是不能共存的。

(2). CAP 常见的组合形式?

① AP

 放弃一致性,追求分区容忍性和可用性。这是很多分布式系统设计时的选择。

 例如:上边的商品管理,完全可以实现 AP,前提是只要用户可以接受所查询到的数据在一定时间内不是最新的即可。

 通常实现 AP 都会保证最终一致性,后面将的 BASE 理论就是根据 AP 来扩展的,一些业务场景比如:订单退款,今日退款成功,明日账户到账,只要用户可以接受在一定的时间内到账即可。

② CA

 放弃可用性,追求一致性和分区容错性,zookeeper 其实就是追求的强一致,又比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。

③ CA

 放弃分区容忍性,即不进行分区,不考虑由于网络不通或结点挂掉的问题,则可以实现一致性和可用性。那么系统将不是一个标准的分布式系统,是一个单体系统

(3). 总结

  CAP 是一个已经被证实的理论,一个分布式系统最多只能同时满足:一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)这三项中的两项。它可以作为我们进行架构设计、技术选型的考量标准。对于多数大型互联网应用的场景,结点众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到 N 个 9(99.99..%),并要达到良好的响应性能来提高用户体验,因此一般都会做出如下选择:保证 P 和 A ,舍弃 C 强一致,保证最终一致性

2. Base 理论分析

(1). 背景

  CAP 理论告诉我们一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三项中的两项,其中 AP 在实际应用中较多,AP 即舍弃一致性,保证可用性和分区容忍性,但是在实际生产中很多场景都要实现一致性,比如前边我们举的例子主数据库向从数据库同步数据,即使不要一致性,但是最终也要将数据同步成功来保证数据一致,这种一致性和 CAP 中的一致性不同,CAP 中的一致性要求 在任何时间查询每个结点数据都必须一致,它强调的是强一致性Base 理论强调的是最终一致性,是允许可以在一段时间内每个结点的数据不一致,但是经过一段时间每个结点的数据必须一致,它强调的是最终数据的一致性。

(2). Base 理论

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE 理论是对 CAP 中 AP 的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足 BASE 理论的事务,我们称之为“柔性事务”。

  • 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如电商网站交易付款出现问题了,商品依然可以正常浏览。

  • 软状态:由于不要求强一致性,所以 BASE 允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。

  • 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变 为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。

3. 分布式事务解决方案分类

(1). 强一致性

 任意时刻数据都是一致的,常见的有:2PC、3PC。

(2). 弱一致性

 允许某一时刻不一致,承诺在一定时间内变成一致的,常见的有 TCC。(Try-Confirm-Cancel 代码层面)

(3). 最终一致性

 允许数据不一致,但是最终最终,数据还是得一致的业务层面。 常见的有:本地消息表、最大努力通知。

三. 强一致性-2PC

1. 2PC 原理

(1). 含义

  2PC(Two-phase commit protocol) 顾名思义,就是分两阶段提交,准备阶段(Prepare phase)、提交阶段(commit phase),2 是指两个阶段,P 是指准备阶段,C 是指提交阶段。

(2). 流程

 2PC 引入一个新的角色,事务协调器,用来协调各个参与者的提交和回滚。

A. 准备阶段:协调器给各个参与者发送准备指令,同步阻塞等待所有参与者响应之后进入提交阶段。

PS:事务管理器给每个参与者发送 Prepare 消息,每个数据库参与者在本地执行事务,并写本地的 Undo/Redo 日志,此时事务没有提交。(Undo 日志是记录修改前的数据,用于数据库回滚,Redo 日志是记录修改后的数据,用于提交事务后写入数据文件)

B. 提交阶段:①假设所有参与者均返回成功,那么协调器向所有参与者发送提交事务的指令,并阻塞等待所有事务执行成功后返回执行成功,最后释放锁资源。

       ② 假设有 1 个参数返回失败,那么协调器则向所有参与者发送回滚事务的指令,并阻塞等待所有事务回滚成功后返回执行完成,最后释放锁资源。

成功流程:

失败流程:

2. 2PC 异常剖析

(1). 提交阶段失败

 A. 提交事务失败:只能不断重试,因为有可能一些参与者的事务已经提交成功了,只能不断的重试,直到提交成功,到最后真的不行只能人工介入处理。

 B. 回滚事务失败:不断重试,直到所有参与者都回滚了,不然那些在第一阶段准备成功的参与者会一直阻塞着。

总结:2PC 是一个同步阻塞协议,像第一阶段协调者会等待所有参与者响应才会进行下一步操作,当然第一阶段的协调者有超时机制,假设因为网络原因没有收到某参与者的响应或某参与者挂了,那么超时后就会判断事务失败,向所有参与者发送回滚命令。在第二阶段协调者的没法超时,因为按照我们上面分析只能不断重试!

(2). 事务协调器故障

(协调者故障,通过选举得到新协调者)

 A. 假设协调者在发送准备命令之前挂了,还行等于事务还没开始。

 B. 假设协调者在发送准备命令之后挂了,这就不太行了,有些参与者等于都执行了处于事务资源锁定的状态。不仅事务执行不下去,还会因为锁定了一些公共资源而阻塞系统其它操作。

 C. 假设协调者在发送回滚事务命令之前挂了,那么事务也是执行不下去,且在第一阶段那些准备成功参与者都阻塞着。

 D. 假设协调者在发送回滚事务命令之后挂了,这个还行,至少命令发出去了,很大的概率都会回滚成功,资源都会释放。但是如果出现网络分区问题,某些参与者将因为收不到命令而阻塞着。

 E. 假设协调者在发送提交事务命令之前挂了,这个不行,傻了!这下是所有资源都阻塞着。

 F. 假设协调者在发送提交事务命令之后挂了,这个还行,也是至少命令发出去了,很大概率都会提交成功,然后释放资源,但是如果出现网络分区问题某些参与者将因为收不到命令而阻塞着。

3. XA 方案

  参考:https://www.cnblogs.com/dyzcs/p/13780668.html

4. .Net 下的 DTC 模式

 EF 支持,EFCore 不支持,EF Core 中的 System.Transactions 实现将不包括对分布式事务的支持,因此不能使用 TransactionScope 或 CommittableTransaction 来跨多个资源管理器协调事务。主要分布式事务需要依赖于 Windows 系统的 MSDTC 服务,但.NET Core 要实现跨平台,基于跨平台的分布式事务没有统一的标准,后续版希望改进。

详见:https://www.cnblogs.com/yaopengfei/p/7748221.html 底部。

开启 msdtc 服务的步骤: cmd 命令→net start msdtc

主要依赖下面这个服务:

5. Seata 方案

 参考:https://www.cnblogs.com/dyzcs/p/13780668.html

Seata 实现 2PC 与传统 2PC 的差别

 架构层次方面:传统 2PC 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身,通过 XA 协议实现,而 Seata 的 RM 是以 jar 包的形式作为中间件层部署在应用程序这一侧的。

 两阶段提交方面:传统 2PC 无论第二阶段的决议是 commit 还是 rollback ,事务性资源的锁都要保持到 Phase2 完成才释放。而 Seata 的做法是在 Phase1 就将本地事务提交,这样就可以省去 Phase2 持锁的时间,整体提高效率。

6. 总结

(1). 2PC 是一种尽量保证强一致性的分布式事务,因此它是同步阻塞的,而同步阻塞就导致长久的资源锁定问题,总体而言效率低,并且存在单点故障(协调器)问题,在极端条件下存在数据不一致的风险。

(2). 2PC 适用于数据库层面的分布式事务场景,而我们业务需求有时候不仅仅关乎数据库,也有可能是上传一张图片或者发送一条短信。

四. 强一致性-3PC

1. 3PC 含义与流程

 3PC 包含了三个阶段:分别是准备阶段(CanCommit)、预提交阶段(PreCommit)和提交阶段(DoCommit)。相比于 2PC 它在参与者中也引入了超时机制,并且新增了一个阶段使得参与者可以利用这一个阶段统一各自的状态。

PS:3PC 的预提交阶段等价于 2PC 的准备阶段,3PC 的提交阶段等价于 2PC 的提交阶段,3PC 的准备阶段是新增的。

(1). 准备阶段

 准备阶段的变更成不会直接执行事务,而是会先去询问此时的参与者是否有条件接这个事务,因此不会一来就干活直接锁资源,使得在某些资源不可用的情况下所有参与者都阻塞着。

(2). 预提交阶段

 预提交阶段的引入起到了一个统一状态的作用,它像一道栅栏,表明在预提交阶段前所有参与者其实还未都回应,在预提交阶段中表明所有参与者都已经回应了。

(3). 提交阶段

提交事务或者回滚事务。


2. 3PC 剖析

(1). 上面我们知道,2PC 是同步阻塞的,我们已经分析了协调者挂在了提交请求还未发出去的时候是最伤的,所有参与者都已经锁定资源并且阻塞等待着。

  那么 3PC 中引入了超时机制,参与者就不会傻等了,如果是等待提交命令超时,那么参与者就会提交事务了,因为都到了这一阶段了大概率是提交的,如果是等待预提交命令超时,那该干啥就干啥了,反正本来啥也没干。

  然而超时机制也会带来数据不一致的问题,比如在等待提交命令时候超时了,参与者默认执行的是提交事务操作,但是有可能执行的是回滚操作,这样一来数据就不一致了。

(2). 3PC 的引入是为了解决提交阶段 2PC 协调者和某参与者都挂了之后新选举的协调者不知道当前应该提交还是回滚的问题。

  新协调者来的时候发现有一个参与者处于预提交或者提交阶段,那么表明已经经过了所有参与者的确认了,所以此时执行的就是提交命令。

  所以说 3PC 就是通过引入预提交阶段来使得参与者之间的状态得到统一,也就是留了一个阶段让大家同步一下。

  但是这也只能让协调者知道该如果做,但不能保证这样做一定对,这其实和上面 2PC 分析一致,因为挂了的参与者到底有没有执行事务无法断定。

  所以说 3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但是不能保证数据一致,除非挂了的那个参与者恢复。

3. 总结

  3PC 引入了参与者超时机制,并且增加了预提交阶段使得故障恢复之后协调者的决策复杂度降低,但整体的交互过程更长了,性能有所下降,并且还是会存在数据不一致问题。所以 2PC 和 3PC 都不能保证数据 100%一致,因此一般都需要有定时扫描补偿机制

用户头像

C/C++后台开发技术交流qun:720209036 2022-05-06 加入

还未添加个人简介

评论

发布
暂无评论
分布式事务详解、理论分析、及强一致性(2PC、3PC)剖析_数据库_C++后台开发_InfoQ写作社区