分布式的一致性问题是我们需要掌握的重要基础知识,那么在了解这个基础知识之前,我们首先需要去了解一些经典的一致性协议和算法。在本文中我们将介绍一些经典的一致性协议和算法包括二阶段提交协议,三阶段提交协议,Paxos算法。
0、先导知识:
在讲解2PC和3PC之前,我们这里有一个先导知识要讲解
在分布式系统中,每一个机器节点虽然都能够明确地知道自己在进行事务操作过程中的结果是成功或失败,但却无法直接获取到其他分布式节点的操作结果。因此,当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引一个称为“协调者(Coordinator)”的组件来统一调度所有分布式节点的执行逻辑,这此被调度的分布式节点则被称为“参与者”(Participant)。协调者负责调度参与者的行为,并最终決定这些参与者是否要把事务真正进行提交。基于这个思想,衍生出了二阶段提交和三阶段提交两种协议
因此在2PC和3PC的协议中,首先我们要理解两种角色,一个是协调者,一个是参与者。协调者负责统一调度所有的分布式节点,而参与者就是我们具体执行事务的每个节点。
1、2PC
2PC,是Two- Phase Commit的缩写,即二阶段提交,是计算机网络尤其是在数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务处理过程中能够保持原子性和一致性而设计的一种算法。通常,二阶段提交协议也被认为是一种一致性协议,用来保证分布式系统数据的一致性。
那这个是什么意思呢?
其实说白了,二阶段提交协议就是将事务的提交过程分成两个阶段来进行处理。
这两个阶段分别是 准备阶段 和 执行阶段
在执行一个操作之前,协调者会先和参与者连接一下,问问参与者的状态。就像你想请女孩子吃饭,在提出吃饭请求之前,总是会礼貌性的问句,在吗?
准备阶段就是——在吗?
执行阶段就是——一起吃个饭
我们先来搞个图示来形象的先来看一看,挑选其中最为典型的两种场景:成功执行事务的时候和失败执行事务的时候
我们用文字来解释一下这个过程:
第一阶段:提交事务请求
1、事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
2、执行事务
各参与者节点执行事务操作,将未执行的请求和准备做的请求信息记入事务日志中。
3、参与者们向协调者反馈事务询问的响应
如果参与者成功执行了事务操作,那么就反馈给协调者yes响应,表示事务可以执行,如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行。
第二阶段:执行事务提交
在第一阶段的时候,我们收到的请求有两种可能:
假如所有的参与者返回的都是yes(准备好了),那么执行事务提交;假如有任何一个参与者返回的响应是No(没有准备好),或者等待超时了,那么协调者会认为有的参与者不具备执行本次操作的条件,选择中断事务操作,因此就会回滚事务。
我们分开来看:
执行事务提交
1、发送提交请求
协调者向所有参与者节点发出Commit请求。
2、事务提交
参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。
3、反馈事务提交结果
参与者在完成事务提交之后,向协调者发送Ack消息。
4、完成事务
协调者接收到所有参与者反馈的Ack消息后,完成事务。
中断事务
1、发送回滚请求
协调者向所有参与者节点发出Rollback请求
2、事务回滚
参与者接收到Rollback请求后,会利用在阶段一中记录的未执行的请求信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
3、反愧事务回滚结果
参与者在完成事务回滚之后,向协调者发送Ack消息。
4、中断事务
协调者接收到所有参与者反馈的Ack消息后,完成事务中断。
优缺点分析
二阶段提交协议的优点:原理简单,实现方便
二阶段提交协议的缺点:同步阻器、单点问题、数据不一致、并且二阶段提交协议也太过保守
同步阻塞
二阶段提交协议存在的最明显也是最大的一个问题就是同步阻塞,这会极大地限制分布式系统的性能。在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,将无法进行其他任何操作。
单点问题
协调者的角色在整个二阶段提交协议中起到了非常重要的作用。一旦协调者出现问题,那么整个二阶段提交流程将无法运转,更为严重的是,如果协调者是在阶段二中出现问题的话,那么其他参与者将会一直处于锁定事务资源的状态中,而无法继续完成事务操作。
数据不一致
在执行事务提交的时候,当协调者向所有的参与者发送Commit请求之后,发生了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了Commit请求。于是,这部分收到了Commit请求的参与者就会进行事务的提交,而其他没有收到Commit请求的参与者则无法进行事务提交,于是整个分布式系统便出现了数据不一致性现象。
大过保守
如果在协调者指示参与者进行事务提交询向的过程中,参与者出现放障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠其自身的超时机制来判断是否需要中断事务,这样的策略显得比较保守。换句话说,二阶段提交协议没有设计较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败。
2、3PC
看过了2PC,那么3PC就比较好理解了。
3PC,是Three-PhaseCommit的缩写,即三阶段提交,是2PC的改进版,其将二阶段提交协议的“提交事务请求”过程一分为二,形成了由CanCommit.PreCommit和doCommit三个阶段组成的事务处理协议。
整个协议流程呢,我们还是学刚才,看图说话:
那么看完这张图之后,我们用文字来描述一个3PC的过程
第一阶段
阶段一叫做CanCommit,也就是对应图中“可以提交吗”的提问
1、事务询问。
协调者向所有的参与者发送一个包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
2、各参与者向协调者反馈事务询问的响应。参与者在接收到来自协调者的canCommit请求后,正常情况下,如果其自身认为可以顺利执行事务,那么会反馈Yes响应,并进人预各状态,否则反馈No响应。
第二阶段
阶段二叫做PreCommit,也就是对应图中“准备提交了”的动作
执行事务预提交
假如协调者从所有的参与者获得的反锁都是Yes响应,那么就会执行事务预提交。
1、发送预提交请求。
协调者向所有参与者节点发出preCommit的请求,并进入Prepared阶段。
2、事务顶提交。
参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。
3、各参与者向协调者反馈事务执行的响应。如果参与者成功执行了事务操作,那么就会反馈给协调者Ack响应,同时等待最终的指令:提交(commit)或中止(abort)。
中断事务
假如任何一个参与者向协调者反馈了NO响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
1、发送中断请求。
协调者向所有参与者节点发出abort请求。
2、中断事务。
无论是收到来自协调者的abort请求,或者是在等待协调者请求过程中出现超时,参与者都会中断事务。
第三阶段
阶段三叫做DoCommit,也就是对应图中“提交事务”的动作
该阶段将进行真正的事务提交,会存在以下两种可能的情况。
执行提交
1、发送提交请求。
进人这一阶段,假设协调者处于正常工作状态,并且它接收到了来自所有参与者的Ack响应,那么它将从“预提交”状态转换到“提交”状态,并向所有的参与者发送doCommit请求。
2、事务提交。
参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。
3、反馈事务提交结果。
参与者在完成事务提交之后,向协调者发送Ack消息。
4.完成事务。
协调者接收到所有参与者反馈的Ack消息后,完成事务。
中断事务
进人这一阶段,假设协调者处于正常工作状态,并且有任意一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
1、发送中断请求。协调者向所有的参与者节点发送abort请求。
2、事务回滚。
参与者按收到abort请求后,会利用其在阶段二中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
优缺点分析
三阶段提交协议的优点:
相较于二阶段提交协议,三阶段提交协议最大的优点就是降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致。
我们来详细的分析一下,在二阶段提交的时候,是不是协调者询问参与者,能执行吗?然后就直接执行了,这个时候事务资源就被占用了,如果此时事务执行失败,事务资源是不是一直被锁定着呢?那么三阶段提交的时候,发送了一个PreCommit请求,假如这个时候发现事务无法执行,直接就中断了,这样子是不是就降低了参与者的阻塞范围呢?而且当单点出现故障的时候,要执行的事务也不会在故障机器上执行,而是去其他正常机器上执行,这样子是不是也就使得后续数据可以一致呢?
三阶段提交协议的缺点:
三阶段提交协议在去除阻塞的同时也引人了新的问题,那就是在参与者接收到preCommit消息后,如果网络出现分区,此时协调者所在的节点和参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。
所以说,三阶段请求一定程度上解决了数据不一致的问题,但是又没有完全解决。
3、Phxos算法
接下来我们来看目前使用最为广泛的Phxos算法
Phxos算法是 基于消息传递 且具有 高效容错特性 的一致性算法,也是公认的解决分布式一致性问题 最有效的算法之一。
问题产生背景——拜占庭将军问题
拜占庭是古代东罗马帝国的首都,由于地域宽广,守卫边境的多个将军(系统中的多个节点)需要通过信使来传递消息,达成某些一致的决定。但由于信使中可能存在叛徒(系统中节点出错),这些叛徒将努力向不同的将军发送不同的消息,试图会干扰一致性的达成。
这就是著名的“拜占廷将军问题”。从理论上来说,在分布式计算领城,试图在异步系统和不可靠的通道上来达到一致性状态是不可能的,因此在对一致性的研究过程中,都往往假设信道是可靠的。而事实上,大多数系统都是部署在同一个局域网中的,因此消息被篡改的情况非常罕见,另一方面,由于硬件和网络原因而造成的消息不完整问题,只需一套简单的校验算法即可避免一—因此,在实际工程实战中,可以假设不存在拜占庭问题,也即假设所有消息都是完整的,没有被篡改的。那么,在这种情况下需要什么样的算法来保证一致性呢?
Lamport 在1990 年提出了一个理论上的一致性解決方案
算法的故事模型
在古希腊有一个叫做Paxos的小岛,岛上采用议会的形式来通过法令,议会中的议员通过信使进行消息的传递。值得注意的是,议员和信使都是兼职的,他们随时有可能会离开议会厅,并且信使可能会重复的传递消息,也可能一去不复返(这种情况就对应了分布式系统的节点故障和网络故障)。因此,议会协议要保证在这种情况下法令仍然能够正确的产生,并且不会出现冲突。
怎么来解决呢?我们先看一下思路
假设有一组可以提出提案的进程集合,那么对于一个一致性算法来说需要保证以下几点:
- 在这些被提出的提案中,只有一个会被选定。
- 如果没有提案被提出,那么就不会有被选定的提案。
- 当一个提案被选定后,进程应该可以获取被选定的提案信息。
那如果我们要保证一致性的安全,还需要做到以下几点:
- 只有被提出的提案才能被选定(Chosen) 。
- 只能有一个值被选定。
- 如果某个进程认为某个提案被选定了,那么这个提案必须是真的被选定的那个
算法的抽象过程
对应上述的故事模型,我们可以把Paxos算法划分为3种角色:Proposer、Acceptor、Learner,在实现中一个节点可以担任多个角色。
- Proposer负责提出提案
- Acceptor负责对提案进行投票
- Learner获取投票结果,并帮忙传播
运行过程分为两个阶段,Prepare阶段和Accept阶段。
Proposer需要发出两次请求,Prepare请求和Accept请求。Acceptor根据其收集的信息,接受或者拒绝提案。
Prepare阶段
- Proposer选择一个提案编号n,发送Prepare(n)请求给超过半数(或更多)的Acceptor。
- Acceptor收到消息后,如果n比它之前见过的编号大,就回复这个消息,而且以后不会接受小于n的提案。另外,如果之前已经接受了小于n的提案,回复那个提案编号和内容给Proposer。
Accept阶段
- 当Proposer收到超过半数的回复时,就可以发送Accept(n, value)请求了。 n就是自己的提案编号,value是Acceptor回复的最大提案编号对应的value,如果Acceptor没有回复任何提案,value就是Proposer自己的提案内容。
- Acceptor收到消息后,如果n大于等于之前见过的最大编号,就记录这个提案编号和内容,回复请求表示接受。
- 当Proposer收到超过半数的回复时,说明自己的提案已经被接受。否则回到第一步重新发起提案。
活锁问题
如果两个Proposer还处于第一阶段时,互相提出编号更大的提案,这时候会出现“活锁”状态,陷入了无限死循环中(破坏了算法活性)。
怎么防止活锁问题呢?
最简单的解决方案:在Proposer失败之后给一个随机的等待时间,这样就减少同时请求的可能。
也可以选出一个主Proposer,只有主Proposer可以提出提案。
至于怎么选择,不属于Paxos的范畴,可以参考RAFT使用竞选,谁快谁当选;也可以参考PBFT的依次成为leader等。
4、小结
我们主要从协议设计和原理实现角度详细讲解了二阶段提交协议、三阶段提交协议和Paxos这三种典型的一致性算法。可以说,这三种一致性协议都是非常优秀的分布式一致性协议,都从不同方面不同程度地解决了分布式数据一致性问题,使用范明都非常广泛。其中二阶段提交协议解决了分布式事务的原子性问题,保证了分布式事务的多个参与者要么都执行成功,要么都执行失败。但是,在二阶段解决部分分布式事务问题的同时,依然存在一些难以解决的诸如同步阻塞、无限期等待和“脑裂”等问题。
三阶段提交协议则是在二阶段提交协议的基础上,添加了PreCommit过程,从而避免了二阶段提交协议中的无限期等待问题。
而Paxos算法引入了“过半”的理念,通俗地讲就是少数服从多数的原则。同时,Paxos算法支持分布式节点角色之间的轮换,这极大地避免了分布式单点的出现,因此Paxos算法既解决了无限期等待问题,也解决了“脑裂”问题,是目前来说最优秀的分布式一致性协议之一。