一步一步推导Paxos算法
Posted ioSeeker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一步一步推导Paxos算法相关的知识,希望对你有一定的参考价值。
前言
本文参考Paxos made simple这篇论文, 对Paxos算法的推导过程用接地气的场景来表述. 下文会从"n个同学开party, 需要确定谁来当东道主"的场景, 一步一步的推导出Paxos算法.
背景
假设有n个同学, 准备周末去某个人家里开party, 他们现在需要确认他们这些人中究竟谁做东道主负责party的举办事宜.
每个人都可以提议自己当东道主, 并且有权同意、否决别人的提议(也就是系统中, 每个节点既是proposer, 又是acceptor).
另外, 他们每个人之间都是点对点通信的, 比如说通过微信私聊的方式.
那么, 通过什么投票方案可以得到一个共识呢? 总体方向肯定就是"多数派原则"了. 但是这个原则之下, 还是有很多边边角角的问题会导致无法达成共识, 还需要有具体措施来规避这些问题.
下面会从"多数派原则"这个最基本方案, 一步一步的解决遇到的问题, 一次次的迭代, 最终渐渐推导出完善的一套方案: Paxos!
例如, 下文的版本1只是使用"多数派原则"这个基本方案存在明显的漏洞, 后面的版本2、3、4在版本1的基础上, 一步一步的解决这些问题.
版本1 多数派原则
问题:
无法达成多数派, 出现split vote, 形成僵持局面
假设n=5, 共有ABCDE这些个同学(系统中有5个节点).
另外还假设这些同学脾气都很犟, 不肯变通, 自己同意了一个提议后, 打死都不再改变主意了(不允许各个节点accept一个值后, 不能再改变).
我们看看这样的设定会有什么问题, 最后来思考如何避免这个问题.
那么考虑这个场景: 同学A向提议"来我家", AB都同意了; 与此同时, 同学C提议"来我家", CD都同意了; 与此同时,同学E提议"来我家", E自己同意了. 这样下来, 没有一个提议可以形成多数派.
由于这些同学脾气都很犟, 互不相让,不再改变主意, 那么这次开party肯定就泡汤了, 因为5个同学没有达成一致的共识.
解决方案:
允许acceptor改变自己的主意, 如果acceptor即是已经accept了某个值, 后面也可以accept其他的提议.
也就是一轮投票不成, 大家可以再商量商量, 不要脾气犟的跟驴一样.
版本2 节点可以接受后续的提议
在版本1的基础上, 添加一个新措施: 节点可以改变主意, 接受后续的提议. 这样我们就有了迭代2. 我们继续看版本2的问题是什么.
问题:
有acceptor在接受一个提议后又改变了主意, 可能会导致系统选择了多个值(conflict choice)
假设n=5, 共有ABCDE这些个同学(系统中有5个节点).
另外还假设这些同学脾气都很好, 可以改变自己的主意.
那么考虑这个场景: 同学A向提议"来我家", ABC都同意了; 与此同时, 同学E提议"来我家", CDE都同意了;
这里E充当了老好人的作用, 导致这5个同学都以为达成共识了, 同学们里面有两个人都认为自己是东道主了. 最后AB高高兴兴的在A的家里开party, 然而CDE就是在E的家里开party…
解决方案:
在proposer提议之前, 再引入一个prepare阶段: 先向所有acceptor发起询问, 询问它有没有已经接受某个值(accepted, 不一定是chosen. 当然, 在Paxos中, 这个accepted的值肯定也是最后被chosen的值), 如果有接受的值, 那proposer必须改变自己的提议值, 使用已经接受的值作为新的提议值. prepare阶段之后, 再进行提议阶段(accept阶段).
版本3 两阶段协议
在版本2的基础上, 引入了两阶段协议的思想. 这样我们就有了版本3.
问题:
系统节点之间的通信没有保障, 消息可能丢失, 也可能延时到达. 可能导致proposer误认为当前还没有被accepted值, 也就是两阶段协议形如虚设, 最后导致了和版本2一样的问题(conflict choice)
假设n=5, 共有ABCDE这些个同学, 他们使用两阶段协议的思想来进行协商.
考虑如下图所示场景:
同学A发起提议前的prepare阶段发现还没有值被接受, 所以直接提议"来我家".
在他的提议被BC同学看到之前, E同学发起了提议"来我家"(他在prepare阶段也发现当前还没有值被选定).
由于网络的不可靠, 同学E的提议先于A的提议到达了BC那, 所以BC先accept了同学E的提议. 所以同学E认为已经确定了自己就是开patry的东道主.
但是, 故事还没有结束, BC同学此后又收到了A同学迟来的提议, 因为同学们都是脾气挺好的老好人, 所以BC改变了主意接受A同学作为东道主.
所以, 现在的情况又和版本2类似, 同学们里面有两个人都认为自己是东道主了.
解决方案:
上述场景中, 因为"迟来"的提议导致了conflict choice的出现, 所以, 我们需要有手段可以屏蔽掉上述场景中那些"迟来"的提议.
方法是为每个提议都进行编号, 而且acceptor需要承诺如果已经遇到一个编号为n的提议, 那么后续遇到的编号小于n的提议, 都直接拒绝.
编号维护
每个proposer节点都保存自己至今为止所看到、用到过的最大的编号,记为maxProposal.
要生成一个新的提议号时,proposer用n=++maxProposal来作为提议编号.
在proposer角度来看, 这个编号可以不需要持久化. 从下文可以知道, 只要acceptor做了持久化即可.
版本4 有序的两阶段协议
经过多次迭代, 我们已经接近完美的投票流程了. 我们再版本3的基础上, 对所有提议进行编号后, 得到的新版的投票流程如下:
prepare阶段
proposer: 发起prepare请求, 请求中携带着本次提议的编号n, 其中n=++maxProposal.
acceptor: 接到prepare请求时, 如果编号n大于等于promiseProposal, 那么他承诺永远也不会同意提议编号比这n值小的prepare请求, 记为promiseProposal=n, 需要进行持久化. 另外, 如果它已经accept了某个值, 需要把接受的值(记为acceptedValue)和当时的提议编号(记为acceptedProposal)返回给proposer; 如果n小于promiseProposal, 那么acceptor可以拒绝该请求, 同时要返回自己的promiseProposal以便proposer更新下一次的提议编号.
proposer在发出prepare请求之如果收到多数派的acceptor的承诺, 那么可以进入accept阶段. 根据acceptor返回的acceptedValue来确定有没有已经被accept的值, 如果有, 我们就要在accept阶段时使用这个值. 否则的话使用自己一开始的提议值.
如果prepare阶段没有收到多数派的承诺, 说明本次提议已经过期, 需要更新自己的提议编号, 重新发起prepare请求.
accept阶段
proposer: 发起accept请求, 请求中携带本次提议的编号n和本次的提议值.
acceptor: 接到accept请求后, 检查编号n是否大于等于promiseProposal, 如果是, 那么acceptor接受该提议, 更新acceptedProposal和promiseProposal的值为n, 以及更新acceptedValue为提议值, 需要进行持久化; 如果不是的话, acceptor就拒绝该请求, 同时要返回自己的promiseProposal以便proposer更新下一次的提议编号.
如果proposer收到了过半数的acceptor的接受,那么它就可以确定自己提议的值被选定了. 否则需要更新提议编号, 重新回到prepare阶段.
最后我们一步步完善后得到的流程其实就是Paxos算法. 关于Paxos的正确性的证明, 在这里就不赘述了. Paxos算法的具体流程和证明请参考[1].
最终, 同学们通过Paxos算法确定了谁家作为东道主, party顺利举行! (当然, 也可能有少数派的同学出状况了没有参加party~~).
总结
我们从简单的"多数派原则", 经过的多次迭代可以简述为如下:
纯粹"多数派原则"会导致split vote的问题.
所以要允许acceptor改变自己的主意, 但这样做会导致多个值被选定(conflict choice).
所以要引入两阶段协议, 让proposer提议之前, 检查有没有已经被选定的值, 没有的话再来提议. 但是可能会有延时到达的提议, 导致两阶段形同虚设.
所以要对proposal进行排序. 如果acceptor已经接受了更加新的提议, 对于迟来的老的提议应该拒绝掉.
参考
[1] Paxos made simple
以上是关于一步一步推导Paxos算法的主要内容,如果未能解决你的问题,请参考以下文章