Paxos是用于一种分布式系统而且具备容错性的一致性算法,是目前业界公认能解决分布式系统一致性的问题算法之一。 Paxos于1990年由Lamport提出,但直到1998才正式被计算机科学界接受。由于Lamport刚开始采用描述故事的方式来描述算法,但不被相关的出版社接受。后来Lamport发表《Paxos Make Simple》论文使得Paxos加速被广大计算机工程师理解并接受。但一直以来计算机的工程师抱怨Paxos算法是难以理解、晦涩,本人在学习之初也有深有体会,因此写下这篇文章总结我的对Paxos算法理解,也供你们参考。算法
Paxos算法的核心问题是:解决分布式系统的一致性的问题,全部问题均围绕着在分布式环境达成一致性而展开讨论的。Paxos算法为了达成一致性,算法就必须保证其安全性和活性。安全
安全性:只有被提出的提案才能被选定,而且只有一个提案被选定。性能优化
活性:最终保证会有一个提案被选定。异步
安全性和活性的组合结果就是:最终有且只有一个被提出的提案被选定。分布式
在推导算法过程当中,咱们定义了一些角色及名词。性能
proposer:提案者,它能够提出一个提案。学习
acceptor:提案的受理者,有权决定是否它自己是否批准该提案。优化
choose:提案被选定,当有半数以上Acceptor批准该提案时,就认为该提案被选定了。spa
learner:不参与Paxos提案选定的过程,只在提案被选定时,知道提案结果的角色。blog
proposal:提案,由Proposer 提出。一个提案由一个编号及value造成的对组成,如[m, value],提案的编号必须是全局惟一,value即表明了提案自己的内容。
在具体的执行过程当中,同一个进程可能不止充当一种角色,同一个进程可能在三个角色中互换。如下是关于这三个角色的通讯做如下约定:
系统全部消息均存在延迟、丢失、重复的可能,系统也能够随时会重启。
系统全部的消息不存篡改的问题,也即不存在拜占庭的问题。
P1:一个acceptor必须批准(accept)它收到的第一个提案。
这个需求引出一个问题,就是这个若是有多个提案被不一样的proposers同时提出,这可能会致使虽然每一个acceptor都批准了一个提案,可是没有一个提案是由多数人都批准的,也就是没有提案能够选定的。
上图都知足p1需求,但没法选定一个提案。 p1再加上一个提案被选定须要由半数以上的acceptor批准的需求暗示着一个acceptor必须可以批准(accept)不止一个提案。咱们为每一个提案设定一个编号来记录一个acceptor批准的那些提案。为了不混淆,须要保证不一样的提案具备不一样的编号。当一个具备某value值的提案被半数以上的acceptor批准后,咱们就认为该value被选定了。此时咱们也认为该提案被选定了,提案是由[编号,value]组成。
P2.若是提案的值为[m,v]被已选定,那全部编号大于m的提案的值必须是v。
由于编号是全序的,条件P2就保证了只有一个Value值被选定的这一关键安全性属性。被选定的提案,首先必须被至少一个Acceptor批准,所以咱们能够批准知足以下条件来保证P2。
P2a. 若是具备value值v的提案被选定了,那么全部比它编号更高的被acceptor批准的提案的value值也必须是v。
咱们仍然须要p1来保证提案会被选定。可是由于系统通讯是异步的或者延迟,一个提案可能会在某个acceptor还未收到任何提案时就被选定了。假设一个新的proposer 启动了,而后产生了一个具备另外一个value值的更高编号的提案,根据p1,就须要c批准这个提案,可是这与P2a矛盾。所以若是要同时知足p1和P2a,须要对P2a进行强化:
P2b.若是具备value值v的提案被选定,那么全部比它编号更高的被proposer 提出的提案的value值也必须是v。
一个提案被acceptor批准以前确定要由某个proposer 提出,所以P2b就隐含了P2a,进而隐含了P2。因此,只要论证P2b成立就能够了。
假设[M0,V0]值V0的提案被选定了,须要证实任何具备编号Mn(Mn>M0)的提案的Value值为V0。
为了发现如何保证P2b,咱们来如何证实它成立。咱们假设某个提案[M0,V0]提案被选定了,须要证实任何具备编号Mn(Mn>M0)的提案值都为V0。也就是咱们须要批准数学概括法证实:假设M0至Mn-1提案的Value值都是V0,证实Mn提案的Value值也是V0。由于编号为M0的提案已经被选定了,这意味着确定存在一个由半数以上的acceptor组成的集合C,C中的每一个acceptor都批准了这个提案。再结合概括假设,M0被选定意味着:
C中的每一个acceptor都批准了一个编号在M0至Mn-1之间的提案,而且每一个编号在M0至Mn-1之间的被acceptor批准的提案都具备value值V0。
任何包含半数以上的acceptor的集合S都至少包含C中的一个成员,咱们能够批准维护以下不变性就能够保证编号为Mn的提案value 值为V0:
P2c.对于任意的Mn和Mv,若是编号为Mn和value值为Mn的提案被提出,那么确定存在一个由半数以上的acceptor组成的集合S,知足如下二个条件的任意一个:
只要一直保证P2c的正确性,就能够知足P2b了。从P2开始至P2C,是对提案一系列条件的逐步增强的过程。如今咱们只须要证实P2c的正确性就能够了。
咱们再看P2c,实际上P2c规定了每一个proposer 如何产生一个提案,对于产生的每一个提案[Mn,Mv]须要知足这个条件“存在一个由超过半数的acceptor 组成的集合S:要么S中没有人批准(accept)过编号小于 n 的任何提案,要么S的任何acceptor批准的全部提案(编号小于Mn)中,Mv是编号最大的提案的决议”。当proposer 遵照这个规则产生提案时,就能够保证知足P2b。下面咱们反过来看,证实P2c能够保证P2b,采用数学概括法证实,便是第二数学概括法。
首先假设提案[M0,V0]被选定了,设比该提案编号大的提案为[Mn,Vn],咱们须要证实的就是在P2c的前提下,对于全部的提案[Mn,Vn],有V0=Vn。
第一步:当Mn=M0+1时,若是有这样编号的提案,首先咱们知道[M0,V0]被选定了,这样就不可能存在一个S且S中acceptor批准太小于Mn的提案[S与批准[M0,V0]的acceptor集合确定有交集],那Mn只能是多数集S中编号小于Mn的最大编号的那个提案的值了,此时Mn=M0+1,理论上小于Mn的最大的编号确定是M0,同时因为S和批准[M0,V0]的acceptor集合都是多数集,就保证了两者确定有交集,这样proposer 在肯定Mn取值时,确定选到就是V0。
上面实际上就是数学概括法的第一步,确切的说是使用的是第二数学概括法证实了第一步。上面是第一步,验证了某个初始值成立。下面,须要假设编号在[M0+1,Mn-1]区间内成立,并在此基础上推出Mn上也成立。
第二步:根据假设编号在[M0+1,Mn-1]区间内的全部提案value都具为V0,须要证实的是编号为Mn的提案值为V0。根据P2c,首先一样的不可能存在一个S且S中没有人批准太小于Mn的提案,那么编号为Mn的Value值,只能是一个多数集S中编号小于Mn的最大编号的那个提案的值,若是这个最大编号落在[M0+1,Mn-1]区间内的,那么值确定是V0,若是不是落在[M0+1,Mn-1]区间,那么它的编号确定就是M0了,不可能比M0再小了,由于S也确定会与批准[M0,V0]的acceptor集合确定有交集,那么它的编号值就不会比M0小,而编号若是是M0那么它的值也是V0。由此得证。
以上这个证实过程使用第二数学概括法,不少人理解不了Paxos的原理很大部分缘由,就是不理解这个证实过程。其实,只要紧扣第P2C中的两个约束条件,多读几遍也就理解了。
为了维护P2c的不变性,一个proposer 在产生编号为Mn的提案时,必需要知道某一个将要或已经被半数以上acceptor批准的编号小于Mn的最大编号的提案。获取那些已经被批准的提案很简单,只要经过问询就能够实现了。proposer 会请求acceptors不要再批准任何编号小于Mn的提案。这就致使了以下的提案生成算法:
以上的流程是Paxos的proposer 的提出提案的处理逻辑,也是Paxos难理解、晦涩的方面,也是算法的重点和难点。我我的的学习经历的是P2b和P2c的证实和衔接比较难理解,特别是P2c的数学概括证实那块逻辑。
acceptor批准提案
从上面的内容能够看到,一个accpetor可能接到二种请求,分别是:prepare和accept请求。咱们已经描述了proposer 的算法。那么acceptor端呢?它能够接收两种来自proposer 的请求: prepare请求和accept请求。acceptor能够忽略任何请求而不影响算法的安全性。任什么时候候acceptor能够回应accept请求,并批准提案,当且仅当它没有承诺或批准一个更高编号的提案。能够推导出增强条件P1a:
P1A. Acceptor能够批准一个编号为Mn的提案,当且仅当它没有回应过一个编号大于Mn的Prepare请求。
P1a包含了P1,是P1增强条件。固然,acceptor能够忽略任何prepare和accept请求,也能够延迟和丢失响应,这些均不会影响Paxos的算法的安全性。
如今咱们获得了一个完整的提案选择算法,并知足咱们要求的安全属性是基于提案编号上全局惟一的。如今咱们作一些简单优化就能获得最终算法:
假设一个acceptor接收到一个编号为Mn的prepare请求,可是它已经回应了一个编号大于Mn的prepare请求。因而acceptor就没有必要回应这个prepare请求了,由于它不会批准这个编号为Mn的提案。它还能够忽略已经批准过的提案的prepare请求。
有了这些优化,acceptor只须要保存它已经批准的最高编号的提案(包括编号和决议),以及它已经回应的全部prepare请求的最高编号。由于任何状况下,都须要保证P2C,acceptor必须存储这些信息,包括失效并重启以后。
结合proposer 和acceptor的行为,咱们将把算法能够分为两个阶段来执行。
阶段1.
阶段2.
proposer 能够提出多个提案,只要它遵循上面的算法。它能够在任什么时候刻放弃一个提案。(这不会破坏正确性,即便在提案被放弃后,提案的请求或者回应消息才到达目标)若是其它的proposer 已经开始提出更高编号的提案,那么最好能放弃当前的提案。所以,若是acceptor忽略一个prepare或者accept请求(由于已经收到了更高编号的prepare请求),它应该告知proposer 放弃提案。这是一个性能优化,而不影响正确性。
咱们很容易构建这样一个场景,两个proposer 持续发送比对方的更高提案编号,而且最终它们二者没有一个被选中。例如:proposer -pn提出 pn1完成了phase 1。另外一个proposer -qn接着批准了qn2 > pn1完成了phase 1。proposer -pn在phase 2的以pn1标记的accept request会被全部acceptor拒绝,由于acceptor已经承诺不接受任何number小于qn2的提案。所以proposer-qn开始用新的pn3 > pn2来开始并完成phase 1,而这又致使了Proposer qn2在phase 2的accept被忽略。如此反复进行。
为了保证流程的执行,咱们必须选出一个主proposer,做为提案的惟一人选。若是主proposer能和大数派的集合进行通讯,而且使用了一个比全部已经批准的提案号都大的编号,那么它就能成功产生被接受的proposal。批准拒绝已有的提案而且批准更高的提案号,主proposer最终会选择到一个足够大的提案号,最终使用提案被选定,从而达到数据一致性的目的。