Google Chubby的做者Mike Burrows说过这个世界上只有一种一致性算法,那就是Paxos,其它的算法都是残次品。github
PAXOS能够用来解决分布式环境下,选举(或设置)某一个值的问题(好比更新数据库中某个user的age是多少)。分布式系统中有多个节点就会存在节点间通讯的问题,存在着两种节点通信模型:共享内存(Shared memory)、消息传递(Messages passing),Paxos是基于消息传递的通信模型的。它的假设前提是,在分布式系统中进程之间的通讯会出现丢失、延迟、重复等现象,但不会出现传错的现象。Paxos算法就是为了保证在这样的系统中进程间基于消息传递就某个值达成一致。算法
注意,在Paxos算法中是不考虑拜占庭将军问题的数据库
好比下面的状况:有三个服务器进程A,B,C运行在三台主机上提供数据库服务,当一个client链接到任何一台服务器上的时候,都能经过该服务器进行read和update的操做。首先先看一个A、B、C不那么均等的方法:promise
A、B、C选举主机名最大的服务器为master提供服务,全部的read、update操做都发生在它上面,其他的两台服务器只是slave(后者,在这里相似proxy),当client是链接在master的服务器的时候,其直接和master交互,就相似于单机的场景。当client时链接在slave的时候,全部的请求被转移到了master上进行操做,而后再结果返回给client。若是master挂了,那么剩余两台主机在检测到master挂的状况后,再根据主机名最大的方法选举master。服务器
上面的算法有一个问题,它让A、B、C不那么均等了,A、B、C存在角色之分,且在某个时候master挂机后,须要马上选举出新的master提供服务。同时,这个算法还要求各个服务器之间保持心跳。而PAXOS算法则不一样,PAXOS提供了一种将A、B、C等价的方式提供上面的操做,并保证数据的正确性。在某台主机宕机后,只要总数一半以上的服务器还存活,则整个集群依然能对外提供服务,甚至不须要心跳。网络
为描述Paxos算法,Lamport虚拟了一个叫作Paxos的希腊城邦,这个岛按照议会民主制的政治模式制订法律,可是没有人愿意将本身的所有时间和精力放在这种事情上。因此不管是议员,议长或者传递纸条的服务员都不能承诺别人须要时必定会出现,也没法承诺批准决议或者传递消息的时间。可是这里假设没有拜占庭将军问题(Byzantine failure,即虽然有可能一个消息被传递了两次,可是绝对不会出现错误的消息);只要等待足够的时间,消息就会被传到。另外,Paxos岛上的议员是不会反对其余议员提出的决议的。
首先将议员的角色分为proposers,Acceptors,和learners(容许身兼数职)。proposers提出提案,提案信息包括提案编号和提议的value;Acceptor收到提案后能够接受(accept)提案,若提案得到多数Acceptors的接受,则称该提案被批准(chosen);learners只能“学习”被批准的提案。划分角色后,就能够更精确的定义问题:
决议(value)只有在被proposers提出后才能被批准(未经批准的决议称为“提案(proposal)”);
在一次Paxos算法的执行实例中,只批准(chosen)一个value;
learners只能得到被批准(chosen)的value。
批准value的过程当中,首先proposers将value发送给Acceptors,以后Acceptors对value进行接受(accept)。为了知足只批准一个value的约束,要求经“多数派(majority)”接受的value成为正式的决议(称为“批准”决议)。这是由于不管是按照人数仍是按照权重划分,两组“多数派”至少有一个公共的Acceptor,若是每一个Acceptor只能接受一个value,约束2就能保证。整个过程(一个实例或称一个事务或一个Round)分为两个阶段:
phase1(准备阶段)
Proposer向超过半数(n/2+1)Acceptor发起prepare消息(发送编号)
若是prepare符合协议规则Acceptor回复promise消息,不然拒绝
phase2(决议阶段或投票阶段)
若是超过半数Acceptor回复promise,Proposer向Acceptor发送accept消息(此时包含真实的值)
Acceptor检查accept消息是否符合规则,消息符合则批准accept请求
根据上述过程当一个proposer发现存在编号更大的提案时将终止提案。这意味着提出一个编号更大的提案会终止以前的提案过程。若是两个proposer在这种状况下都转而提出一个编号更大的提案,就可能陷入活锁,违背了Progress的要求。这种状况下的解决方案是选举出一个leader,仅容许leader提出提案。可是因为消息传递的不肯定性,可能有多个proposer自认为本身已经成为leader。Lamport在The Part-Time Parliament一文中描述并解决了这个问题。
注意P1是不完备的。若是刚好一半Acceptor接受的提案具备value A,另外一半接受的提案具备value B,那么就没法造成多数派,没法批准任何一个value。
若是一个没有chosen过任何proposer提案的Acceptor在prepare过程当中回答了一个proposer针对提案n的问题,可是在开始对n进行投票前,又接受(accept)了编号小于n的另外一个提案(例如n-1),若是n-1和n具备不一样的value,这个投票就会违背P2c。所以在prepare过程当中,Acceptor进行的回答同时也应包含承诺:不会再接受(accept)编号小于n的提案。
假设A为整个Acceptor集合,B为一个超过A一半的Acceptor集合,B为A的子集,C也是一个超过A一半的Acceptor集合,C也是A的子集,有此可知任意两个过半集合中一定有一个共同的成员Acceptor;此说明了一个Acceptor能够接受不止一个提案,此时须要一个编号来标识每个提案,提案的格式为:[编号,Value],编号为不可重复全序的,由于存在着一个一个Paxos过程只能批准一个value这时又推出了一个约束P3;
该约束还能够表述为:
一旦一个具备value v的提案被批准(chosen),那么以后任何Acceptor再次接受(accept)的提案必须具备value v。
一旦一个具备value v的提案被批准(chosen),那么之后任何proposer提出的提案必须具备value v。
若是一个编号为n的提案具备value v,那么存在一个多数派,要么他们中全部人都没有接受(accept)编号小于n
的任何提案,要么他们已经接受(accept)的全部编号小于n的提案中编号最大的那个提案具备value v。
由于每一个Proposer均可提出多个议案,每一个议案最初都有一个不一样的Value因此要知足P3就又要推出一个新的约束P4;
一个显而易见的方法是当acceptors批准一个value时,将这个消息发送给全部learner。可是这个方法会致使消息量过大。
因为假设没有Byzantine failures,learners能够经过别的learners获取已经经过的决议。所以acceptors只需将批准的消息发送给指定的某一个learner,其余learners向它询问已经经过的决议。这个方法下降了消息量,可是指定learner失效将引发系统失效。
所以acceptors须要将accept消息发送给learners的一个子集,而后由这些learners去通知全部learners。
可是因为消息传递的不肯定性,可能会没有任何learner得到了决议批准的消息。当learners须要了解决议经过状况时,可让一个proposer从新进行一次提案。注意一个learner可能兼任proposer。
有A1, A2, A3, A4, A5 5位议员,就税率问题进行决议。议员A1决定将税率定为10%,所以它向全部人发出一个草案。这个草案的内容是:
现有的税率是什么?若是没有决定,则建议将其定为10%.时间:本届议会第3年3月15日;提案者:A1
在最简单的状况下,没有人与其竞争;信息能及时顺利地传达到其它议员处。
因而, A2-A5回应:
我已收到你的提案,等待最终批准
而A1在收到2份回复后就发布最终决议:
税率已定为10%,新的提案不得再讨论本问题。
这实际上退化为二阶段提交协议。
如今咱们假设在A1提出提案的同时, A5决定将税率定为20%:
现有的税率是什么?若是没有决定,则建议将其定为20%.时间:本届议会第3年3月15日;提案者:A5
草案要经过侍从送到其它议员的案头. A1的草案将由4位侍从送到A2-A5那里。如今,负责A2和A3的侍从将草案顺利送达,负责A4和A5的侍从则不上班. A5的草案则顺利的送至A3和A4手中。
如今, A1, A2, A3收到了A1的提案; A3, A4, A5收到了A5的提案。按照协议, A1, A2, A4, A5将接受他们收到的提案,侍从将拿着
我已收到你的提案,等待最终批准
的回复回到提案者那里。
而A3的行为将决定批准哪个。
假设A1的提案先送到A3处,而A5的侍从决定放假一段时间。因而A3接受并派出了侍从. A1等到了两位侍从,加上它本身已经构成一个多数派,因而税率10%将成为决议. A1派出侍从将决议送到全部议员处:
税率已定为10%,新的提案不得再讨论本问题。
A3在好久之后收到了来自A5的提案。因为税率问题已经讨论完毕,他决定再也不理会。可是他要抱怨一句:
税率已在以前的投票中定为10%,你不要再来烦我!
这个回复对A5可能有帮助,由于A5可能由于某种缘由好久没法与与外界联系了。固然更可能对A5没有任何做用,由于A5可能已经从A1处得到了刚才的决议。
依然假设A1的提案先送到A3处,可是此次A5的侍从不是放假了,只是中途耽搁了一会。此次, A3依然会将"接受"回复给A1.可是在决议成型以前它又收到了A5的提案。这时协议有两种处理方式:
1.若是A5的提案更早,按照传统应该由较早的提案者主持投票。如今看来两份提案的时间同样(本届议会第3年3月15日)。可是A5是个惹不起的大人物。因而A3回复:
我已收到您的提案,等待最终批准,可是您以前有人提出将税率定为10%,请明察。
因而, A1和A5都收到了足够的回复。这时关于税率问题就有两个提案在同时进行。可是A5知道以前有人提出税率为10%.因而A1和A5都会向全体议员广播:
税率已定为10%,新的提案不得再讨论本问题。
一致性获得了保证。
2. A5是个无足轻重的小人物。这时A3再也不理会他, A1不久后就会广播税率定为10%.
在这个状况中,咱们将看见,根据提案的时间及提案者的权势决定是否应答是有意义的。在这里,时间和提案者的权势就构成了给提案编号的依据。这样的编号符合"任何两个提案之间构成偏序"的要求。
A1和A5一样提出上述提案,这时A1能够正常联系A2和A3; A5也能够正常联系这两我的。此次A2先收到A1的提案; A3则先收到A5的提案. A5更有权势。
在这种状况下,已经回答A1的A2发现有比A1更有权势的A5提出了税率20%的新提案,因而回复A5说:
我已收到您的提案,等待最终批准。
而回复了A5的A3发现新的提案者A1是个小人物,不予理会。
A1没有达到多数,A5达到了,因而A5将主持投票,决议的内容是A5提出的税率20%.
若是A3决定平等地对待每一位议员,对A1作出"你以前有人提出将税率定为20%"的回复,则将形成混乱。这种状况下A1和A5都将试图主持投票,可是此次两份提案的内容不一样。
这种状况下, A3若对A1进行回复,只能说:
有更大的人物关注此事,请等待他作出决定。
另外,在这种状况下, A4与外界失去了联系。等到他恢复联系,并须要得知税率状况时,他(在最简单的协议中)将提出一个提案:
现有的税率是什么?若是没有决定,则建议将其定为15%.时间:本届议会第3年4月1日;提案者:A4
这时,(在最简单的协议中)其余议员将会回复:
税率已在以前的投票中定为20%,你不要再来烦我!
为了让这套系统能正确运行,咱们须要一个精确的时钟。因为操做系统的物理时钟常常是有误差的,因此咱们决定采用一个逻辑时钟。时钟的目的是给系统中 发生的每个事件编排一个序号。假设咱们有一台单独的机器提供了一个全局的计数器服务。它只支持一个方法:incrementAndGet()。这个方法 的做用是将计数器的值加一,而且返回增长后的值。咱们将这个计数器称为globalClock。globalClock的初始值为0。而后,系统中的每一个其它机器,都有一个本身的localClock,它的初始值来自globalClock。
int globalClock::incrementAndGet(){ return (MAX_SERVER_ID+1)*++localCounter+serverID; }
假设机器和机器之间经过request-response这样的模式通信。每条网络消息的类型要么是reqeust,要么是response。response又分为两种:OK和Rejected。不管什么类型的网络消息,都必须带有一个时间戳,时间戳取自这台机器的localClock。咱们规定消息的接收方必须执行如下行为:
if(message.timestamp&localClock && message.type==request) reject(message); else { localClock=message.timestamp; process(message); }
简而言之就是:咱们不处理过期的请求。而且利用网络间传输的消息做为时钟同步机制。一旦收到过期的请求,就返回Rejected类型的答复。另外,咱们要求时钟不能倒流。咱们必须容忍机器忽然crash。在机器从新起来以后,要么localClock从globalClock取一个新值,要么localClock每次更新的时候都必须写入到本地硬盘上,重启以后从那再读进来。咱们偏向于第二种方案,由于我后面将会讲怎么去掉 globalClock这个服务器。
Paxos协议分为两个阶段。
设置时钟:proposer令localClock=globalClock.incrementAndGet()。
prepare:proposer向全部Acceptor发送一个prepare消息。接收方应返回它最近一次accept的value,以及 accept的时间,若在它尚未accept过value,那么就返回空。proposer只有在收到过半数的response以后,才可进入下一个阶段。一旦收到reject消息,那么就重头来。
构造Proposal:proposer从prepare阶段收到的全部values中选取时间戳最新的一个。若是没有,那么它本身提议一个value。
发送Proposal:proposer把value发送给其它全部机器,消息的时间戳取自localClock。接收方只要检查消息时间戳合法,那么就接受此value,把这个value和时间戳写入到硬盘上,而后答复OK,不然拒绝接受。proposer若收到任何的reject答复,则回到 step1。不然,在收到过半数的OK后,此Proposal被经过。
这里具体例子来讲明Paxos的整个具体流程: 假若有Server一、Server二、Server3这样三台服务器,咱们要从中选出leader,这时候Paxos派上用场了。整个选举的结构图以下:
每一个Server都向Proposer发消息称本身要成为leader,Server1往Proposer1发、Server2往Proposer2发、Server3往Proposer3发;
如今每一个Proposer都接收到了Server1发来的消息但时间不同,Proposer2先接收到了,而后是Proposer1,接着才是Proposer3;
Proposer2首先接收到消息因此他从系统中取得一个编号1,Proposer2向Acceptor2和Acceptor3发送一条,编号为1的消
息;接着Proposer1也接收到了Server1发来的消息,取得一个编号2,Proposer1向Acceptor1和Acceptor2发送一条,编号为2的消息; 最后Proposer3也接收到了Server3发来的消息,取得一个编号3,Proposer3向Acceptor2和Acceptor3发送一条,编
号为3的消息;
这时Proposer1发送的消息先到达Acceptor1和Acceptor2,这两个都没有接收过请求因此接受了请求返回[2,null]给Proposer1,并承诺不接受编号小于2的请求;
此时Proposer2发送的消息到达Acceptor2和Acceptor3,Acceprot3没有接收过请求返回[1,null]给Proposer2,并承诺不接受编号小于1的请求,但这时Acceptor2已经接受过Proposer1的请求并承诺不接受编号小于的2的请求了,因此Acceptor2拒绝Proposer2的请求;
最后Proposer3发送的消息到达Acceptor2和Acceptor3,Acceptor2接受过提议,但此时编号为3大于Acceptor2的承诺2与Accetpor3的承诺1,因此接受提议返回[3,null];
Proposer2没收到过半的回复因此从新取得编号4,并发送给Acceptor2和Acceptor3,而后Acceptor2和Acceptor3
Proposer3收到过半(三个Server中两个)的返回,而且返回的Value为null,因此Proposer3提交了[3,server3]的议案;
Proposer1收到过半返回,返回的Value为null,因此Proposer1提交了[2,server1]的议案;
Proposer2收到过半返回,返回的Value为null,因此Proposer2提交了[4,server2]的议案;
Acceptor一、Acceptor2接收到Proposer1的提案[2,server1]请求,Acceptor2承诺编号大于4因此拒绝了经过,Acceptor1经过了请求;
Proposer2的提案[4,server2]发送到了Acceptor二、Acceptor3,提案编号为4因此Acceptor二、Acceptor3都经过了提案请求;
Acceptor二、Acceptor3接收到Proposer3的提案[3,server3]请求,Acceptor二、Acceptor3承诺编号大于4因此拒绝了提案;
此时过半的Acceptor都接受了Proposer2的提案[4,server2],Larner感知到了提案的经过,Larner学习提案,server2成为Leader;
一个Paxos过程只会产生一个议案因此至此这个流程结束,选举结果server2为Leader;