ZooKeeper是Hadoop Ecosystem中很是重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的相似服务叫Chubby。今天这篇文章分为三个部分来介绍ZooKeeper,第一部分介绍ZooKeeper的基本原理,第二部分介绍ZooKeeper提供的Client API的使用,第三部分介绍一些ZooKeeper典型的应用场景。网络
1. 数据模型
如上图所示,ZooKeeper数据模型的结构与Unix文件系统很相似,总体上能够看做是一棵树,每一个节点称作一个ZNode。每一个ZNode均可以经过其路径惟一标识,好比上图中第三层的第一个ZNode, 它的路径是/app1/c1。在每一个ZNode上可存储少许数据(默认是1M, 能够经过配置修改, 一般不建议在ZNode上存储大量的数据),这个特性很是有用,在后面的典型应用场景中会介绍到。另外,每一个ZNode上还存储了其Acl信息,这里须要注意,虽然说ZNode的树形结构跟Unix文件系统很相似,可是其Acl与Unix文件系统是彻底不一样的,每一个ZNode的Acl的独立的,子结点不会继承父结点的,关于ZooKeeper中的Acl能够参考以前写过的一篇文章《说说Zookeeper中的ACL》。并发
2.重要概念
2.1 ZNode
前文已介绍了ZNode, ZNode根据其自己的特性,能够分为下面两类:app
ZNode还有一个Sequential的特性,若是建立的时候指定的话,该ZNode的名字后面会自动Append一个不断增长的SequenceNo。分布式
2.2 Session
Client与ZooKeeper之间的通讯,须要建立一个Session,这个Session会有一个超时时间。由于ZooKeeper集群会把Client的Session信息持久化,因此在Session没超时以前,Client与ZooKeeper Server的链接能够在各个ZooKeeper Server之间透明地移动。oop
在实际的应用中,若是Client与Server之间的通讯足够频繁,Session的维护就不须要其它额外的消息了。不然,ZooKeeper Client会每t/3 ms发一次心跳给Server,若是Client 2t/3 ms没收到来自Server的心跳回应,就会换到一个新的ZooKeeper Server上。这里t是用户配置的Session的超时时间。性能
2.3 Watcher
ZooKeeper支持一种Watch操做,Client能够在某个ZNode上设置一个Watcher,来Watch该ZNode上的变化。若是该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置Watcher的Client。须要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,若是想继续Watch的话,须要客户端从新设置Watcher。这个跟epoll里的oneshot模式有点相似。spa
3. ZooKeeper特性
3.1 读、写(更新)模式
在ZooKeeper集群中,读能够从任意一个ZooKeeper Server读,这一点是保证ZooKeeper比较好的读性能的关键;写的请求会先Forwarder到Leader,而后由Leader来经过ZooKeeper中的原子广播协议,将请求广播给全部的Follower,Leader收到一半以上的写成功的Ack后,就认为该写成功了,就会将该写进行持久化,并告诉客户端写成功了。操作系统
3.2 WAL和Snapshot
和大多数分布式系统同样,ZooKeeper也有WAL(Write-Ahead-Log),对于每个更新操做,ZooKeeper都会先写WAL, 而后再对内存中的数据作更新,而后向Client通知更新结果。另外,ZooKeeper还会按期将内存中的目录树进行Snapshot,落地到磁盘上,这个跟HDFS中的FSImage是比较相似的。这么作的主要目的,一固然是数据的持久化,二是加快重启以后的恢复速度,若是所有经过Replay WAL的形式恢复的话,会比较慢。线程
3.3 FIFO
对于每个ZooKeeper客户端而言,全部的操做都是遵循FIFO顺序的,这一特性是由下面两个基本特性来保证的:一是ZooKeeper Client与Server之间的网络通讯是基于TCP,TCP保证了Client/Server之间传输包的顺序;二是ZooKeeper Server执行客户端请求也是严格按照FIFO顺序的。blog
3.4 Linearizability
在ZooKeeper中,全部的更新操做都有严格的偏序关系,更新操做都是串行执行的,这一点是保证ZooKeeper功能正确性的关键。
ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些经常使用的API:
1. 名字服务(NameService)
分布式应用中,一般须要一套完备的命令机制,既能产生惟一的标识,又方便人识别和记忆。 咱们知道,每一个ZNode均可以由其路径惟一标识,路径自己也比较简洁直观,另外ZNode上还能够存储少许数据,这些都是实现统一的NameService的基础。下面以在HDFS中实现NameService为例,来讲明实现NameService的基本布骤:
2. 配置管理(Configuration Management)
在分布式系统中,常会遇到这样的场景: 某个Job的不少个实例在运行,它们在运行时大多数配置项是相同的,若是想要统一改某个配置,一个个实例去改,是比较低效,也是比较容易出错的方式。经过ZooKeeper能够很好的解决这样的问题,下面的基本的步骤:
3. 组员管理(Group Membership)
在典型的Master-Slave结构的分布式系统中,Master须要做为“总管”来管理全部的Slave, 当有Slave加入,或者有Slave宕机,Master都须要感知到这个事情,而后做出对应的调整,以便不影响整个集群对外提供服务。以HBase为例,HMaster管理了全部的RegionServer,当有新的RegionServer加入的时候,HMaster须要分配一些Region到该RegionServer上去,让其提供服务;当有RegionServer宕机时,HMaster须要将该RegionServer以前服务的Region都从新分配到当前正在提供服务的其它RegionServer上,以便不影响客户端的正常访问。下面是这种场景下使用ZooKeeper的基本步骤:
4. 简单互斥锁(Simple Lock)
咱们知识,在传统的应用程序中,线程、进程的同步,均可以经过操做系统提供的机制来完成。可是在分布式系统中,多个进程之间的同步,操做系统层面就无能为力了。这时候就须要像ZooKeeper这样的分布式的协调(Coordination)服务来协助完成同步,下面是用ZooKeeper实现简单的互斥锁的步骤,这个能够和线程间同步的mutex作类比来理解:
5. 互斥锁(Simple Lock without Herd Effect)
上一节的例子中有一个问题,每次抢锁都会有大量的进程去竞争,会形成羊群效应(Herd Effect),为了解决这个问题,咱们能够经过下面的步骤来改进上述过程:
这里须要补充一点,一般在分布式系统中用ZooKeeper来作Leader Election(选主)就是经过上面的机制来实现的,这里的持锁者就是当前的“主”。
6. 读写锁(Read/Write Lock)
咱们知道,读写锁跟互斥锁相比不一样的地方是,它分红了读和写两种模式,多个读能够并发执行,但写和读、写都互斥,不能同时执行行。利用ZooKeeper,在上面的基础上,稍作修改也能够实现传统的读写锁的语义,下面是基本的步骤:
7. 屏障(Barrier)
在分布式系统中,屏障是这样一种语义: 客户端须要等待多个进程完成各自的任务,而后才能继续往前进行下一步。下用是用ZooKeeper来实现屏障的基本步骤:
8. 双屏障(Double Barrier)
双屏障是这样一种语义: 它能够用来同步一个任务的开始和结束,当有足够多的进程进入屏障后,才开始执行任务;当全部的进程都执行完各自的任务后,屏障才撤销。下面是用ZooKeeper来实现双屏障的基本步骤: