分布式学习笔记—一致性算法,Paxos算法
1. 一致性算法:Paxos算法
Paxos算法
是Lamport提出的一种基于消息传递的分布式一致性算法- 自Paxos问世以来就持续垄断了分布式一致性算法,
Paxos这个名词几乎等同于分布式一致性
。Google的很多大型分布式系统都采用了Paxos算法来解决分布式一致性问题,如Chubby、Megastore以及Spanner等。开源的ZooKeeper,以及MySQL 5.7推出的用来取代传统的主从复制的MySQL Group Replication等纷纷采用Paxos算法解决分布式一致性问题。 - 然而,Paxos的最大特点就是
难
,不仅难以理解,更难以实现。
2. Paxos 解决了什么问题
- 解决了分布式系统一致性问题。
3. Paxos相关概念
首先一个很重要的概念叫提案(Proposal)
。最终要达成一致的value就在提案里。提案 (Proposal)
:Proposal信息包括提案编号 (Proposal ID) 和提议的值 (Value)- 在Paxos算法中,有如下角色:
Client:客户端
,客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求。Proposer:提案发起者
,提案者提倡客户请求,试图说服Acceptor对此达成一致,并在发生冲突时充当协调者以推动协议向前发展Acceptor:决策者
,可以批准提案,Acceptor可以接受(accept)提案;如果某个提案被选定(chosen),那么该提案里的value就被选定了Learners:最终决策的学习者
,学习者充当该协议的复制因素
4. Paxos问题描述
- 假设有一组可以提出提案的进程集合,那么对于一个一致性算法
需要保证以下几点
:- 在这些被提出的提案中,只有一个会被选定
- 如果没有提案被提出,就不应该有被选定的提案。
- 当一个提案被选定后,那么所有进程都应该能学习(learn)到这个被选定的value
5. Paxos算法—Proposer生成提案
- 这里有个比较重要的思想:Proposer生成提案之前,应该先去『学习』已经被选定或者可能被选定的value,然后以该value作为自己提出的提案的value。如果没有value被选定,Proposer才可以自己决定value的值。这样才能达成一致。这个学习的阶段是通过一个『Prepare请求』实现的。
- 于是我们得到了如下的提案生成算法:
- Proposer选择一个新的提案编号N,然后向某个Acceptor集合(半数以上)发送请求,要求该集合中的每个Acceptor做出如下响应(response)
- Acceptor向Proposer承诺保证不再接受任何编号小于N的提案。
- 如果Acceptor已经接受过提案,那么就向Proposer反馈已经接受过的编号小于N的,但为最大编号的提案的值。
- Proposer选择一个新的提案编号N,然后向某个Acceptor集合(半数以上)发送请求,要求该集合中的每个Acceptor做出如下响应(response)
- 我们将该请求称为编号为N的Prepare请求。
- 如果Proposer收到了半数以上的Acceptor的响应,那么它就可以生成编号为N,Value为V的提案[N,V]。这里的V是所有的响应中编号最大的提案的Value。如果所有的响应中都没有提案,那 么此时V就可以由Proposer自己选择。
- 生成提案后,Proposer将该提案发送给半数以上的Acceptor集合,并期望这些Acceptor能接受该提案。我们称该请求为Accept请求。
6. Acceptor接受提案
- 刚刚讲解了Paxos算法中Proposer的处理逻辑,怎么去生成的提案,下面来看看Acceptor是如何批准提案的
- 根据刚刚的介绍,一个Acceptor可能会受到来自Proposer的两种请求,分别是Prepare请求和Accept请求,对这两类请求作出响应的条件分别如下
- Prepare请求:Acceptor可以在任何时候响应一个Prepare请求
- Accept请求:在不违背Accept现有承诺的前提下,可以任意响应Accept请求
- 因此,对Acceptor接受提案给出如下约束:
- P1a:一个Acceptor只要尚未响应过任何编号大于N的Prepare请求,那么他就可以接受这个编号为N的提案。
7. Paxos算法优化
- 从Proposer和Acceptor对提案的生成和批准两方面来讲解了Paxos算法在提案选定过程中的算法细节,同时也在提案的编号全局唯一的前提下,获得了一个提案选定算法,接下来我们再对这个初步算法做一个小优化,尽可能的忽略Prepare请求
- 如果Acceptor收到一个编号为N的Prepare请求,在此之前它已经响应过编号大于N的Prepare请求。根据P1a,该 Acceptor不可能接受编号为N的提案。因此,该Acceptor可以忽略编号为N的Prepare请求。
- 通过这个优化,每个Acceptor只需要记住它已经批准的提案的最大编号以及它已经做出Prepare请求响应的提案的最大编号,以便出现故障或节点重启的情况下,也能保证P2c的不变性,而对于Proposer来说,只要它可以保证不会产生具有相同编号的提案,那么就可以丢弃任意的提案以及它所有的运行时状态信息
8. Paxos算法描述
- 我们来对Paxos算法的提案选定过程进行下总结,那结合Proposer和Acceptor对提案的处理逻辑,就可以得到类似于两阶段提交的算法执行过程,Paxos算法分为两个阶段。具体如下:
- 阶段一:
- Proposer选择一个提案编号N,然后向半数以上的Acceptor发送编号为N的Prepare请求。
- 如果一个Acceptor收到一个编号为N的Prepare请求,且N大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的案。
- 阶段二:
- 如果Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应,那么它就会发送一个针对[N,V]提案的Accept请求给半数以上的Acceptor。注意:V就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定。
- 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor没有对编号大于N的Prepare请求做出过响应,它就接受该提案。
9.Learner学习被选定的value
- 方案一:
- Learner获取一个已经被选定的提案的前提是,该提案已经被半数以上的Acceptor批准,因此,最简单的做法就是一旦Acceptor批准了一个提案,就将该提案发送给所有的Learner
- 很显然,这种做法虽然可以让Learner尽快地获取被选定的提案,但是却需要让每个Acceptor与所有的Learner逐个进行一次通信,通信的次数至少为二者个数的乘积
- 方案二:
- 另一种可行的方案是,我们可以让所有的Acceptor将它们对提案的批准情况,统一发送给一个特定的Learner(称为主Learner), 各个Learner之间可以通过消息通信来互相感知提案的选定情况,基于这样的前提,当主Learner被通知一个提案已经被选定时,它会负责通知其他的learner
- 在这种方案中,Acceptor首先会将得到批准的提案发送给主Learner,再由其同步给其他Learner.因此较方案一而言,方案二虽然需要多一个步骤才能将提案通知到所有的learner,但其通信次数却大大减少了,通常只是Acceptor和Learner的个数总和,但同时,该方案引入了一个新的不稳定因素:主Learner随时可能出现故障
- 方案三:
- 在讲解方案二的时候,我们提到,方案二最大的问题在于主Learner存在单点问题,即主Learner随时可能出现故障,因此,对方案二进行改进,可以将主Learner的范围扩大,即Acceptor可以将批准的提案发送给一个特定的Learner集合,该集合中每个Learner都可以在一个提案被选定后通知其他的Learner。
- 这个Learner集合中的Learner个数越多,可靠性就越好,但同时网络通信的复杂度也就越高
10.如何保证Paxos算法的活性
- 我们已经基本上了解了Paxos算法的核心逻辑,那接下来再来看看Paxos算法在实际过程中的一些细节
- 活性:最终一定会发生的事情:最终一定要选定value
- 假设存在这样一种极端情况,有两个Proposer依次提出了一系列编号递增的提案,导致最终陷入死循环,没有value被选定,具体流程如下:
- 解决:通过选取主Proposer,并规定只有主Proposer才能提出议案。这样一来只要主Proposer和过半的Acceptor能够正常进行网络通信,那么但凡主Proposer提出一个编号更高的提案,该提案终将会被批准,这样通过选择一个主Proposer,整套Paxos算法就能够保持活性