前言
说是前言,其实这里是最后写的,我在写这篇文章前看过了不少网上文章,也直接读了lamport关于paxos的原始论文,能记住paxos的规则,但始终感觉复杂难懂,无法融会贯通,不理解为什么如此设计
因此我一边写一边持续思考,发现一步一步通顺了起来,甚至最后自认为可以用一句非常简单的道理就可以概括paxos,希望读者也能在看完后有种恍然大悟的感觉:晦涩难懂的paxos其实也不过如此
下面就用现实模拟的方式一步一步引出paxos的规则,最后会用一句话简单总结paxos的精髓
问题
假设现在有个村子,村里有多个公民(相当于分布式系统的各节点),但是没有一个村长,所有人都绝对平等,但想绝对掌握每个人的行踪很难的,有的人可能短期失联找不到,好在村民都很淳朴不会恶意破坏规矩(无拜占庭),那么如何决策一件事,并且让所有公民都达成一致呢?比如想给村子起个名字
投票
现实中解决这个问题还是比较简单,所有人都有提案的权利,民主社会靠投票决定用哪个就可以了,少数服从多数
但是这还是有个问题,现实投票还是有个组织或裁判的角色,负责制定一个固定的投票时间窗口的,但是这里肯定是没有这种角色,因为众生绝对平等么,所以啥时候才算投票结束呢?如果有一个村民懒(节点宕机),一直不投票,要不要等呢,等到啥时候呢。
胜选
所以投票就得有个结束规则,就是只要:村民半数以上都同意某个结果,那么这个投票就结束,然后这个结果就胜选,作为最终结果
但这个规则有可能会导致问题,最简单的投票方式就是每个人只能投一票,这样会导致有多个提案时任何一个提案都没法超过半数,即使只有两个提案,如果每个都刚好半数村民同意,那这俩提案再也拉不到其他票了,都不会胜选
多票
这个问题其实解决方案也不是唯一的(比如raft就有其它方案),paxos的提出者lamport解决的思路也比较直接粗暴,既然是因为一个人只能投一票导致的,那就直接让每个人可以投多票
但这么做有引起了新问题,如果每个村民都可以投多票,就会出现多个提案都被投票数超过半数的情况,那就没法确定最终结果了
所以lamport又提出了直接的解决方案,既然没法避免多个提案都被通过,那就想办法保证多个通过的提案一定是同样的值,比如村民张三李四的提案都半数通过了,那么张三和李四的提案内容必须相同,比如都是“靠山村”,这样也能保证最终结果是唯一的
到这里感觉这个方案就像一步步挤出来的: 一人投一票 -> 导致可能选不出 -> 一人投多票 -> 导致选出多个 -> 保证多个选出提案相同值 -> 终
所以最后只要保证这个看似不讲理的:多个超过半数同意的提案(choosen)的值一定一样,问题就解决了
lamport: We can allow multiple proposals to be chosen,but we must guarantee that all chosen proposals have the same value
妥协
这个看似不靠谱的要求却成了保证最终达成一致结果的关键,最开始的提案肯定是一般不会一样的,比如张三提案“靠山村”、而李四提案“靠海村”,所以要保证最后结果一致,必然过程中有一个要做出妥协,改变自己提案的内容
那么问题来了,众生平等,谁妥协,什么时候妥协?
虽然每个提案者是平等的,但是提案总有个先来后到,先提案的人肯定无法预测未来,那么必然是后来者向新来者妥协
编号
那如何确定谁先提的案呢,很简单,提案时用时间做提案编号就可以,比如张三9点开始提的案,编号就是9,李四10点开始提的案,编号就是10,所以就算张三先来、李四后到
这个就是编号,代表谁先发起的提案,后发起的提案要向先发起提案妥协
两阶段
有了先后,这个问题就可以解决了
假如张三先提案,李四后提案,李四提案前先看一眼是否有村民已经选择了张三的提案(Phase 1),如果有,李四就妥协使用张三提案的内容进行提案(Phase 2),这不就保证最终结果一致了么
这个看一眼,看似简单,但实际上还是需要去挨家挨户的问,这是需要一定时间完成的(网络传输的速度是不确定的)
这就会导致一个问题,比如张三9:00提案还没来的及找村民投票, 李四9:01提案挨家挨户的问是否有人同意别人的提案(Phase 1),此时张三也再看(Phase 1),二者的结果都一样:所有村民都没投票呢,于是二者几乎同时分别发起了“靠山村”、“靠海村”的提案(Phase 2),又导致结果不一致了,这把又无解了
承诺
问题的根结在于“编号小的不一定就快”,提案者第一阶段询问各家投票结果时,很容易看到当前是否有人给编号小提案投票,但由于有可能编号小的提案人磨磨唧唧,编号大的询问完后它才开始发起投票,所以被逼无奈:在询问这段时间要额外做一件事,以保证上面那种事情绝对不会发生
怎么做呢,那就堵死!,就是李四在一阶段询问某个村民比如王五时,如果王五还没有给其他人投票,那就告诉他不需给比我编号更小的方案投票了,要一个这样得承诺
相当于李四在phase1阶段如果发现没有村民接受更小编号的提案,那未来就不会出现更小编号的提案被投票(张三被李四把路堵死了),所以目前的规则就是:
一个人再提案时,需要先询问,跟据询问结果,要么继承之前的提案,要么作废之前的提案,所以最终得结果一定是可以有多提案通过,但是所有通过得提案得内容一定是一样一样得
到此我们就解决了全部问题:
一人投一票 -> 导致可能选不出 -> 一人投多票 -> 导致选出多个 -> 保证多个选出提案相同值 -> 终
询问(补充)
但是上面隐藏了一个BUG,就是这个询问阶段,要看看有没有小编号得提案已被投票,以上的假设都是后来者可以完整得看到所有村民的投票结果,但是别忘了最开始的一个前提:所有村民的行踪是无法完全确定的,也就是说有些村民可能给一个小编好的提案投票后就失联了,那么这就会导致询问阶段可能无法准确看到到底有没有小编号得提案被投票
这个问题和之前投票结束规则的制订问题类似,那里一样无法保证拿到所有村民投票篇,所以定义半数以上同意就算通过,所以解决方案可以仿照一下:
询问阶段(phase1)只询问半数以上节点即可,然后根据半数以上节点的投票结果,来决定继承还是作废
这样做就可能导致不准确,明明有了村民已投票了编号更小的张三提案,但是由于李四没问到这个村民(但李四已经问过半数以上的村民了),所以李四发起了新的提案投票,这样是否有问题呢?
答案时不会的,想一想如果李四询问了大部分村民且都没有投票给张三,那么李四就获得了这大部分村民的承诺不会再给张三投票了,张三的投票必然无法到半数,所以张三的提案相当于被李四作废了
相较于之前询问时严格的查看所有人的投票结果,这次造成的问题只不过时原来可能会继承的特殊情况,变成了作废的情况,但无论时继承还是作废,都可以保证最总结果是一致的
作废(补充)
还有一种情况没有特殊提及,但是从张三的视角来看其实也有被侧面提及,就是后来者李四作废张三的方案,同样李四的方案也有可能作废,因为螳螂捕蝉黄雀在后,可能有个比李四编号更大的提案让李四的提案作废
如果李四在询问阶段发现,已经有编号更大的提案先询问过了,且半数以上村民都做出承诺了,或者甚至有人已经给更大编号投票了会怎么样?
那根据刚才的总结,很自然的李四就不需要再去发起投票了,因为显然自己已经被后者作废了,此时李四成了“张三”了
继承(补充)
上面还有个感觉不太符合常规的地方,就是当编号大的提案发现有编号小的提案已经有了投票,就会继承这个值来发起提案,这有点不符合日常,按道理有了别人的提案那我直接弃权不就得了?
这个是因为这样的,因为提案者再询问的过程中,其实就把小编号的路堵死了,它已经告诉半数以上节点不要接受小编号提案了,所以自己必须继承而不是弃权
看起来弃权是不掺和,实际上有了承诺这一步,继承才是不掺和,弃权反而是在搞破坏
最后
最后仍然用最简单的语句总结一下,一句话讲清paxos的核心内容:
根据提案的先后,要么后者作废前者,要么后者继承前者,最终结果一定是唯一的