特别推荐:两军问题,一篇以实例来讲Paxos流程的好文!
Paxos
The Paxos algorithm, when presented in plain English, is very simple.
简单个屁
目标
在一个分布式系统中,几台计算机要就一个问题达成统一(比如他们要选择一个共同的命令序列)。现在要求在随时可能出现宕机和通讯失败、延迟(但信息不会中途被修改)的情况下设计一种通讯协议,使得他们最终选择出的结果被大多数机器接受
注意:proposal和choose似乎给人一种竞争的关系,但实际上paxos描述的是一种合作关系,即几个机器试图更快的得到共识,不论这个共识由谁提出
概念
- be chosen:当含有某个value的proposal被多数acceptor接受时,这个value就被确定为最终结果,称该value和对应proposal被选中了
- 特别的,可以有多个proposal被选中,只要他们都有同一个value
假设
不同的proposal必须具有不同的编号,这个的实现依硬件而异,在本中我们直接假设其成立,并且从小到大按自然数使用
协议
协议规则
为了使得最后必然得出结果,我们要求
- P1:一个Acceptor必须接受他拿到的第一个proposal
为了只得到一个结果,我们要求
- P2:如果一个带着v值的proposal被选中了,那么被选中的所有编号高于它的proposal所携带的value值也必须是v
由于是我们在设计这个协议,我们可以通过规定下述条件来简单的实现P2
- P2a:如果一个带着v值的proposal被选中了,那么被任何acceptor接受的所有编号高于它的proposal所携带的value值也必须是v
假如有一个Acceptor刚刚宕机了,那为了使得它既遵守P1又遵守P2a,我们实际上可以更干脆的要求
- P2b:如果一个带着v值的proposal被选中了,那么被任何proposer提出的所有编号高于它的proposal所携带的value值也必须是v
我们可以看看什么情况下能实现P2b,发现其实可以这样
- P2c:对于任何一个发布的proposal(n,v),必须存在一个多数Acceptor集合S,要么这个S中的所有Acceptor全部没有v,要么S中的Acceptor所接受的编号最大的proposal的value就是v
P2c可以保证实现所有P2系列的条件,这可以由数学归纳法得到
协议的执行
Proposer
Proposer的议程分两步:prapare request & accept request
- 使用一个新的proposal编号n,试图向Acceptor集合的每一个成员(或者至少向大部分)发送请求,并要求其回复如下内容
- 保证不再接受编号小于n的proposal(直接枪毙已经被prepare但未被接受的proposal,从而避免与之产生冲突)
- 如果Acceptor当前接受了某个proposal,返回这个proposal(包括编号和值)
- 只有一个proposer收到了大部分Acceptor的回应时,它才被允许发布proposal(否则回到步骤1),且它的内容如下
- 若回应的所有Acceptor当前都没有proposal,那么proposer可以使用任意value进行提议
- 否则,使用回应的Acceptor所接受的proposal中最高编号最高的proposal对应的value
Acceptor
在proposer议程的基础上,我们必须要更准确的规定Acceptor的行为准则,所以我们需要扩充P1条件
- P1a:一个Acceptor能够且必须接受一个编号为n的proposal,当且仅当它没有响应过任何一个编号大于n的prepare request
- 说明:“能够且必须”在原文中的表述为“can”,但根据后文给出的执行过程的描述(it accepts the proposal unless…),这里理解为能够且必须更为贴切,也更方便理解
有了它,这个算法实际上已经完整了,但为了更高效的执行,我们引入如下优化
- 一个Acceptor在如下情况不会对prepare request进行响应
- 已经接收到了超过n的prepare request(这个优化除了节约开支,还有一些额外的意义。若不然proposer需要去比较收到的proposal的最大编号和自己手里的编号,或acceptor需要对prepare request回应是否初步接受)
- 已经接受了编号n的proposal(可能是因为proposer重复发送了信息)
在这个优化下,Acceptor只需要记住当前最新接受的proposal(也即他接受的编号最大的proposal)和它响应过的编号最大的prepare request的编号
完整流程
Prepare Phase
- Proposer使用一个proposal的编号n,向全部Acceptor或至少一个majority发送内容为n的prepare request
- Acceptor接收一个编号为n的prepare request。若n大于它已经回复的prepare request的编号,那么它将承诺不再接受编号小于n的proposal,并且返回它已经接受的编号最大的proposal
Accepte Phase
- 当Proposer收到了大部分Acceptor对它prepare request的回复(否则不提出该proposal),它就给这些回应者发送一个编号为n,值为v的accept request。若回复中存在proposal,v等于其中编号最大的proposal的value;若不然,v任意。
- Acceptor接收一个accept request,其中proposal的编号为n,若它没有响应过编号大于n的prepare request,则它立刻接受并返回accepted,若不然,返回rejected
一个proposer可以生成多个proposal,只要它能满足算法要求。它可以在协议的任何时刻放弃proposal。
如果acceptor因为已经接受了更大编号的prepare request而忽略其他的prepare或accept request时,它应通知对应的proposer放弃该proposal。这仅仅是对性能做优化,与正确性无关。
Learner
Learner需要获取被选中的value,可以考虑的策略如下
- 每个acceptor在接受一个proposal后向所有learner发送这个proposal
- 尽管这和可以尽快使得learner找到选中的value,但冗余操作实在非常多
- 选择一个distinguished learner,所有acceptor向它发送proposal,它再把最后选定的value通知其他learner
- 这个可能不太可靠,因为distinguished learner可能故障
- 选择一组distinguished learner,任意一个都可以在确定最终结果时通知其他所有learner
- 中和了以上两者的优缺点
其实因为message可能丢失,所以可能没有learner知道有value被选中了。所以learner可以直接去问acceptor接受了什么proposal。再进一步,可能acceptor的故障使得我们搞不清楚是否有majority接受了一个proposal,这种情况下,learner只有在新的proposal被接受时才知道被选中的value是什么。所以如果一个learner想知道是否有value被选中,以及若有则选中的value是什么,它可以让一个proposer发送一个proposal(使用上文算法)
活锁
两个proposer可能持续发送prepare request导致他们的accept request反复被对方无效化。
为了保证流程的执行,我们必须选出一个distinguished proposer,作为唯一的proposal发送者。