zookeeper是分布式协调系统,用来协调、同步多服务器之间的状态,容错能力强

一个应用要保证HA,往往需要N个服务器(N>1)提供服务,其中有M台master,N-M台slave。这样一台挂了,另外N-1台也能提供服务。所以,数据也会备份成N份散布在这些服务器上。现在的问题变成了,如何管理这N台服务器?如何在master节点失败的时候重新选择master?如何保证所有服务器存储的备份数据一致?

如果你碰到上面那些棘手的问题,zookeeper刚好可以帮到你:

  • 存储公共配置信息

  • 跟踪所有进程运行状态

  • 集群中各节点的管理

  • master选主

  • 提供分布式同步支持(分布式锁等功能)

上面列出的是官方提供的功能支持,还有很多其他功能等待挖掘。

当你的系统依赖zookeeper保证HA和一致性的时候,你肯定也好奇,zookeeper本身是如何保证这两个特性的。幕后功臣往往容易被忽视,其实它就是Zookeeper原子广播协议(Zab协议)。如果你了解过二阶段提交、paxos算法、raft算法等大名鼎鼎的分布式一致性算法,Zab肯定也不会陌生,因为他们要达到的目的相同,就是保证应用程序的HA和一致性。

为了读懂后面的内容,有些术语需要了解:

Zab的理论和实现并不完全一致。

实现是在理论的基础之上做了一些优化手段,这些优化是在zookeeper不断发展的过程中给加上的。我接下来的讲解,也是参照这篇论文,以zookeeper 3.3.3的版本的实现为准,并用自己的语言总结。

理论中的协议

随着系统启动或者恢复,会经历Zab协议中描述的如下四个阶段

  • 阶段0:Leader选举。每个peer从Quorum peer中选出自己心中的预备leader

  • 阶段1:发现。预备leader从Quorum Follower中发现最新的数据,并覆盖自己的过期数据

  • 阶段2:同步。预备leader采用二阶段提交的方式将自己的最新数据同步给Quorum Follower。完成这个步骤,预备leader就转为正式leader

  • 阶段3:广播。Leader接受写请求,并通过二阶段提交的方式广播给Quorum Follower

之前我只是简述了一下理论中的协议,然而理想很骨感,有很多需要改进或者妥协的地方。下面我会一一阐明:

  • 阶段0的选举leader实际上很简陋,只是“看对眼”了就选为预备leader,所以预备leader的数据可能并非最新

  • 预备leader数据过期,就需要用阶段1来弥补,通过互相传输数据,来发现最新的数据,并完成预备leader的数据升级

  • 更多的网络间数据传输代表了更大的网络开销

协议实现

了解了理想的骨感之后,我们回归现实。

真正apache zookeeper在实现上提出设想:优化leader选举,直接选出最新的Peer作为预备Leader,这样就能将阶段0和阶段1合并,减少网络上的开销和多流程的复杂性


由图可知,代码实现上,协议被简化为三个阶段

  • 快速选举Leader阶段:从Quorum Peer中选出数据最新的peer作为leader

  • 恢复阶段:Leader将数据同步给Quorum Follower

  • 广播阶段:Leader接受写请求,并广播给Quorum Follower

Talk is cheap.Show me the Code

这时Linus说过的一句话,无论语言多么有力,只有代码才能真正展现作者的思想。之前我只是对是协议实现的三个阶段做了一番简述,只有代码才能真正拨开Zab协议外面那层迷雾。(为了不浪费篇幅,这里采用伪代码)


快速选举Leader阶段(FLE)



首先初始化一些数据

  • ReceivedVotes:投票箱,存储投票节点以及当前投票信息

  • OutOfElection:存储已经成为Leader、Follower,不参与投票的节点信息,以及它的历史投票信息

  • 在选票中写上自己的大名

  • send notification:发起选票通知,该节点会携带选票,进入目标节点的队列中,相当于给自己投了一票,并毛遂自荐给其他人

如果当前节点处于选举状态,则它也会接到选票通知。它会从队列中不断轮询节点,以便获取选票信息(如果超时,则不断放松超时时间,直至上限)。根据轮询出来的发送节点的状态,来做相应的处理。

  • election:如果发送节点的轮次(round)大于自己,说明自己的选举信息过时,则更新自己的选举轮次,清空投票箱,更新自己的选票内容,并将新的选票通知给其他节点;如果发送节点的轮次等于自己,并且投票内容比自己的更新,则只需要更新自己的选票,并通知给其他节点就行;如果发送节点的轮次小于自己,说明投票内容过期,没有参考意义,直接忽略。所有未被忽略的选票,都会进入投票箱。最终根据选票箱中的结果,判断当前节点的选票是不是占大多数,如果是就根据当前节点的选票选出Leader

  • leading或following:发送节点轮次等于自己,说明发送节点还参与投票,如果发送节点是Leading或者它的选票在选票箱中占大多数,则直接完成选举;如果发送节点已经完成选举(轮次不同)或者它收集的选票较少,那么它的信息都会存放在OutOfElection中。当节点不断完成选举,OutOfElection中数量逐渐变成Quorum时,就把OutOfElection当做投票箱,从中检查发送节点的选票是否占多数,如果是就直接选出Leader


