感叹一下
不得不说近几年国内软件行业发生了巨大的变化,之前几乎所有应用都围绕桌面展开,而近几年很多让人神魂颠倒的关键词一个接一个的映入眼帘:web2.0、移动应用、云计算、大数据。互联网的浪潮一波接着一波,我们这代人也鉴证了在一次次的革命中科技企业的兴衰成败。貌似说的有些远了,其实我只是想说,互联网的繁荣会产出大规模的数据,必定给分布式技术带来的无比巨大的挑战,所以我们应该理解其基本原理来更好的投身于我们的事业之中~
进入正题。我是根据wiki上的算法描述来解释的,第一次看wiki的描述感觉摸不着头脑,有些地方总觉得不对,但是后来细细品味,wiki的文字并没有疏忽。
另外本人利用闲暇时间开发出了一个类Paxos的工程实现,借鉴了chubby以及zookeeper等项目的一些工程化方法,代码已经开源:https://github.com/15Koala/cocklebur 目前已经实现自动选主,数据服务还在开发阶段。后续我会把实现的具体细节以博客的形式发表出来,希望能和大家多多交流!
用Paxos做什么?
Paxos是一种使多个个体达成一致的一种协议,他被广泛的应用在分布式领域当中。然而大家在真正用它的时候都会选择类paxos去实现,而paxos则被认为是各种版本的类paxos的源头。Paxos致力解决在消息重复、丢失、延迟的情况下分布式系统消息传递一致性的保证,如果每个节点都遵守该协议那么就可以一致。
可能说道这里,有些人会感到困惑,什么叫达成一致呢?这跟整个集群有什么关系?好,那我说个具体的例子,比如:你想让一个集群启动后自动的选举出一个主节点作为master,你可能会说,我配置好了就OK了,但是master挂了怎么办?Paxos可以让集群自动选举出master,“选举”要比“任命”更鲁棒。而选举的过程其实就是修改节点状态(节点上的数据)的过程,大家根据统一的约定(Paxos协议)去选出一个master。
Paxos协议的描述
现在我们过一遍Paxos算法的内容,此时一定不要想太多工程实现上的细节问题,现在我们只需了解它怎么去工作就好了,因为具体实现与下面陈述的大相径庭。
首先,过程涉及三种角色,提案者(proposer),评议者(acceptor),使用提案的人(learner)。通俗点说就是proposer们要给acceptor们提一些提案,让acceptor最终敲定一个提案,敲定之后再通告所有的learner们。补充一句,为了打消你的一些疑虑,补充下面几点:1. proposer可以提交很多次自己的提案(因为这相当于消息重发);2. 也可以没让某些acceptor收到自己的提案(因为这相当于丢包了,但不能全部都收不到啊,这样这个提案就完全没意义了),3. Acceptor收到的提案可以顺序错乱的(这相当某些消息延迟了);4. 每个proposer都没有说违心的话,都有啥说啥了(这相当于没有拜占庭问题,发送的信息都是正确无误的)。
OK,到了现在人物出场完毕,接下来解释一下提案过程所涉及的概念:
提案(value):每个proposer向acceptor们提交提案时都会附带一个序号(自增的,你可以把提交的时间戳作为它的序号),之所以需要这个编号,是让它作为acceptor取舍提案的依据。但是value是提案的唯一标识,比如,一个proposer提了两次value = v1的提案,虽然第二次的序号要比第一次大,但是依然是同一个提案。
多数派(majority):多数派是所有acceptor的一个子集,元素个数多余一半就称之为多数派。他代表了某一群acceptor。根据抽屉原理,任意两个多数派之间必然有交集。 接受(accept):acceptor总会接收它收到的第一个提案;如果序号比之前接受的提案序号都高,那么它也会接收提案; 批准(chosen):一个多数派接受了一个提案,并且在该proposer发送accept请求确认之后,那么我们说该提案被批准。 prepare请求:提案请求,proposer 向acceptor多数派发送提案请求。 accept请求:批准请求,proposer 征求acceptor批准自己的提案。
OK,我们来看一下Paxos提案两个阶段:
A.准备(prepare)阶段:
1.proposer 选择一个提案编号 n (在proposer 角度看永远是自增的,但是在acceptor角度看就不一定了,因为可能延迟,你懂的)并将提案(value)发送给 acceptors 中的一个多数派(就是说超过一半的acceptor接收到了,但不能说是proposer 发送给了多一半的人,因为这并不能保证多数派收到提案,时时刻刻要提醒自己,发送不一定能收到!要仔细体会前面几句的区别哦);
2.acceptor 收到提案后,如果提案的编号大于它已经回复的所有提案的编号,接收该提案(约束P1a,见wiki百科)。acceptor 将自己上次接受的提案回复给 proposer(其实这里主要是为了优化用的,因为proposer如果知道了有跟自己不一样且编号较大的提案,他自己就不会再去申请了),然后,并承诺给该proposer不再回复小于 n 的提案;
B.批准(chosen)阶段:
1.当一个 proposor 收到了多数 acceptors 对 prepare 的回复后,就进入批准(chosen)阶段。他要向回复提案请求的 acceptors 发送 accept 请求,包括编号 n 和他的value(注意,此时可能会有多个proposor 认为自己已经被多数派认可,因为acceptors 在接受proposor1之后可能有来了一个持有更大编号的proposor2,实际上此时多数派可能更支持proposor2。另外需要再次强调一下,多数派一旦持有一个意见之后,不可能在找出持有其他意见的多数派,因为任意两个多数派必有交集,所以可以反证之); 2.在不违背自己向其他 proposer 的承诺的前提下(上文提到过这个承诺-不再接收编号更小的提案,言外之意,如果在该proposor发送accept请求之前,多数派成员发现了序号更高而且跟该proposor的提案不同的提案,那么肯定就接受了),acceptor 收到 accept 请求后即接受这个请求(如果acceptors们发现此时发accept请求的proposor持有的value并不是当前acceptor所接受的,那么将不会接受,所以接受的数量构不成多数派,那么该提案就失败了,如果构成多数派,那么就批准了)。
到此,整个协议就描述完毕,你可能更加关心各种异常情形。因为单从上面的描述中很难看出遇到异常情况如何去决策。
比如,一个常见的问题就是如果在批准阶段,一个proposor没有被批准(虽然他之前还看到希望来着),他发送的accept请求已经被某些acceptor所接受了,那么这些acceptor就不会在接受其他的value了,那他们以后怎么办?我想持有这样问题的朋友一定是在想这并不是所有人达成一致呀!没错,在批准的那一刻并不是所有人都一致,而是一个多数派一致了,那么这就够了,因为只要找出一个多数派批准,那么我们就强行决定,所有人都应该批准!
再比如一个问题:批准之后要是一个持有更高编号的延迟了,导致让编号较小的人率先获得多数派的批准。回答是只能对那些晚了的人表示遗憾了,因为有些事一旦错过就不在...这里不是开玩笑,是因为有些超时的行为是需要去权衡的,你也可能设定一个等待时间,在批准前先等一等,看看有没有晚来的“优秀提案”。