前两篇学习笔记给出paxos算法较为详细的文字描述。如果对算法的两个阶段四个过程的定义还不太了解可以点击这里,这段文字出自夏超伦的论文,描述在算法的两个阶段里proposer、acceptor、learner三个角色需要完成什么工作。可惜小夏同学并没有描述得很详细,我在编码实现的时候发现有很多重要的细节没有提到,proposer pick a value这么重要的过程都没有写.........感觉工程价值不大(自我感觉良好^_^),所以当时就打算要写篇详细的文档记录编码时遇到的问题,废话就不说了,下面就是Paxos算法(basic paxos)的工程化描述。
一、基本算法
1.1 基本角色及该角色至少维护的数据
Proposer(Coordinator):【crnd,cval】初始化【0,NULL】。在lamport的fast paxos[1]一文中描述的角色中,proposer代表客户端用以提出请求,coordinator代表提出提案的真正角色。但为了和其他文献[2]中的角色介绍兼容(俗称向下兼容^_^),本文决定用proposer来表示提出提案的角色。
Acceptor:【rnd,vrnd,vval】,初始化【0,0,NULL】
Learner:学习value,开始执行任务。
在最初的算法中,每个角色都有多个并且相同角色之间互相平等。
1.2 变量定义
● Instance id:实例编号
● crnd:某P发起过的最高的提案编号
● cval:某P在提案编号为crnd的round中提出的Value
● rnd:Round number,某Acceptor参加过的最新的提案编号
● vrnd: value_round number,某Acceptor最新批准过的提案编号
● vval:某Acceptor最新批准过的Value值。
● Value:客户端的具体请求:命令或者其他
● Quorum: (a majority of)大多数的acceptor
1.3 众所周知的两个阶段
Phase1: 包括prepare 和 promise or reject(可选)
Phase2: 包括accept 和accepted or Nack(可选)
一个round(一个paxos算法的执行过程)包括Phase1和Phase2两个阶段。
1.4 补充说明
★Proposer可以不断提出新的提案,而不必等待之前的所有提案都被批准。
★这里假设没有拜占庭将军问题(Byzantine failure),即虽然有可能一个消息被传递了两次,但是绝对不会出现错误的消息;只要等待足够的时间,消息就会被传到。即消息可能延时或者丢失(等待无限长的时间),但是不会出错。
1.5 算法描述
一个实现paxos算法的系统中可能有多个实例(instance),一般来说,一个实例处理一个来自客户端的命令请求(value),各个实例之间可以同时运行而不互相影响。处理一个实例时,可能包括了一个或者多个round,即每一个实例都需要运行一次一致性算法。
下面将讨论某一个实例运行时可能出现的各种情况,值得一提的是,round number被定义为提案编号,且所有的提案编号之间必须是一个偏序关系(例如将序数和 proposer 的名字拼接起来),为了避免歧义,设序数为M,每个proposer都有一个proposer_id,则某个prposer_id为1的proposer所提出的提案中,初始化的提案编号为M+proposer_id。在这里,为了说明简单,我们令M = 10,则proposer_id = 1的proposer提出的提案中提案编号被初始化为11。
A、proposer pick a value原则(在phase
• 收到来自某quorum的phase 1b【rnd,vrnd,vval】消息回复。令K为来自该quorum消息中所有vrnd的最大值,V为来自该quorum的消息中vrnd = k的消息对应的vval值的集合(当k>0时,fast paxos在文中证明了这样的vval值只有一个)。
If k = 0,P可以任意挑选一个来自client的value作为提案提出
else,将V中的那个唯一的value作为提案提出
B、proposer原则
• 在提案编号为i (rnd = i)的提案没有被大多数acceptor(某quorum)批准之前,若收到某A的Nack【j】或者reject【j】,j>i,说明该实例中已经有更改编号的提案发生,应设置crnd = h+M,(提案编号的递增可以自己定义)并重复prepare过程。
C、acceptor原则
◆ 任何一个acceptor必须批准它收到的第一个value
◆ 如果某acceptor在处理某proposer(proposer_id = x)的proposer提出的提案编号为i的提案过程中,收到了来自其他proposer(proposer_id = y)的编号更高的提案消息prepare【j】(j>i),则该acceptor利用reject【j】或者Nack【j】提醒proposer(proposer_id = x)有更高编号的提案发生,中断原有的round i。如果x= y ,即同一个proposesr提出了更高编号的提案消息(prepare【j】),则acceptor接受编号更高j的提案消息(promise【j】),忽略提案编号为i的提案消息(accept【I,value】)。
① 收到来自客户端的请求
② 假设proposer_id为1的proposer开始提出一个提案来处理这个来自客户端的请求,
③ 该proposer发出phase
④ 如果是初始化状态下,则每个acceptor都将原有的三元组结构更新为【11,0,NULL】,表示该acceptor不再处理提案编号小于11的其它提案。并且回复promise【11,NULL】给proposer_id = 1。如果是非初始化状态下(即vval不为NULL),每个acceptor回复promise【11,vval】给proposer_id = 1,即回复phase 1b消息。
⑤ 如果该proposer_id收到来自大多数(a quorum of)的acceptor回复的promise消息。它按照1.5.1中pick a value的原则选择适当的value值,记做vval_s,并组成一个内容至少包含【11,vval_s】的accept phase
⑥ 如果是初始状态,收到accept消息的所有acceptor接受该提案,并发送投票结果给proposer和所有learner。非初始状态,在简单模型下,所有的acceptor接受该提案,并发送结果给proposer和所有learner。将自身的三元组更新为【11,11,vval_s】。
⑦ 如果某Learner收到来自收到来自大多数(a quorum of)的acceptor的accepted,至少含有【11,vval_s】消息。该learner学习vval_s。
根据1.5.1的acceptor的原则可知,一个round有可能被中断和被忽略。现在举例来说明:
A、 在1.5.2描述的模型中,如果在步骤④到步骤⑥之间,另一个proposer(比如proposer_id = 2,则它的初始化提案编号为12)开始了步骤③,传送了一个至少包含【12】的prepare消息,并且由于网络传输的不确定性,【12】比【11】更早的抵达某个acceptor,则当【11】抵达时,由于两个prepare不是由同一个proposer提出,这个acceptor将会显式地拒绝这个prepare消息。并给proposer_id = 1的proposer发送reject【12】(或者nack【12】)消息。则【11】被中断了。
B、 如果在【11】编号的提案的表决之前,proposer_id = 1的proposer又提出一个新的prepare消息(这是被允许的),这个新提案的编号可能为【21】(本例中,举例新编号的递增差值是M,则11+M = 21)。并且【11】的消息在网络传输中丢失了,那么当某acceptor先收到【12】,再收到【11】时,由于这两个prepare都是由同一个proposer提出,那么该acceptor将直接忽略消息【11】。
二、发生活锁
如1.5.3所述的情况A所示,如果两个proposer同时提出决议,并且互相中断,将会形成活锁。在参考文献[3]。
三、选举leader
为了解决活锁的问题,lamport在fast paxos文章中推荐使用竞争leader的方式来进行算法。在这种情况下,只有确认自己成为了leader的proposer才能开始一个新的提案。就避免了被别人中断的情况发生,从而防止了相互中断形成活锁。
四、算法过程的动画演示
在公司做paxos专题的时候做了一个带动画的ppt,演示basic paxos算法的基本过程,感兴趣可以在这里下载。
五、后续:“分布式一致性Paxos算法学习笔记(四):算法回顾”
笔记四将对算法进行一次回顾。
References
[1]Leslie Lamport.Fastpaxos. DistributedComputing,19(2):79–103,2006.
[2]Leslie Lamport. Paxos made simple.2001,11,1
[3]http://en.wikipedia.org/wiki/Paxos_algorithm
特别声明:本篇文章的第一作者是我的师妹yz同学,本文对paxos算法的研究由我俩共同完成,而本文主要内容及文中提到的ppt由yz同学完成。本人为本文的第二作者。如有异议请参考相关法规联系我。
(1.1) 向所有的Acceptors发送Prepare(N_A)请求;
(1.2) 如果收到Reject(N_H)信息,那么重新发送Prepare(N_H+1);
(2.1) 如果收到Acceptors集合的任意一个Majority的Promise(N_A, V_A)回复,那么如果所有的V_A均为空,Proposer自由选取一个V_A’,回发Accept(N_A, V_A’);否则回发Accept(N_A, V_i);
(2.2) 如果收到Nack(N_H),回到(1.1)过程,发送Prepare(N_H+1);
(3.1) 如果收到任意一个Majority所有成员的Accepted信息(表明选举完成),向所有Proposers发送自身成为leader的消息;
(3.2) 向Learner发送value值。其中:
N_A为该次提案的编号;
N_H为当前提案的最高编号;
V_i为V_A中提案编号最高的value;
(1.1)接收Prepare(N_A),如果N_A>N_H,那么回复Promise(N_A, V_A),并置N_H=N_A;否则回复Reject(N_H)
(2.1)接收Accept(N_A, V_A),如果N_A<N_H,那么回复Nack(N_H)信息(暗示了该Proposer提完案后至少有一个其余的Proposer广播了具有更高编号的提案);否则设置this.V_A=V_A,并且回复Accepted信息。其中:
Promise(N_A, V_A):向Proposer保证不再接受编号不大于N_H的提案;
Accepted向Proposer发送提案被通过信息;
V_A为Acceptor之前审批过的提案(允许为空);
N_H为Acceptor之前接收提案的最高编号。
相对来说,Learner的行为理解更简单一些:学习value,开始执行任务。
算法在执行过程中,实例和实例之间是异步的;角色和角色之间既有同步的行为(因为一个完全异步的系统中,一致性问题将是无法被解决的[6]),又有异步的行为。下表2.1中按照我的理解以全局的角度来说明这其中的关系:
表2.1 一次Paxos实例
角色 / 时段
|
Phase 1
|
Phase 2
|
Phase 3
|
Proposer (P)
|
[1]竞争Leader: 向A发送prepare
|
[1]接收A的promise
[2]选取一个value并发送accept
|
(*)
|
Acceptor (A)
|
[1]接收处理P的prepare
[2]回复reject 或promise
|
[1]接收处理accept
[2]回复accepted或nack
[3]通知L
|
(*)
|
Learner (L)
|
无
|
无
|
[1]接收广播学习value或
[2]创建Proposer对象学习value
|