恢复阶段



经过FLE,已经选出了日志中具有最新已提交的事务的节点作为预备Leader。下面就分Leader和Follower两个视角来介绍具体实现。

Leader视角

首先,更新lastZxid,将纪元+1,计数清零,宣布改朝换代啦。然后在每次接收到Follower的数据同步请求时,都会将自己lastZxid反馈回去,表示所有Follower以自己的lastZxid为准。接下来,根据具体情况来判断该如何将数据同步给Follower

  • 如果Leader的历史提交事务比Follower的最新事务要新,说明Follower的数据有待更新。更新方式取决于,Leader最早事务有没有比Follower最新事务要新:如果前者更新,说明在Leader看来Follower所有记录的事务都太过陈旧,没有保留价值,这时只需要将Leader所有history发给Follower就行(响应SNAP);如果后者更新,说明在Leader看来,Follower从自己的lastZxid开始到Leader日志的最新事务,都需要同步,于是将这一部分截取并发送给Follower(响应DIFF)

  • 如果Leader的历史提交事务没有Follower的最新事务新,说明Follower存在没有提交的事务,这些事务都应该被丢弃(响应TRUNC)

当Follower完成同步时,会发送同步ack,当Leader收到Quorum ack时,表示数据同步阶段大功告成,进入最后的广播阶段。

Follower视角

通知Leader,表示自己希望能同步Leader中的数据。

  • 当收到Leader的拒绝响应时,说明Leader不承认自己作为Follower,有可能该Leader并不可靠,于是开始重新开始FLE

  • 当收到SNAP或DIFF响应时,Follower会同步Leader发送过来的事务

  • 当收到TRUNC响应,Follower会丢弃所有未完成的数据

当每个Follower完成上述的同步过程时,会发送ack给Leader,并进入广播阶段。


广播阶段


进入到这个阶段,说明所有数据完成同步,Leader已经转正。开始zookeper最常见的工作流程:广播。

广播阶段是真正接受事务请求(写请求)的阶段,也代表了zookeeper正常工作阶段。所有节点都能接受客户端的写请求,但是Follower会转发给Leader,只有Leader才能将这些请求转化成事务,广播出去。这个节点一样有两个角色,下面还是按照这两个角色来讲解。

Leader视角:

  • Leader必须经过ready,才能接受写请求。完成ready的Leader不断接受写请求,转化成事务请求,广播给Quorum Follower。

  • 当Leader接收到ack时,说明Follower完成相应处理,Leader广播提交请求,Follower完成提交。

  • 当发现新Peer请求作为Follower加入时,将自己的纪元、事务日志发送给该Peer,以便它完成上述恢复阶段的过程。收到该Peer的同步完成的ack时,Leader会发送提交请求,以便Peer提交所有同步完成的事务。这时,该Peer转正为Follower,被Leader纳入Quorum Follower中。

Follower视角:

  • Follower被发现是Leading状态,则执行ready过程,用来接受写请求。

  • 当接受到Leader广播过来的事务请求时,Follower会将事务记录在history,并响应ack。

  • 当接收到Leader广播过来的提交请求时,Follower会检查history中有没有尚未提交的事务,如果有,需要等待之前的事务按照FIFO顺序提交之后,才能提交本事务。

这篇文章没有介绍Zookeeper的使用,而是着重讲解它的核心协议Zab的实现。正如文中提及,Zab最早的设想和现在的实现并不相同,今日的实现是在Zookeeper不断发展壮大的过程中不断优化、改进而来的,也许早期的实现就是yahoo论文中构想的那样。罗马不是一日建成,任何人都不能指望一口吃个大胖子。如果Zookeeper刚开始就想着如何优化到极致,那反而会严重影响到这个项目本身的价值,因为它很可能还没面试就被淘汰。

We should forget about small e ciencies, say about 97% of the time: premature optimization is the root of all evil.

通过本文可以看出,过早优化是万恶之源。但是同时,一个好的程序员也不会忘记需要优化的那部分,他会定位相应的代码,然后针对性的修改。这也是zookeeper的开发者所做的。


©著作权归作者所有:来自51CTO博客作者mob604756f5e525的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. Android(安卓)开机自启动程序
  2. android使用pull解析器来解析和生成xml文件
  3. android与.NET webservice
  4. Android中获取应用程序(包)的信息-----PackageManager的使用(一)
  5. Android中获取应用程序(包)的信息-----PackageManager的使用(一)
  6. Android(安卓)TimeLine 时间节点轴的实现
  7. android unity 文件读写_unity Android(安卓)打包后读取 xml 文
  8. Android(安卓)(shape,gradient)使用总结
  9. Android通过shape.xml制作渐变背景

随机推荐

  1. 我需要帮助完成这个生物信息学计划
  2. python的turtle模块画折线图
  3. python安装及写一个简单的验证码组件(配合
  4. Python 虚拟环境 windows平台 virtualenv
  5. 轮询Web服务的最佳方式(例如,对于Twitter应
  6. 生成使用AES包装的RSA私钥
  7. 整理python dict list set 增删改查
  8. Tensorflow入门程序MNIST学习
  9. numpy无法将值分配给列的一部分
  10. python及其模块下载集合