此片文章专注于理解basic paxos的本质,只涉及核心场景,有部分定义借鉴了其他文章,建议结合其他文章一起看可以获得更为全面的理解;
由于第一版排版与一些定义的模糊,修订之后发布第二版;
本质
BasicPaxos是一个通过二段式规则(perpare规则与accept规则)来保证一个值在一至少3个及以上节点去中心化集群上被法定人数(大多数)接受并且可由其他节点Listen(获取被通过的值)的CP(一致性、分区容错性)算法;
应用场景
在一个大多数节点都可用的分布式集群中维护一个可以保证大多数节点可以获取到的都是一致的值;
规则
此处是规则原文,为严谨理解,就不翻译了
Phase 1
(a) A proposer selects a proposal number n and sends a prepare request with number n to a majority of acceptors.
(b) If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, then it responds to the request with a promise not to accept any more proposals numbered less than n and with the highest-numbered pro-posal (if any) that it has accepted.
Phase 2
(a) If the proposer receives a response to its prepare requests (numbered n) from a majority of acceptors, then it sends an accept request to each of those acceptors for a proposal numbered n with a value v , where v is the value of the highest-numbered proposal among the responses, or is any value if the responses reported no proposals.
(b) If an acceptor receives an accept request for a proposal numbered n, it accepts the proposal unless it has already responded to a prepare request having a number greater than n.
名词定义
n (version):相互不冲突的提案编号,用于区分不同轮次的提案。
Value:提案的值,即最后试图达成共识的候选结果。
Proposer:提案发起者,用于提出议案,提案的内容为:令 x=value,对同一轮提案,最多提议一个value 。Proposer角度看,提案分为两个阶段:Prepare阶段、Propose阶段。一轮提案的value不一定非要是Proposer自己提议的value。
Acceptor:提案投票者,有 N 个。Proposer 提出的 x=value 提案必须获得超过半数(N/2+1)的 Acceptor接受后才能被chosen。Acceptor 之间完全对等,在独立的时间轴执行提案投票。从Acceptor角度看,投票分两阶段进行:Promise阶段、Accept阶段。
Listener: 提案接受者,listener本身代表了信息单向传递的目的,接收在被大多数投票者接受(Accepted)的提案
值得注意的是,在一个集群中,不会严格区分Proposer、Acceptor、Listener,即任何一个节点在不同的场景下,既有可能是提案发起者也有可能是提案投票者也有可能是提案接受者。
结合场景解析规则
解析会先描述规则然后引出场景,总共涉及5个场景,顺序为1 -> 2 -> 3 -> 4 -> 5;
Prepare
规则:
1)获取一个proposal number, n;
2)提议者向所有节点广播prepare(n)请求;
3)接收者(Acceptors比较善变,如果还没最终认可一个值,它就会不断认同提案号最大的那个方案)比较n和minProposal,如果n>minProposal,表示有更新的提议minProposal=n;如果此时该接受者并没有认可一个最终值,那么认可这个提案,返回OK。如果此时已经有一个accptedValue, 将返回(acceptedProposal,acceptedValue);
4)提议者接收到过半数请求后,如果发现有acceptedValue返回,表示有认可的提议,保存最高acceptedProposal编号的acceptedValue到本地;
第三步与第四步为给后来新提案的preposer获取已被选举出来的值的步骤,极为关键的两步,也是paxos最核心的约束所要表达的一步,这么做的好处是在其他proposer来发起提案的时候可以获取到已经被Accepted(也就是可以保证在大多数节点具有一致性)的值;
约束(核心):Phase 1a If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, then it responds to the request with a promise not to accept any more proposals numbered less than n and with the highest-numbered pro-posal (if any) that it has accepted.
方法定义:
prepare(提议版本号)
promise(提议版本号,已接受的value)
场景1
这是一个失败场景,preposer2向多数acceptor发出提案,但响应的没有达到多数,故失败;
涉及约束:
Phase 1
(a) A proposer selects a proposal number n and sends a prepare request with number n to a majority of acceptors.
场景图说明:
此处有5个Acceptor,Proposer 2向所有Acceptor发出了prepare提案版本为1请求,由于网络或者其他原因,只有2号与4号Acceptor(自上往下)返回了promise的响应,由于返回的接受者个数没有达到大多数(法定人数),故preposer2的prepare提案请求失败,等待下次prepare提案请求。
场景2
这个是一个prepare成功的场景,并且附加了acceptor只能接受提案号大于已接受提案号的条件;
涉及约束:
Phase 1
(b) If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, ……
场景图说明:
此次由preposer1发起prepare提案版本为2的请求,并且收到了大多数的Acceptor的响应,故此次prepare版本为2的请求成功,可以进入下个阶段;
场景5
这是一个在发布流程中没有接收到场景4广播的Listener 7转换为preposer 7,然后向已有Value被接收(selected)的Acceptor发起请求,并获取到已被选举到的值的过程:
如上图所示,其中preposer 7向所有可用的Acceptor,prepare(3)的时候,Acceptor根据约定,返回已经接收的值promise(2,V2),preposer 7根据约束,取大多数Acceptor返回的promise中版本号最大的acceptedValue作为自身的value;
当此场景结束之后,即达成了paxos算法的目的,proposer7(Listener7)与其他Listener一样,最终都接收到了同一个值:v2
涉及约束:
Phase 1
(b) If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, then it responds to the request with a promise not to accept any more proposals numbered less than n and with the highest-numbered pro-posal (if any) that it has accepted.
如果一个编号为 n 的提案具有 value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于 n 的任何提案,要么他们已经接受(accept)的所有编号小于 n 的提案中编号最大的那个提案具有 value v。
此约束如何推出的?可见文章底部的证明过程;
Accept
规则:
广播accept(n,value)到所有节点;
接收者比较和minProposal,如果n>=minProposal,则acceptedProposal=minProposal=n,acceptedValue=value,并且返回接收的版本号,否则,拒绝并且返回minProposal
提议者接收到过半数请求后,如果发现有返回值>n,表示有更新的提议,重新发起提议;否则value达成一致。
方法定义:
accept(acceptedVersion,acceptedValue)
accepted(acceptedVersion,acceptedValue)
场景3
这个接着场景2(preposer1获得了大多数acceptor的响应),进入accept阶段,根据规则,当preposer收到大多数acceptor返回的accepted响应后,将自身的值置为对应的value;
场景图说明:
由于preposer 1在场景2中成功prepare(2),故向所有Acceptor发起accept操作,并且获得了大多数(法定人数)的响应,preposer 1 将2号提案视为Selected,并且准备发布流程;
Publish
实际上在basic paxos中,发布步骤是被涵盖在accept步骤中的,为了方便理解,在此单独拿出
规则
将Selected的提案发布给Listener的子集,再有这组Listener发布给剩余全部的Listener;
但是由于消息传递的不确定性,可能会没有任何learner获得了决议批准的消息。当learners需要了解决议通过情况时,可以让一个proposer重新进行一次提案(也就是场景4)。注意一个learner可能兼任proposer。
场景4
按照场景 1->2->3->4->5 过一遍基本可以理解basic paxos的规则,结合思考一下场景5的意义,基本就可以看出算法的本质,即Basic Paxos如何最终保证一致性的。
算法的提出与证明
来自维基百科
首先将议员的角色分为 proposers,acceptors,和 learners(允许身兼数职)。proposers 提出提案,提案信息包括提案编号和提议的 value;acceptor 收到提案后可以接受(accept)提案,若提案获得多数派(majority)的 acceptors 的接受,则称该提案被批准(chosen);learners 只能“学习”被批准的提案。划分角色后,就可以更精确的定义问题:
决议(value)只有在被 proposers 提出后才能被批准(未经批准的决议称为“提案(proposal)”);
在一次 Paxos 算法的执行实例中,只批准(chosen)一个 value;learners 只能获得被批准(chosen)的 value。在 Leslie Lamport 之后发表的paper中将 majority 替换为更通用的 quorum 概念,但在描述classic paxos的论文 Paxos made simple 中使用的还是majority的概念。另外还需要保证 progress。这一点以后再讨论。作者通过不断加强上述3个约束(主要是第二个)获得了 Paxos 算法。批准value 的过程中,首先 proposers 将 value 发送给 acceptors,之后 acceptors 对 value 进行接受(accept)。为了满足只批准一个 value 的约束,要求经“多数派(majority)”接受的 value 成为正式的决议(称为“批准”决议)。这是因为无论是按照人数还是按照权重划分,两组“多数派”至少有一个公共的 acceptor,如果每个 acceptor 只能接受一个 value,约束2就能保证。
于是产生了一个显而易见的新约束:
P1:一个 acceptor 必须接受(accept)第一次收到的提案。
注意 P1 是不完备的。如果恰好一半 acceptor 接受的提案具有 value A,另一半接受的提案具有 value B,那么就无法形成多数派,无法批准任何一个 value。约束2并不要求只批准一个提案,暗示可能存在多个提案。只要提案的 value是一样的,批准多个提案不违背约束2。于是可以产生约束 P2:
P2:一旦一个具有 value v 的提案被批准(chosen),那么之后批准(chosen)的提案必须具有 value v。
注:通过某种方法可以为每个提案分配一个编号,在提案之间建立一个全序关系,所谓“之后”都是指所有编号更大的提案。如果 P1 和 P2 都能够保证,那么约束2就能够保证。批准一个 value 意味着多个 acceptor 接受(accept)了该 value。因此,可以对 P2 进行加强:
P2a:一旦一个具有 value v 的提案被批准(chosen),那么之后任何 acceptor 再次接受(accept)的提案必须具有 value v。
由于通信是异步的,P2a 和 P1 会发生冲突。如果一个 value 被批准后,一个 proposer 和一个acceptor 从休眠中苏醒,前者提出一个具有新的 value 的提案。根据 P1,后者应当接受,根据 P2a,则不应当接受,这种场景下 P2a 和 P1有矛盾。于是需要换个思路,转而对 proposer 的行为进行约束:
P2b:一旦一个具有 value v 的提案被批准(chosen),那么以后任何 proposer 提出的提案必须具有 value v。
由于 acceptor 能接受的提案都必须由 proposer 提出,所以 P2b 蕴涵了 P2a,是一个更强的约束。但是根据 P2b 难以提出实现手段。因此需要进一步加强 P2b。假设一个编号为 m 的 value v 已经获得批准(chosen),来看看在什么情况下对任何编号为 n(n>m)的提案都含有 value v。因为 m 已经获得批准(chosen),显然存在一个 acceptors 的多数派 C,他们都接受(accept)了v。考虑到任何多数派都和 C 具有至少一个公共成员,可以找到一个蕴涵 P2b 的约束 P2c,
此处也是此篇文章一开始提出的核心:
P2c:如果一个编号为 n 的提案具有 value v,那么存在一个多数派,要么他们中所有人都没有接受(accept)编号小于 n 的任何提案,要么他们已经接受(accept)的所有编号小于 n 的提案中编号最大的那个提案具有 value v。
可以用数学归纳法证明 P2c 蕴涵 P2b:
假设具有value v的提案m获得批准,当n=m+1时,采用反证法,假如提案n不具有value v,而是具有value w,根据P2c,则存在一个多数派S1,要么他们中没有人接受过编号小于n的任何提案,要么他们已经接受的所有编号小于n的提案中编号最大的那个提案是value w。由于S1和通过提案m时的多数派C之间至少有一个公共的acceptor,所以以上两个条件都不成立,导出矛盾从而推翻假设,证明了提案n必须具有value v;
若(m+1)..(N-1)所有提案都具有value v,采用反证法,假如新提案N不具有value v,而是具有value w',根据P2c,则存在一个多数派S2,要么他们没有接受过m..(N-1)中的任何提案,要么他们已经接受的所有编号小于N的提案中编号最大的那个提案是value w'。由于S2和通过m的多数派C之间至少有一个公共的acceptor,所以至少有一个acceptor曾经接受了m,从而也可以推出S2中已接受的所有编号小于n的提案中编号最大的那个提案的编号范围在m..(N-1)之间,而根据初始假设,m..(N-1)之间的所有提案都具有value v,所以S2中已接受的所有编号小于n的提案中编号最大的那个提案肯定具有value v,导出矛盾从而推翻新提案n不具有value v的假设。根据数学归纳法,我们证明了若满足P2c,则P2b一定满足。P2c是可以通过消息传递模型实现的。另外,引入了P2c后,也解决了前文提到的P1不完备的问题。