Paxos 介绍
Paxos算法是分布式IT大师Lamport提出的,主要目的是通过这个算法,让参与分布式处理的每个参与者逐步达成一致意见。用好理解的方式来说,就是在一个选举过程中,让不同的选民最终做出一致的决定。
Lamport为了讲述这个算法,假想了一个叫做Paxos的希腊城邦进行选举的情景,这个算法也是因此而得名。
在他的假想中,这个城邦要采用民主提议和投票的方式选出一个最终的决议,但由于城邦的居民没有人愿意把全部时间和精力放在这种事情上,所以他们只能不定时的来参加提议,不定时来了解提议、投票进展,不定时的表达自己的投票意见。
Paxos算法的目标就是让他们按照少数服从多数的方式,最终达成一致意见。
分布式消息传递背景
基于消息传递模型的分布式系统,不可避免的会发生以下异常:
- 进程可能会慢、被杀死或者重启;
- 消息可能会延迟、丢失、重复;
- 在基础Paxos场景中,先不考虑出现消息篡改(即拜占庭错误)的情况;
Paxos算法解决的问题,就是在一个可能发生上述异常的分布式系统中,如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性。注意,这里的“某个值”,可以是某一个具体的数值,比如,1,2,3,4,5。也可以是某一种具体的操作,这个操作可以序列化成“一个值”,只要这个“值”确定了,也就等于这个操作确定了。
即要达到:
- 所有提出的提案最终只有一个被选定
- 如果没有提案,那么最终没有任何选定的提案
- 当提案被选定后,所有客户端都应该能获取到提案信息
维基百科的定义
Paxos的目的是让整个集群的结点对某个值的变更达成一致。Paxos可以说是一个民主选举的算法——大多数节点的决定会成个整个集群的统一决定。任何一个点都可以提出要修改某个数据的提案,是否通过这个提案取决于这个集群中是否有超过半数的节点同意。取值一旦确定将不再更改,并且可以被获取到(不可变性,可读取性)。
简而言之,这个就有点像我们的人民代表大会制度,每一个与会代表都可以提出自己的提案,只要能够获得超过半数的代表同意,这个提案就可以获得通过。
Paxos算法
角色说明
Paxos把这个过程中的所有参与者划分成了几种角色,分别如下:
- Proposer:提议者,提出议案(同时存在一个或者多个,他们各自发出提案)
- Acceptor:接受者,收到议案后选择是否接受
- Learner:最终决策学习者,只学习正确的决议
- Client:产生议题者,发起新的请求
上面这一幅图表示出了各个角色之间的关系。这4种角色中最主要的是Proposer和Acceptor。
Proposer就像Client的代理人,由Proposer拿着Client的议题去向Acceptor提议,让Acceptor来做出决策。主要的交互过程在Proposer和Acceptor之间。
这幅图表示的是角色之间的逻辑关系,每一种角色就代表了一种节点类型。在物理部署环节,可以把每一种角色都部署在一台物理机器上,也可以组合任何两种或者多种角色部署在一台物理机器上,甚至于,把这四种角色都部署在同一台物理机器上也是可以的。
算法细节
Paxos算法描述非常的简单,就只有两步,决议的提出(Prepare)与批准(Accept)。就这么一句话,你懂了么?是不是云里雾里的。
Prepare-提出决议阶段
proposer选择一个提案编号n并将Prepare请求发送给acceptors中的一个多数派(即超过半数);acceptor收到Prepare消息后,如果提案的编号大于它已经回复的所有Prepare消息,则acceptor将自己上次接受的提案回复给proposer,并承诺不再回复小于n的提案;
上图是一个proposer和5个acceptor之间的交互,对2种不同的情况做了处理。
Accept-批准决议阶段
当一个proposer收到了多数acceptors对Prepare的回复后,就进入批准阶段。它要向回复Prepare请求的acceptors发送accept请求,包括编号n和value;
在不违背自己向其他proposer的承诺的前提下,acceptor收到accept请求后即接受这个请求。
可以看出,Proposer与Acceptor之间的交互主要有4类消息通信,这4类消息对应于Paxos算法的两个阶段4个过程。
Prepare-提议阶段
prepare 起草(Proposer -> Acceptor)
promises 允诺(Acceptor -> Proposer)
Accept-批准阶段
accept 接受(Proposer -> Acceptor)
accepted 已接受(Acceptor -> Proposer)
用两段提交方式来确定了一个值。
上面的图解都只有一个Proposer,但是实际肯定是有多个Proposer发出请求,所以在每个过程中都会有些特殊情况处理,这也是为了达成一致性所做的事情。
七个人约饭局的问题(两个Proposer参与)
现在用个生活化的例子来说明一下两个Proposer参与的情况,更多的Proposer和Acceptors的情况下,很可能就是交互的轮次会更多而已。
七个人约饭。有2个Proposers和5个Acceptors。A和B是Proposer,其余的C,D,E,F,G是Acceptor。
〇
这里是所有角色的初始状态,所有的Acceptors都没有响应过任何编号的提案,所以编号都是0,且选择都是NULL
①
这时候,A发起了第一轮建议,给所有的Acceptors发短信说,我们去吃海底捞吧,由于异常,只有两个Acceptors收到了这个Prepare(1, 海底捞)的短信。由于这两个Acceptors也是没有任何主见的人,所以他们就爽快的同意了A的建议,并且给A回复了承诺短信promise(1, NULL)
Paxos协议的第一阶段,就是proposer发起提案,每一个提案都需要包含一个提案的唯一编号。
对于acceptor来说,如果他还没有承诺过任何提案,那么就必须对收到的第一个提案做出承诺,即:把这个提案作为备选的可接受提案。做出承诺的方式就是给proposer做出承诺回复,回复的信息是proposal的提案编号和空值(NULL),NULL表示自己还没有承诺过任何提案
由于A只收到了D和G两个人的承诺,未能超过半数,所以无法对所有人发起确认消息(accept)。
Proposer必须要收到超过半数以上Acceptors的承诺,才能发起后续的accept请求
②
随后,B发起第二轮建议,给所有人发短信说,我们去吃披萨吧。Prepare(2,披萨),由于异常原因,只有C,D,E,F四个人处理了他的消息,C,E,F由于没有做出过任何承诺,所以给B回复了承诺短信promise(2, NULL),B由于已经给A做出过承诺,承诺了海底捞,所以给B回复了承诺promise(2, 海底捞)
如果Acceptor已经向Proposer承诺过某个编号的提案,在收到了新的提案时,会先去比较提案编号。
- 如果新的提案编号小于或者等于前面承诺过的提案编号,则会忽略这个新的提案;
- 如果新的提案编号大于曾经承诺过的提案编号,那就把曾经承诺过的提案连同新的提案编号回复给这个Proposer。
B收到了上述四个Acceptors的承诺回复,由于C,E,F三个人回复都相同,同意他说的去吃披萨,超过了半数,所以B给所有人发送了accept短信,通知大家都去吃披萨,Accept(2, 披萨)。由于异常原因,只有C,D收到了这个短信,并且接受了这个建议。
Proposer必须在收到了超过半数的Acceptors的承诺之后,向所有Acceptors发起accept请求,要求所有的Acceptors批准这个提案。
③
这个时候,A发起了第三轮建议,给所有Acceptors发送短信,还是建议去吃海底捞,Prepare(3,海底捞)。这时候C,E,F,G都收到了这个建议。C回复说,我已经批准了去吃披萨,Promise(3,披萨)。E,F则发现,他们之前已经承诺了去吃披萨,于是给A回复说,“我们还是去吃披萨吧”,Promise(3,披萨)。G则发现,他之前的承诺是去吃海底捞,所以还是回复说吃海底捞,Promise(3,海底捞)。
对于Acceptor来说,在收到Prepare消息的时候,如果它已经批准了(Accept)某项提案(Proposal),则必须把这个已经批准的提案回复给Proposer。如果他已经响应过之前的某项提案,则需要把之前响应过的最大编号的提案回复给Proposer。
A收到了这四个回复承诺,有三个(超过半数了)都说去吃披萨,A一看,只好少数服从多数,就给所有人回复短信说,“那好吧,听你们的就去吃披萨好了”,Accept(3,披萨)。
Proposer接收到的promises回复如果有超过半数达成一致,则必须要选择超过半数Acceptors承诺的值发起accept请求。
至此,所有的Proposers与Acceptors达成共识。