携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
书接前几回,继续分析分布式事务相关概念
6.面试宝典-数据库事务概述 - 掘金 (juejin.cn)
7.面试宝典-数据库索引概述 - 掘金 (juejin.cn)
8.面试宝典-数据库索引数据结构 - 掘金 (juejin.cn)
1.产生背景
由于业务越来越复杂,数据越来越膨胀,微服务架构的兴起,传统的单结点数据库和单个应用网站已经无法满足需求,我们可能会把业务拆解成若干个微服务,根据业务垂直分库,或者根据分片规则水平分表。如果单个请求需要对多个服务/数据库操作,就涉及到多个服务/数据库之间的一致性问题,此时就需要分布式事务。
事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向 系统提交,要么都执行、要么都不执行 。事务是一个不可分割的工作逻辑单元.
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。 简单来说:就是事务操作在多结点的数据库上,如何保证一致性
2.相关概念
2.1.刚性事务和柔性事务
刚性事务:遵循ACID原则,强一致性。
柔性事务:遵循BASE理论,最终一致性;与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。
2.2.ACID原则,BASE理论与CAP理论
2.2.1.ACID原则
事务必须具备以下四个属性,简称 ACID 属性:
原子性(Atomicity) 1. 事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执 行。
一致性(Consistency) 2. 当事务完成时,数据必须处于一致状态。
隔离性(Isolation) 3. 对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方 式依赖于或影响其他事务。
永久性(Durability) 4. 事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性。
2.2.2.分布式事务CAP理论
1.一致性(consistency)
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。。
关于一致性,如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。此外,如果允许存在部分数据不一致,那么就称之为弱一致性。
2.可用性(Availability)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。“有限的时间内”是指,对于用户的一个操作请求,系统必须能够在指定的时间内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。
3.分区容错性(partition-tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
CA without P: 如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。
CP without A: 如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
AP wihtout C: 要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。
Nacos从1.0版本选择Ap和CP混合形式实现注册中心,默认情况下采用Ap保证服务可用性,CP形式底层采用Raft协议保证数据的一致性问题。 如果选择为Ap模式,注册服务的实例仅支持临时模式,在网络分区的的情况允许注册服务实例。 选择CP模式可以支持注册服务的实例为持久模式,在网络分区的产生了抖动情况下不允许注册服务实例。
2.2.3.柔性事务BASE理论
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
1.基本可用(Basically Available)
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
2.软状态( Soft State)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
3.最终一致( Eventual Consistency)
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。
实现分布式事务的方案比较多,常见的比如基于 XA 协议的 2PC、3PC,基于业务层的 TCC,阿里的seata,还有应用消息队列 + 消息表实现的最终一致性方案等
2.3 XA规范
DTP模型
DTP (distributed Transaction Processing) 模型,是一个名叫 The Open Group 的组织提出的分布式事务处理规范,已经成为事实上的事务模型组件的行为标准。
DTP规范包括AP(应用程序)、RM(资源管理器)、TM(事务管理器)三部分组成,其具体分工如下:
- AP: 负责事务发起和结束;
- RM:负责管理每个数据库的连接数据源;
- TM:负责事务的全局管理,包括事务的生命周期管理和资源的分配协调;
XA协议
XA是一个分布式事务协议,由Tuxedo提出,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。 XA接口提供RM资源管理器与TM事务管理器之间进行通信的标准接口,在tm与多个rm之间形成了一个双向通信桥梁,保证操作多个数据库时的ACID特性!(注意XA没有TC这个角色)
XA协议采用两阶段提交方式来管理分布式事务。该协议分为预备和提交两个阶段:
- 预备:负责执行业务逻辑
- 提交:负责事务的commit
XA实现分布式事务的原理如下:
缺陷
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。 但是,XA也有致命的缺点
-
商业支持:XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
-
性能问题:XA无法满足高并发场景,这个其实是显而易见的问题。XA是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持折数据库的锁所有资源服务需要一直维持事务直到最晚的事务preCommit之后(木桶效应)。因此事务所涉及的环节越多那么对于每个资源实际上都是保持了一个比较长的长事务,进而加重数据库本身的负担(资源锁定,回滚,死锁检测等)。如果有其他人要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险
-
一致性问题:虽然commit是一个耗时以及操作都相对简单的过程,但是仍然可能发生异常,即在preCommit都成功之后,协调者发送commit指令,或因为网络问题不可达或因为资源服务(数据库)本身异常,那么仍旧会产生不一致,即使资源服务恢复之后它也无法决定是执行commit还是rollback操作。
-
单点故障问题:如上述服务自身crash了之后,那么所有的资源服务将无所是从,无论是在任何阶段,或导致资源无法释放,或导致不知道应该回滚还是提交(与一致性问题一致,只是出现异常的是协调者服务)。
2.4. 2pc,3pc
2.4.1.两阶段提交协议(2PC)
2PC 是二阶段提交(Two-phase Commit)的缩写,分为两个阶段完成,第一个阶段是准备阶段(prepare),第二个阶段是提交阶段(commit/rollback),准备阶段和提交阶段都是由事务管理器(协调者)发起的,协调的对象是资源管理器(参与者)。
准备阶段:协调者向所有参与者发起指令,每个参与者评估自己的状态,如果参与者评估指令可以完成,参与者会写 redo 和 undo 日志,然后锁定资源,执行操作,但不提交。
提交阶段:如果所有参与者返回准备成功,即预留资源和执行操作成功,协调者会向所有参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者返回准备失败,即预留资源或者执行操作失败,协调者会向所有参与者发起中止指令,参与者取消已经变更的事务,执行 undo 日志并释放锁定的资源。
2PC问题
阻塞:事务执行过程中所有参与者都是阻塞型的,占用的资源被一直锁定,不会被释放,第三方参与者访问参与者占有的资源时会被阻塞,影响性能;
单点故障:协调者一旦发生故障,参与者与协调者失去同步,参与者会被一直阻塞。尤其在提交阶段,所有参与者都处于锁定资源状态中,无法完成事务操作;(虽然可以选举出新的协调者,但仍然无法解决参与者被阻塞的问题);
数据不一致:提交阶段协调者向参与者发送提交指令,发生局部网络故障,会出现一部分参与者未收到提交指令无法提交事务的情况,导致多个参与者之间出现数据不一致的现象;
二阶段提交协议在准备阶段需要等待所有参与者的反馈,因此可能造成数据库资源锁定时间过长,不适合高并发和子事务生命周长较长的业务场景。两阶段提交牺牲了一部分可用性来换取一致性
2.4.2. 三阶段提交协议(3PC)
与两阶段提交不同的是,三阶段提交有两个改动。 1、引入超时机制。同时在协调者和参与者中都引入超时机制。 2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
3PC是三阶段提交(Three-phase Commit)的缩写,是对2PC 的改进,3PC在2PC的准备阶段和提交阶段中加入一个预提交阶段。保证了在最后提交阶段之前,各参与者节点的状态都一致。同时在协调者和参与者中都引入超时机制解决了阻塞的问题,当参与者由于各种原因未收到协调者的commit 请求后,会对本地事务进行commit,不会一直阻塞等待,解决了2PC的单点故障问题,因此维基百科中定义3PC为“非阻塞”协议。
3PC 的三个阶段:CanCommit(准备阶段)、PreCommit(预提交阶段)、DoCommit(提交阶段) 准备阶段:协调者询问参与者是否可以执行事务提交操作,协调者只需要回答是还是不是,而不需要做真正的操作,这个阶段参与者在等待超时后会自动中止。
预提交阶段:如果在CanCommit准备阶段所有的参与者都返回可以执行操作,协调者向所有参与者发送预提交请求,然后参与者写 redo 和 undo 日志,锁定资源,执行操作,但不提交;如果在CanCommit准备阶段任何一个参与者返回不能执行操作的结果,则协调者向所有参与者发送中止指令。这里与两阶段提交协议的准备阶段是相似的,此阶段的参与者在等待超时后会自动提交。
提交阶段:如果所有参与者在PreCommit预提交阶段都返回成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;如果任何一个参与者返回失败,也就是预留资源或者执行操作失败,协调者向所有参与者发起中止指令,参与者取消已经变更的事务,执行 undo 日志,释放锁定的资源。这里与两阶段提交协议的提交阶段一致。
超时机制:
如果在CanCommit 准备阶段等待超时,则自动中止;
如果在PreCommit预提交阶段等待超时,则自动提交。
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
PreCommit阶段
协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
1.发送中断请求 协调者向所有参与者发送abort请求。
2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
doCommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
Case 1执行提交
1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
3.响应反馈 事务提交完之后,向协调者发送Ack响应。
4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。
Case 2中断事务
协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
1.发送中断请求 协调者向所有参与者发送abort请求
2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
3PC的问题
3PC 通过预提交阶段可以减少故障恢复时候的复杂性,但3PC 还是没能从根本上解决数据一致性的问题,比如在预提交阶段时协调者和其中一个参与者都发生了故障,此时其他参与者在超时情况下会自动选择提交或回滚,当新选举出的协调者上任时也会给活着的参与者发送提交命令,但发生故障的那个参与者还未恢复且无法确定是否执行了事务,这里就出现了数据不一致的情况。
事实上 2PC 和 3PC 都无法保证完全的数据一致性,于是需要利用补偿机制尽量确保数据的一致性。(tcc/saga/seata)