首页 > 代码库 > Paxos 学习总结
Paxos 学习总结
最近学习了分布式领域的重要算法Paxos,这里罗列下关键点当作总结。自己水平有限,难免存在谬误,恳请读者指正。本篇不包括Paxos的基本理论介绍,Paxos基础可以参考下面的学习资料章节。
1 Paxos图示
画图总结了原始的Paxos算法,主要来源于Paxos Made Simple。没有Leader也没有任何优化过程,并且把Proposer/Acceptor/Learners分开表示。这只是为了更容易梳理出三者之间的关系。我们只要知道实际Paxos工程中会把这三者重叠处理就可以了。
Phase 1注:
1 Proposer向一个多数派Acceptor发送阶段1a消息时,所谓多数派可能是全部Acceptor也可能只是一个大于50%的Acceptor集合。
2 阶段1可能产生多个Master,在若干Master进入阶段2后,仍有可能有新的搅局者进入阶段1,Paxos的各个阶段是可以互相重叠的。也就是说可能出现某个Proposer处于阶段1而另一个Proposer处于阶段2中,且这两个Proposer也完全有可能因为访问同一个Acceptor而互相影响。处于阶段1和阶段2的Proposer之间的作用就是通过图中的虚线完成的:一个处于阶段2的Proposer因为给某个Acceptor的(num value)赋值导致另一个处于阶段1的Proposer不得不放弃自己的取值转而使用这个阶段2的Proposer的取值。
Phase 2注:
Q:如果多个Master已经发送2a消息,是不是未来的确定性取值一定在这几个Proposer中产生?
A:当然不是,因为随时可能会有新搅局者在此时加入阶段1,假设x(3) y(5) 两个处于阶段2的Proposer(3,5是其n值)已经发出2a阶段消息,但龟速网络导致x的包在路上耽误了,而y的包也只到达了1个Acceptor,其他的包在路上。此时一个新的搅局者z加入阶段1,发送了更大的n(假设是6),由于n大于Max(Max是5,6>5成立)所以它迅速获得资格进入阶段2并使用自己的值提交Acceptor,此后即使x和y的龟速消息再到达也无济于事了,因为Acceptor的Max已经被z的阶段1消息抬高了,x和y的阶段2a消息会被Acceptor丢弃。当然了xy战胜z的概率还是很大的,因为只要xy的2a阶段消息能在z的阶段a1阶段到达Acceptor前,抢占一半以上的Acceptor,就可以了。因为这样就有一半多的Acceptor会阻挡掉z的阶段1a消息(作用路径是图中虚线),迫使z无法使用自己的值,而只能接受y已经设的值。
Phase 3注:
1 学习过程是可能会乱序的,所以必须按照id(也就是图中Phase3的num)递增顺序学习。
2 阶段3的学习过程,消息传递时有多种选择,比如简单的多对多,m个Acceptor和n个Learner建立m*n条消息链路,然后发送消息。或者多对一对多,即所有的Acceptor将消息发送给一个固定的Learner,然后这个Learner再广播给所有其他Learner。可参考<<Paxos Made Simple>> 2.4节
2 学习资料
我觉得读论文大概是学习Paxos的最好的方式了,读不懂怎么办?一篇一篇换着读。下面4篇论文从不同的方面论述了Paxos原理及应用场景,内容虽有重复但也有很大互补性,仔细阅读这4篇基本可以对Paxos有全面的理解。我觉得作为最早发表Paxos算法的<<The Part-Time Parliament>>反而可以略过不读,或者在全面理解Paxos后再当作历史读物看看。
1 <<Paxos Made Simple>>(原文,中文)
这是Lamport 2001年写的基本上就是<<The Part-Time Parliament>>的简化版本。我觉得这篇比较通俗易懂。同时中译文质量也很高而且关键位置译者添加了一些注释有助于理解。在这里面Lamport强调的是Paxos的两阶段执行过程,最后的Phase 3没有被当作独立的阶段(Lamport用独立章节2.3介绍了Phase3),不过在其他文献中更多把Paxos看作3阶段的过程,第3阶段也就是学习阶段。个人觉得3阶段更方便理解所以在作图的时候分为三阶段了。
2 <<The Chubby lock service for loosely-coupled distributed systems>>(原文,中文)
文章作者就是提出“世上只有一种一致性算法,那就是Paxos”的Mike Burrows。他在Google也是Paxos方面的先驱,而这篇Chubby也被下面Google自家的<<Paxos Made Live>>作为第一参考文献来引用。这篇Google工程实践经验主要介绍了Paxos在工程中的应用,Paxos的两大用途之一就是分布式锁,Google使用Paxos完成了其内部的分布式锁服务Chubby。这篇文章围绕3个主题:1什么是分布式锁 2Paxos如何作为分布式锁来使用 3工程实践中的各种困难如何客服。同时我们也能从这篇里寻觅到GFS和Bigtable是如何与Chubby配合的。
3 <<Paxos Made Live>>(原文,中文)
这篇应该算是Google对于其内部使用Paxos的总结,相比于Chubby那篇总结性更强,同时对于Paxos算法本身也有一定介绍 。本篇文章在Chubby篇之后写成,其参考文档里的第一篇就是那篇Chubby。本文很适合作为学习完Paxos原理后的第一篇文章来阅读。通过工程师视角描述的Paxos更容易被工程师理解(以后看到任何Engineering Perspective的字样要注意了,很可能会有惊喜哦)。比如在解释为什么实现一个Paxos系统如此困难时,他们是这么说的“虽然Paxos算法本身用一页伪代码就可以描述下来,但是我们的完整实现包含了数千行C++代码。代码膨胀并不简单地是因为我们采用了C++来取代伪代码,也不是因为我们代码风格的繁琐。将该算法转换为一个实际的,产品级系统需要引入很多的features和优化(有些是已经发表过的研究成果,有些不是)。”。对于Paxos的“在某个值上达成一致”这样的用法,它也明确介绍成“在我们的系统中,需要在‘(多副本)日志中的下一条记录是哪条’上达成一致。”,这种文章读起来真是酣畅淋漓!
4 <<Consensus on Transaction Commit>>(原文,中文)
两位图灵奖作者合著的文章必须不能错过。这篇同时由两位理论创始人介绍了自家的2PC和Paxos,然后提出了一个2PC和Paxos杂交出的Paxos提交算法。通过正常情况和异常情况的性能对比,得出结论“两阶段提交实际上与只有一个协调者Paxos提交是同构的”。这篇文章可以作为学习2PC和Paxos的一篇不错的文章。
5 <<布式锁服务>>(链接)
这篇要比上面4篇来头小多了,是国内大学生论文性的文章,作者的团队基本实现了一个简化版的Chubby,但是因为文章来源于实践还是很有意思,非常值得一读。
3 Paxos实例与Mutl-Paxos
一个Paxos实例就是一次完整的Paxos运行过程。即完整的执行一遍Phase1~3(假设没有优化),向Paxos提交一个value值就意味着在提交该value值时会启动一个Paxos执行实例。实际系统会以Paxos为基础来实现在一系列value值上的一致性,比如把一个日志文件,逐条的分发到一个多机的备份环境,每次分发一条日志可能就是运行一次Paxos实例。多个Paxos实例连续运行也就是Mutl-Paxos,Mutl-Paxos有几种优化方法,可以参考<<Paxos Made Live>> 4.2及5.2节。
4 编号n的选择
Proposer需要选出一个递增的唯一序列号,有一种非常形象直观的方法:在一个具有n个副本的系统中,首先为每个副本r分配一个在0和n-1之间的标识符Ir。副本r可以选择一个比它所有已知的序列号大的最小s作为序号,同时保证s mod n=Ir。举例来说,在一个5副本的Paxos系统里,可以为副本1制作一个序列号队列0,5,10,15...为副本2制作好序列号队列1,6,11,16...以此类推,当Proposer需要增大序列号时,即从自己的队列里顺序取出一个即可,这样就保证了每个Proposer取出的都是全局最大而且与别人不重复的编号了。可参考<<Paxos Made Live>> 4.1节
5 paxos优化
这里优化的基础和原始Paxos算法有微小差异,这里假设所有的Proposer是由一个固定的Leader来发起请求的,选出一个Leader来作为唯一的提案提出者可以防止活锁,活锁是由于不断的新Proposer提出更高编号的阶段1a请求而导致每个Proposer完成阶段2a的消息。关于Leader可参考<<Paxos Made Simple>> 2.4节
1 可以通过让Leader仅给半数以上的Acceptor发送阶段2a消息来减少正常情况下的消息数。Leader只要从半数以上的Acceptor接受到阶段2b消息,就可以知道v已经被选定,如果未收到足够多的阶段2b消息,再向其余的Acceptor发送阶段2a消息。这种方法可参考<<Consensus on Transaction Commit>> 4.1节
2 可以将多个Paxos实例串联起来以减少消息数目。如果在多个实例执行中,协调者(也就是Leader)没有变化,那么阶段1a 1b的消息可以被忽略。为了从这个优化中获益,Muti-Paxos算法会设计很久才会选举一次Leader(这其实就是Fast Paxos的基本思想了,其实这也比较符合直觉,选举Leader其实不是保持一致性的主要工作,而是为了应对异常而已,在很多实现里,选举Leader这一步往往是被简化的)。这种方法可参考<<Paxos Made Simple>> 4.2节
3 Acceptor在选定v后直接广播给learner,而不是先发送给Leader,再由Leader转发给Learner,这样以额外消息数为代价将阶段3的消息延迟消除,与Leader类似,在这些进程收到来自半数以上的acceptor的阶段2b消息后,就可以获知被选定的value值了。这种方法可参考<<Consensus on Transaction Commit>> 4.1节
6 Paxos用法
我觉得<<大规模分布式存储系统>>中的说法比较准确的,Paxos有两种用法:
1 实现全局的锁服务或者命名和配置服务,例如Google Chubby以及Apache Zookeeper。
2 将用户数据复制到多个数据中心,例如Google Megastore以及Google Spanner。
对于用途2相对好理解,因为原始Paxos示例就展示了复制数据到多备份机的功能。对于用途1,可以阅读Chubby论文来学习:首先要明白Paxos分布式锁是建议性锁而单机的类Mutex是强制锁,作为建议性锁要求使用方在使用资源前主动询问锁的状况,建议锁和资源在物理上是分离的,防君子不防小人。我们假设建议性锁与Mutex一样,会有一个类似id的身份标识。分布式锁的id不是"123abc"这样的无意义标识符,而是高大上的Unix文件路径格式,从"123abc"变成"/123/abc",这样id变得非常直观,因为可以通过id直接表示出锁是哪个组哪个应用的哪个功能的,这样的名字"/projectX/groupA/add"明显比"123abc"要直白了很多。这种路径同时使得锁具备了层级父子关系也非常容易引入更多特性。要明白这种表示法其本质上与Unix的文件系统关系不大,因为"/"完全可以用别的符号比如“|”代替,比如"|projectX|groupA|app",写成反斜线完全是为了照顾程序员的直觉,减少学习成本(Chubby 论文是这么说的“因为Chubby的名字空间结构类似于文件系统......同时也降低了培训Chubby用户的难度。”)。所谓锁的id表示法更雅观的名字应该叫命名空间,这类似于编程语言的命名空间:通过层级关系最终定位一个类/变量的方法。
看完了命名空间,那Paxos究竟如何当作分布式锁使用呢?Paxos的基本用途就是使得多台机器在一个确定性取值上达成一致。假设现在有一个5备份机的Paxos环境,我们就叫它5Paxos。有一个外部项目Xapp要使用5Paxos作为分布式锁,Xapp要锁住自己的某个服务http://www.xxx.com/make-id,它需要先向5Paxos发送一个lock值,和自己申请的路径"/Project/Xapp/make-id"。这样5Paxos就在这个确定性取值(“/Project/Xapp/make-id”是lock还是unlock)上达成一致,当前的一致就是lock。只有当Xapp再发送一个unlock后,5Paxos在新的取值unlock上重新达成一致。因为是建议性锁,所以Xapp内部任何要使用这个make-id接口的服务必须在使用前主动检查一下5Paxos存储的"/Project/Xapp/make-id"锁定状态是lock还是unlock。并遵守“在unlock时不访问这个服务”的君子协定,这样锁的作用就最终达成了。而作为分布式锁的最大优点就是高可用性,5Paxos的5台备份机中坏掉2台完全不影响这个锁的正常工作。
Paxos 学习总结