1. 关于Zookeper
ZooKeeper是一个集中服务,用于维护配置信息,命名,提供分布式同步和提供组服务。 所有这些类型的服务都以某种形式被分布式应用程序使用。 每次实施服务时,都会有很多工作来解决不可避免的错误和竞争条件。 由于实施这些服务的困难,最初的应用程序通常会不稳定,这使得它们在变化和难以管理的情况下变得脆弱。 即使正确完成,这些服务的不同实现会导致部署应用程序时的管理复杂性。而Apache ZooKeeper致力于开发和维护一个开源服务器,从而实现高度可靠的分布式协调。
ZooKeeper允许分布式进程通过与标准文件系统组织类似的共享分层名称空间相互协调。名称空间由znodes组成,它们与文件和目录类似---ZooKeeper Wiki
Zookeeper的架构图如下:
2. Zab协议
ZooKeeper支持客户端读取和更新具有高可用性的键值对。通过将数据复制到多个节点并让客户端从任何节点读取来实现高可用性。
要理解Zookeeper,首先得熟悉它的协议,Zookeeper的协议就是Zookeeper Atomic Broadcast (ZAB).Zab协议由4个阶段组成,先来看下总体的层次.
- Phase 0 – Leader选举(选出Leader)
- Phase 1 – 发现(发现全局最新的事务)
- Phase 2 – 同步(各个Follower同步最新的事务)
- Phase 3 – 广播(正常工作的流程)
术语定义:
- history:已经被接收的提案的日志信息(已经完成的事务的记录)
- acceptedEpoch:接受到的最后一个NEWEPOCH消息的epoch。此时此刻所在的Epoch(准Leader生成后就生成的Epoch)。
- currentEpoch:接受到的最后一个NEWLEADER消息的epoch(注意是Leader发过来的)(生成新的Leader之前所在的旧Leader的Epoch)
- lastZxid:history中最后一个提案的ZXID编号
- ZXID通常由64位组成。高32位表示Epoch值,翻译过来指朝代。就是每次换了Leader之后,Epoch值都会增加1,用来表示所在的周期,方便Follower进程区别现在的Leader。由于有些Leader由于网络原因会与Follower断开联系,当它连上后,现在已经有了Leader,那么它就不能是Leader.Follower会根据Epoch值来判断当前的Leader。而后32位用来表示Zxid值。每当处理完一个事务,Epoch值都会增加1。而当重新选了一个Leader,那么Zxid重新开始计数。
3. ZAB协议实现的前提条件
- 完整性(Integrity )
进程Pj如果收到来自进程Pi的消息m,那么进程Pi—定发送了消息m。 - 前置性(Prefix)
如果进程Pj收到了消息m,那么存在这样的消息m':如果消息m'是消息m的前置消息,那么Pj务必先接收到消息m',然后再接收到消息m。我们将存在这种前置性关系的两个消息表示为m'm。前置性是整个协议设计中最关键的一点,由于每一个消息都有可能是基于之前的消息来进行的,因此所有的消息都必须按照严格的先后顺序来进行处理。
4. 各个阶段详解
详细的算法步骤可以参考http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf
-
阶段0: Leader Election(Leader选举阶段)
下篇文章将会讲到FLE算法.
ZooKeeper原来采用了三种选举算法,后来,淘汰了两种,只剩下FLE这种算法。使用FLE算法的目的,就是要选出具有最大提交历史的节点作为候选Leader,这样后续的日志就只需要考虑候选Leader到其Fellower节点的单向同步就可以保证一致性了。在FLE算法中通过筛选具有最大LastZxid(history中最后一个提案的ZXID编号)的节点作为候选Leader,因为具有最大LastZxid的节点肯定具有最全的提交历史。
在FLE算法中,每个节点都只能投一张选票,只有这样才能确定过半选票的统计值,其思路就是在投票的过程中,节点之间互相交换信息,然后节点根据自己获得的信息(发现更好的候选者)不断地更新自己手中的选票,更新的标准就是具有更新的提案号:要么具有更新的epoch,或者在相同epoch下具有更大的机器编号。那么这个迭代更新的过程什么时候结束呢?
首先,每一轮的选取会有一个递增的round number作为标识,这个值越高优先级越高;其次,每一个节点都有一个状态标识自己:election和leading/fellowing,同时每个节点都知道集群中其他节点的个数,以及和他们通信的方式。选举刚刚开始的时候,每个节点在没有先验信息的情况下都把选票投向自己,并把这个消息发送给所有的节点,然后等待其他节点们的响应,节点再收到这个消息的时候:
(1). 如果选票的round number比较旧,则忽略;
(2). 如果选票的round number比自己新,则更新自己的round number,并清空上一轮相关的陈旧信息,开始广播自己新的选票;
(3). 如果是在同一轮投票中:如果接收到的选票的角色是election,并且该消息附带更新的提案号,则更新自己的选票并继续广播自己的选票;如果收到的选票角色是election,但是消息的提案号比自己旧或者跟自己一样,则记录这张选票,而检查发现自己得到针对某个节点超过集群半数的选票,自己切换为leading/fellowing状态,并转入Phase Recovery;
(4). 任何时候一旦收到leading/fellowing的选票,都指明当前集群中已有有效的候选Leader了,直接更新自己切换入Phase Recovery阶段;
-
阶段1: Discovery(发现最后的事务)
在这个阶段,这个阶段选出的准Leader(prospective leader)肯定是集群中过半数机器的投票选出的leader。此时所有节点会把自己的F:acceptedEpoch通过FOLLOWERINFO发送给自己的prospective leader,当那个候选Leader得到过半的FOLLOWERINFO消息时候,会在收到消息中取出所见最大的epoch并将其递增,这样之前的Leader就不能再提交新的提案了,然后该候选Leader再将这个新epoch通过NEWEPOCH消息发回给这些节点并等待确认。
在Fellower节点收到候选Leader发送NEWEPOCH后,将其与自己本地的acceptedEpoch对比,如果比他们大就更新自己acceptedEpoch,并返回ACKEPOCH消息后进入Phase 2,否则切换会Phase 0状态。候选Leader也只能在收到过半数目的ACKEPOCH才会进入Phase 2。需要注意的是这里Fellower发送的ACKEPOCH包含了额外的重要信息——自己最新提交日志,这样候选Leader在收集ACKEPOCH的同时就知道哪个Fellower具有最新提交了,选定到这个具有最新提交的Fellower后向其同步日志。算法和第一阶段消息交流示意图如图3.1。
①:各个Follower将 FOLLOWERINFO发送给自己的prospective leader(让Leader来发现最新的事务)
②:候选Leader选出最新的Epoch并将这个新epoch通过NEWEPOCH消息发回给这些节点并等待确认。(把最新的Epoch返回给Follower)
③: Fellower节点收到候选Leader发送NEWEPOCH后,将其与自己本地的acceptedEpoch对比,如果比他们大就更新自己acceptedEpoch,并返回ACKEPOCH消息(把自己的事务上传)。
-
阶段2: Synchronization(同步阶段)
进入这个阶段后,候选Leader已经确立了最新任期号和最新提交日志,然后他会把自己的history通过新epoch作为NEWLEADER消息发送给所有的集群成员,集群成员更新自己currentEpoch 并按需同步history信息。完成这个步骤后候选Fellower向Leader发送ACKNEWLEADER消息,而候选Leader得到过半数目的ACKNEWLEADER消息后,会向所有的Fellower发送COMMIT并进入Phase 3,而Fellower接收到COMMIT命令而完成提交后,也会切换到Phase 3。第二阶段消息交流信息图示意图如下:
①:准Leader发送NewLeader(e’,history) 给follower,方便follower同步最新的事务,从而与Leader一致,达到一致。
②:Follower在收到准Leader的NewLeader(e’,history),它会对它本地的事务,如果需要更新的本地的事务,则更新。更新完之后,发送一个确认消息给准Leader。表明它已经收到。
③准Leader在收到确认信息后,再发送一个commit消息给Follower,通知他们可以提交事务了。Follower在收到之后,完成提交就可以。
-
阶段3: Broadcast (完成同步后,成员各自完成自己角色的任务)
到达这个阶段后,所有节点检查自己的prospective leader,如果发现它是自己,就切换到正式Leader的状态,不是这种情况的节点切换到正式Fellower的状态,而一致性协议保证此时只可能会有一个Leader。这是整个集群稳定工作状态,稳定之后的基本流程也类似于上面提到的Propose-ACK-COMMIT的伪2PC(与标准的2PC有区别)操作。其中2PC是指参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。消息传播示意图如3.3。
①:客户端发动事务请求给服务端Leader
②: Leader发送请求执行给每个Follower,让Follower执行事务。并把do,undo写入日志。
③:Follower执行事务,并把事务历史写入到日志中去,方便以后撤销。并返回事务是否执行成功给Leader。
④:Leader若收到超过半数Follower消息,则发送commit消息,让所有的Follower提交事务。否则,发送消息让所有的Follwer回滚事务
NOTE: 在这个过程中,如果有新的Follower加入,则Leader把最新的Epoch,以及最新的历史提交记录发送给Follower,方便同步.
5. 总结
从上面的协议过程看来,整个ZooKeeper只要保持有一半以上的机器保持正常运行,那么整个集群的功能还是正常的,不会崩溃.而这得益于一伪2pc的过程,只要一半以上的投票,就可以做出决定.
参考文章:
You Cannot Have Exactly-Once Delivery
ZooKeeper’s Atomic Broadcast Protocol: Theory and Practice
Architecture of ZAB – ZooKeeper Atomic Broadcast protocol
ZooKeeper’s atomic broadcast protocol:Theory and practice