Paxos一致性算法——分布式系统中的经典算法,论文自己也有一段有趣的故事。一致性问题是分布式系统的根本问题之一,在论文中,做者一步步的增强最初一致性问题(2.1节提出的问题)的约束条件,最终导出了一个可实现的一致性模型。当前Paxos算法的研究愈来愈多,相关实现也很多,而原论文依然是最不可少的资料。论文通篇没有一个数学公式,这是大牛的坚持!
【】中的是我我的的注释。
先解释文中几个关键词的翻译:
Proposal译为“议案”,由proposer提出,被aceeptor批准或否决
Value译为“决议”,它是议案的内容,一个议案就是一个{编号,决议}对
Accept译为“批准”,表示议案被acceptor批准
Choose译为“选择”,表示议案“被选择”,也就是被多数acceptor批准算法
Leslie Lamport
2001.11.1安全
Paxos算法,纯文本方式描述,很是简单。【论文初版没多少人理会,郁闷了:)】性能优化
为实现具备容错能力的分布式系统而提出的Paxos算法,曾被认为难以理解,可能由于对于大部分读者而言,原来的描述是基于希腊故事的[5]。【为了描述Paxos算法,Lamport设计了一个虚拟的希腊城邦Paxos】实际上,它是最简单和直观的分布式算法之一【这个…,其实没那么简单吧】。它的核心是一个一致性算法——[5]中提出的“synod”算法。下一节描述这个一致性算法,并听从咱们要求的性质。最后一节解释了完整的Paxos算法,从一致性的直观应用到构建分布式系统的有限状态机模型——应该是总所周知的模型,由于它是论文[5]的主题——它多是分布式系统理论被引用最多的论文。服务器
设想一组能够提出决议(value)的process。一致性算法保证全部提出的决议中,有一个决议会被选择(chosen)。若是没有提出决议,那么将不会有选择。若是一个决议被选择,那么process最终都能知道这个被选择的决议。一致性的安全性包括:
——决议只有被提出后才可能被选择,
——只有一个决议被选择,而且
——process永远不会获知一个决议被选择了,除非这个决议确实已经被选择。
咱们将不会特别明确精确的时间性要求。然而,其目标是最终有一个提出的决议被选择,而且process最终会获知被选择的决议,若是有的话。
咱们为一致性算法划分3个角色,并分别以代理:proposer(提出者),acceptor(批准者)和listener(接收者)表示。实现中,容许一个process扮演多个代理,这里咱们不关心从代理到process的映射。
假设代理之间用消息通讯。咱们采用异步、非拜占庭模型【拜占庭模型(Byzantine model),消息可能丢失、重复或者内容损坏。换而言之,非拜占庭模型就是容许消息的丢失或者重复,可是不会出现内容损坏的状况】:
——代理以任意的速度操做,可能会由于停机而失效,可能会重启。由于任一个代理均可能会在决议被选择后停机再重启,所以解决方案要求代理必须可以记忆某些信息,从而能在重启后从新载入。
——消息传送速度不可预测,可能会重复或丢失,可是内容不会损坏。网络
最简单的方法就是用单个acceptor代理。Proposer发送议案(proposal)给这个acceptor,它选择最早收到的议案。尽管简单,可是若是acceptor停机了,那么系统就不能继续运行了,这个方案并不能知足要求。【明显的单点问题】
看来咱们须要选择另外的方法,咱们用多个acceptor代理,而非一个,proposer向一组acceptor提出议案。一个acceptor可能批准该议案,当有足够大的acceptor集合批准了这个议案时,决议【议案是一个{编号,决议}对】就被选择了。那么这个集合多大才足够呢?为了保证只有一个决议被选择,咱们可让这个集合包含多数的代理【后面也会称之为多数派】。由于任意两个多数派至少有一个相同的代理,若是一个acceptor最多只能批准一个决议,这就是可行的。
假设没有失败或者消息丢失,即便仅有一个proposer提出了一个决议,咱们也但愿能选择一个决议。这就导出了下面的需求:
P1. Acceptor必须批准它接收到的第一个决议。
可是该需求会致使一个问题。同时可能有几个proposer提出了几个不一样的决议,从而致使每一个acceptor都批准了一个决议,可是没有一个决议被多数派批准。即便只有两个决议,若是每一个都被半数的acceptor批准,单个的acceptor失效也会致使不可能知道到底哪一个决议被选择了。
一个决议要通过多数派的批准才能被选择,这个需求和P1暗示了acceptor必须可以批准多个议案。咱们为每一个议案分配一个编号来记录不一样的议案,所以一个议案由编号和决议构成【也就是议案={编号,决议}】。为避免混淆,咱们要求议案的编号是惟一的。这个取决于实现,咱们假设能够作到这一点。若是一个议案{n, v}经过多数派的批准,那么决议v就被选择了。这种状况下,咱们称议案(包括其决议v)被选择了。
咱们容许选择多个议案,可是必须保证全部选择的议案包括相同的决议。对议案编号概括,能够保证:
P2. 若是一个议案{n, v}被选择,那么全部被选择的议案(编号更高)包含的决议都是v。
由于编号是全序的,P2保证了“只有一个决议被选择”这一关键安全属性。议案必须至少被一个acceptor批准才可能被选择。所以只要知足下面的条件,就能够知足P2:
P2A. 若是一个议案{n, v}被选择,那么任何acceptor批准的议案(编号更高)包含的决议都是v。
咱们依然保证P1来确认选择了某些议案。由于通讯是异步的,在特殊状况下,某些acceptor c没有接收到过任何议案,它们可能会【错误的】批准一个议案。设想一个新的proposer“醒来”并提出了一个更高编号的议案(包含不一样的决议)。根据P1的要求,c应该批准这个议案,可是这违反了P2A。为了同时保证P1和P2A,咱们须要加强P2A:
P2B. 若是一个议案{n, v}被选择,那么此后,任何proposer提出的议案(编号更高)包含的决议都是v。
由于一个议案必须在被proposer提出后才能被acceptor批准,所以P2B包含了P2A,进而包含了P2。
如何才能知足P2B呢,让咱们来考虑如何证实它是成立的。咱们假设某个议案{m, v}被选择,而后证实任何编号n>m的议案的决议都是v。对n概括能够简化证实,根据条件:每一个提出的议案(编号从m到n-1)的决议都是v,咱们能够证实编号为n的议案的决议是v。对于选择的议案(编号为m),一定存在一个集合C(acceptor的多数派),C中的每一个acceptor都批准了该议案。结合概括假设,m被选择这一前提意味着:
C中的每一个acceptor都批准了一个编号在m到n-1范围内的议案,而且议案的决议为v。
由于任何由多数派组成的集合S都至少包含C中的一个成员,咱们能够得出结论:若是下面的不变性成立,那么编号为n的议案的决议就是v:
P2C. 对于任意的v和n,若是议案{n, v}被提出,那么存在一个由acceptor的多数派组成的集合S,或者a) S中没有acceptor批准过编号小于n的议案,或者b) 在S的任何acceptor批准的全部议案(编号小于n)中,v是编号最大的议案的决议。
经过保持P2C,咱们就能知足P2B。
为了保持不变性P2C,准备提出议案(编号为n)的proposer必须知道全部编号小于n的议案中编号最大的那个,若是存在的话,它已经或将要被acceptor的某个多数派批准。获取已经批准的议案是简单的,可是预知未来可能批准的议案是困难的。Proposer并不作预测,而是假定不会有这样的状况。也就是说,proposer要求acceptor不能批准任何编号小于n的议案。这引出了下面提出议案的算法【这就是两阶段提交了】。
1 proposer选择一个新编号n,向某个acceptor集合中的全部成员发送请求,【prepare请求阶段,n是prepare请求的编号,也是下面accept请求的议案编号】并要求回应:
a) 一个永不批准编号小于n的议案的承诺,以及
b) 在它已经批准的全部编号小于n的议案中,编号最大的议案,若是存在的话。
我把这样的请求称为prepare请求n。
2 若是proposer收到了多数acceptor的回应,那么它就能够提出议案{n, v},其中v是全部回应中编号最高的议案的决议,或者是proposer选择的任意值,若是acceptor们回应说尚未批准过议案。
一个proposer向一个acceptor集合发送已经被批准的议案(不必定是回应proposer初始请求的acceptor集合),咱们称之为accept请求。
咱们已经描述了proposer的算法。那么acceptor呢?它能够接收两种来自proposer的请求: prepare请求和accept请求。Acceptor能够忽略任何请求,而不用担忧安全性。所以,咱们只须要描述它须要回应请求的状况。任什么时候候它均可以回应prepare请求【本文中,回应就意味着接受了这个prepare请求和编号n】,它能够回应accept请求,并批准议案,当且仅当它没有承诺过【承诺批准或批准一个更高编号的议案】。换句话讲:
P1A. acceptor能够批准一个编号为n的议案,当且仅当它没有回应过一个编号大于n的prepare请求。
P1A蕴含了P1。
如今咱们获得了一个完整的决议选择算法,并知足咱们要求的安全属性——假设议案编号惟一。经过一些简单优化就能获得最终算法。
假设一个acceptor接收到一个编号为n的prepare请求,可是它已经回应了一个编号大于n的prepare请求。因而acceptor就没有必要回应这个prepare请求了,由于它不会批准这个编号为n的议案。它还能够忽略已经批准过的议案的prepare请求。
有了这些优化,acceptor只须要保存它已经批准的最高编号的议案(包括编号和决议),以及它已经回应的全部prepare请求的最高编号。由于任何状况下,都须要保证P2C,acceptor必须记住这些信息,包括失效并重启以后。注意,proposer能够随意的抛弃一个议案——只要它永远不会使用相同的编号来提出另外一个议案。
结合proposer和acceptor的行为,咱们将把算法能够分为两个阶段来执行。
阶段1.
a) Proposer选择一个议案编号n,向acceptor的多数派发送编号也为n的prepare请求。
b) Acceptor:若是接收到的prepare请求的编号n大于它已经回应的任何prepare请求,它就回应已经批准的编号最高的议案(若是有的话),并承诺再也不回应任何编号小于n的议案;
阶段2.
a) Proposer:若是收到了多数acceptor对prepare请求(编号为n)的回应,它就向这些acceptor发送议案{n, v}的accept请求,其中v是全部回应中编号最高的议案的决议,或者是proposer选择的值,若是回应说尚未议案。
b) Acceptor:若是收到了议案{n, v}的accept请求,它就批准该议案,除非它已经回应了一个编号大于n的议案。
Proposer能够提出多个议案,只要它遵循上面的算法。它能够在任什么时候刻放弃一个议案。(这不会破坏正确性,即便在议案被放弃后,议案的请求或者回应消息才到达目标)若是其它的proposer已经开始提出更高编号的议案,那么最好能放弃当前的议案。所以,若是acceptor忽略一个prepare或者accept请求(由于已经收到了更高编号的prepare请求),它应该告知proposer放弃议案。这是一个性能优化,而不影响正确性。
2.3 获知选择的决议
Learner必须找到一个被多数acceptor批准的议案,才能知道一个决议被选择了。一个显而易见的算法就是,让每一个acceptor在批准议案时通知全部的learner。因而learner能够尽快知道选择的决议,可是要求每一个acceptor通知每一个learner——须要的消息个数等于learner数和acceptor数的乘积。
基于非拜占庭假设,一个learner能够从另外一个learner得知被选择的决议。咱们可让acceptor将批准状况回应给一个主Learner,它再把被选择的决议通知给其它的learner。这增长了一次额外的消息传递,也不可靠,由于主learner可能会失效,可是要求的消息个数仅是learner数和acceptor数的总和。
更通常的,能够有多个主Learner,每一个都能通知其它全部的acceptor。主learner越多越可靠,可是通讯代价会增长【消息个数越多】。
因为消息丢失,可能没有learner知道选择了一个决议。Learner能够向acceptor询问批准的议案,可是因为acceptor的失效,可能难以得知多数派是否批准了一个议案。这样,learner只能在新的议案被选择时才能知道acceptor选择的决议。若是learner须要知道是否已经选择了一个决议,它可让proposer根据上面的算法提出一个议案【提出请求就有回应,而且新的提案的决议就是当前选择的决议】。数据结构
很容易构造这样一个场景,两个proposer轮流提出一系列编号递增的议案,可是都没有被选择。Propoer p选择议案的编号为n1,并结束阶段1。接着,另一个proposer q选择了议案编号n2>n1,并结束阶段1。因而p在阶段2的accept请求将被忽略,由于acceptor已经承诺再也不批准编号小于n2的议案。因而p再回到阶段1并选择了编号n3 > n2,这又致使q第二阶段的accept请求被忽略,…
为了保证流程,必须选择一个主proposer,只有主proposer才能提出议案。若是主proposer和多数acceptor成功通讯,并提出一个编号更高的议案,议案将被批准。若是它得知已经有编号更高的议案,它将放弃当前的议案,并最终能选择一个足够大的编号。
若是系统中有足够的组件(proposer,acceptor和网络)能正常工做,经过选择一个主proposer,系统就能保持响应。Fischer、Lynch和Patterson的著名结论[1]代表:选择proposer的可靠算法必须是随机的或者实时的——例如,使用超时机制。然而无论选择成功与否,安全性都能获得保证。异步
Paxos算法[5]假设了一组网络进程。在其一致性算法中,每一个process都同时扮演proposer、acceptor和learner的角色。算法选择一个leader,它就是主proposer和主learner。Paxos一致性算法就是上面描述的那个,请求和响应都用消息发送(响应会被打上对应议案的编号,以防止混淆)。使用持久化存储来保证acceptor失效后也能记起必要的信息。Acceptor在发送响应前必须持久化存储该响应。
接下来就是描述保证任何两个议案的编号都不相同的机制。proposer从互不相交的集合中选择议案编号,所以两个不一样的proposer永远不会提出相同编号的议案。【假设有5个proposer,编号为0~4,可使proposer i的议案编号选择序列为:5*j + i(j >= 0),就能保证永不重复,且递增】每一个proposer都持久化保存它已经提出的编号最高的议案,并使用一个更高的议案编号来开始阶段1。分布式
实现分布式系统的简单方式就是使用一些客户端向中心服务器发送命令【就是C/S模式了】。服务器能够看做是根据必定顺序执行客户端命令的肯定状态自动机。状态机包含当前状态,每读入一个命令并产生相应的结果,它就执行一步。好比,分布式银行系统的客户端多是出纳员,状态机的状态由全部用户的帐户余额构成。取款操做将会执行一条状态机命令:减小帐户余额,输出新旧余额数(当且仅当余额大于取款值)。
使用单个中心服务器的实现是不可靠的,所以咱们使用一组服务器,每个都独立的执行状态机。由于状态机是肯定的,若是执行相同的命令序列,全部的服务器将会产生一样的状态序列和输出【非拜占庭模型假设再一次起做用了】。客户端发起命令后,可使用任何服务器的输出。
为了保证全部的服务器执行的是相同的命令序列,咱们执行一个paxos一致性算法的实例(instance)序列【注解#】,每一个实例是一个独立运行的paxos一致性算法,第i个实例选择的决议就是序列的第i个状态机命令。在算法的每一个实例中,每一个server都扮演全部的角色(proposer、acceptor和learner)。如今,假设服务器组固定,全部的实例都使用相同的代理。
【注解#】
某些资料会把“实例”称为“轮”(round),每轮选择一个决议,但每轮可能会执行屡次一致性算法,好比若是主proposer在阶段1提出的prepare请求被否决了,那么它将会选择新的议案编号,从新提出议案请求,直到议案被多数acceptor批准(消息发送失败也会致使重传请求)。引入轮(就是实例啦)这一律念后,能够作到各轮并行运行,同时批准多个决议,互不干涉,更有效率。
【end #】
正常操做下,一个服务器被选择为leader,它就是全部的实例的主proposer(惟一可以提出议案的)。客户端向leader发送命令,leader决定命令的顺序。若是leader决定某个客户端命令应该是第15个命令,它将试图把该命令选择为第135个实例的决议,一般都能成功。有可能由于失效而失败,或者另外的一个服务器相信它是leader,有另外的选择。可是一致性算法保证最多只能有一个命令被选成第135个。
Paxos一致性算法有效性的关键在于,决议直到阶段2才会真正提出。回忆proposer算法的阶段1结束后,或者要提的决议已经决定了,或者proposer能够提出任何决议。我将描述正常操做下Paxos状态机的执行状况。接下来,再讨论可能的错误状况。我会考虑前一个leader失效、新的leader被选择后会发生什么状况(系统启动是个特例,这时尚未提出命令)。
【在下面的部分,由于客户端的命令就是议案的决议,所以决议就是命令,会混用,并不影响理解】
在一致性算法的全部实例中,新的leader也是learner,应该知道已经选择的命令。假设它知道命令1–13四、138和139——也就是算法实例1–13四、138和139选择的决议(后面将看到命令序列中的间隔是如何引发的)。而后leader为实例135-137和全部大于139的实例执行阶段1(后面会说明是怎么作到的)。假设这些执行的输出将决定实例135和140提出的决议,可是不会影响其它实例的决议。而后leader为实例135和140执行阶段2,并选择了第135和140个命令。
Leader和其它全部知道leader命令的服务器,如今能够执行命令1-135。可是不能执行命令138-140(虽然它们也知道),由于必须选择命令136和137。Leader也能够把客户端提出的后面两个命令选为命令136和137。咱们但愿尽快的消除间隔,因而为命令136和137提出一个特殊的“noop”命令(不改变状态)(为命令136和137执行阶段2)。一旦noop命令被选择,命令138-140就能执行了。
如今命令1-140已经被选择了。Leader还已经为全部大于140的实例完成了一致性算法的阶段1的操做【注解*】,并能够在阶段2中提出任何决议。它把客户端的下一条命令编号为141,并看成实例141的阶段2的决议。再把下一条编号为142,以此执行。
【注解*】
对于同一个leader而言,若是它在执行实例i中执行了阶段1,那么后续的执行实例就不须要再次执行阶段1了,而直接执行阶段2,缘由以下:
1. 由于它提出的议案编号老是递增的,acceptor一定接受阶段1的prepare请求;
2. 每一个实例都是独立运行的paxos算法,互不干扰,决议互相独立;
减小了一个阶段,效率也一定有所提升。
【end*】
在知道提出的命令141被选择以前,Leader能够提出命令142。它提出命令141的消息可能都丢失了,还可能在其余服务器知道leader提出命令141以前,命令142已经被选择了。 当leader在实例141的阶段2中没有收到指望的回应后,它将重传这些消息。若是一切顺利,它提出的命令将被选择。然而,也可能会失败,在命令序列中留下了一个间隔。通常来讲,假设leader能够预取r个命令——也就是说,在命令1到i被选择以后,它就能够同步提出命令i+1, …, i+r。那么最多能够产生r-1个间隔【容易理解,前面的r-1个命令都失败了】。
新选出的leader要为算法的无限个实例执行阶段1——在上面的场景中,是实例135-137,和139以后的全部实例【个人理解是,为了这些实例,它总共只须要成功的执行一次阶段1,理由见上面的注解*,固然屡次执行也不会出问题,就像下面所说的那样】。它能够向其余的服务器发送一条简单的合理短消息,并为全部实例使用相同的议案编号。在阶段1,仅当acceptor已经收到了一个来自其它proposer的阶段2的消息时,除了简单的OK,它的回应会包含额外的消息,(在上述场景中,仅实例135和140是这样的【像前面所描述的,实例135-137和139执行的输出将决定实例135和140提出的决议】)。所以,服务器(做为acceptor)能够为全部实例回应一条简单的短消息。屡次执行阶段1并不会出问题。
由于leader的失效和由此引起的选举应该是小几率事件,有效执行一条状态机命令的开销——达到对命令/决议的一致性——仅仅是一致性算法的阶段2的开销【又一次验证了阶段1并不须要屡次执行】。能够证实,在容许失效的状况下,在全部的同类(一致性)算法中, paxos一致性算法的阶段2具备最小可能的【时间】复杂度[2]。所以paxos算法在本质上就是优化的。
上面对系统正常操做的讨论假设一直存在单个leader,除去在当前leader失效和选举新leader之间的一小段时间。在异常环境中,leader选择可能失败。若是没有服务器被选为leader,那么将不能接受命令。若是多个服务器都认为本身是leader,在同一个算法实例中,它们将都能提出议案,这可能会致使全部的议案都不能被选择。然而,安全属性是知足的——两个不一样的服务器将永远不会在第i个状态机命令的选择上达成一致。选择单一的leader只是为了保证流程【避免冲突】。
若是服务器组能够变更,必须有方法能检测哪些服务器执行的是算法的哪些实例。最简单的作法就是让状态机本身检测。当前的服务器组能够做为状态的一部分,并能被原生的状态机命令修改。咱们可让leader预取r个命令,并将执行算法实例i+r的服务器组的状态设置成执行第i个状态机命令后的状态。因而你可使用简单的重配置算法来进一步加强算法的可靠性【若是半数服务器同时失效,重配置机制也一筹莫展,然而这种几率过低了】。
【下面wiki的两篇文章也是paxos很好的参考资料,一篇中文,一篇英文,然而内容并不重复】
http://en.wikipedia.org/wiki/Paxos_algorithm
http://www.wikilib.com/wiki/Paxos%E7%AE%97%E6%B3%95
【还有两个重要的问题就是如何选举leader,以及server间数据的同步,能够参看zookeeper的实现;这些内容整理好下次再单独发出来:)】oop
[1] Michael J. Fischer, Nancy Lynch, and Michael S. Paterson. Impossibility of distributed consensus with one faulty process. Journal of the ACM, 32(2):374–382, April 1985.
[2] Idit Keidar and Sergio Rajsbaum. On the cost of fault-tolerant consensus when there are no faults—a tutorial. TechnicalReport MIT-LCS-TR-821, Laboratory for Computer Science, Massachusetts Institute Technology, Cambridge, MA, 02139, May 2001. also published in SIGACT News 32(2) (June 2001).
[3] Leslie Lamport. The implementation of reliable distributed multiprocess systems. Computer Networks, 2:95–114, 1978.
[4] Leslie Lamport. Time, clocks, and the ordering of events in a distributed system. Communications of the ACM, 21(7):558–565, July 1978.
[5] Leslie Lamport. The part-time parliament. ACM Transactions on Computer Systems, 16(2):133–169, May 1998.
post