一、概述
ZooKeeper(动物园管理员),顾名思义,是用来管理Hadoop(大象)、Hive(蜜蜂)、Pig(小猪)的管理员,同时Apache Hbase、Apache Solr、LinkedIn Sensei等众多项目中都采用了ZooKeeper。
ZooKeeper曾是hadoop的正式子项目,后发展成为Apache顶级项目,与Hadoop密切相关但却没有任何依赖。它是一个针对大型应用提供高可用的数据管理、应用程序协调服务的分布式服务框架,基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,提供的功能包括:配置维护、统一命名服务、状态同步服务、集群管理等。
在分布式应用中,由于工程师不能很好地使用锁机制,以及基于消息的协调机制不适合在某些应用中使用,因此需要有一种可靠的、可扩展的、分布式的、可配置的协调机制来统一系统的状态。Zookeeper的目的就在于此。
二、特征
ZooKeeper是简单的:ZooKeeper的核心是一个精简文件系统,它提供一些简单的操作和一些额外的抽象操作,例如:排序和通知。
ZooKeeper是富有表现力的:ZooKeeper的原语操作是一组构件(building block),可用于实现很多协调数据结构和协议,例如:分布式队列、分布式锁和一组同级节点中的leader选举(leader election)。
ZooKeeper具有高可用性:ZooKeeper运行在集群上,被设计成具有较高的可用性,因此应用程序可以完全依赖它。ZooKeeper可以帮助系统避免出现单点故障,从而构建一个可靠的应用。
ZooKeeper采用松耦合交互方式:在ZooKeeper支持的交互过程中,参与者之间不需要彼此了解。例如:ZooKeeper可以被用作一个rendezvous机制,让进程在不了解其他进程或网络状况的情况下能够彼此发现并进行交互。参与协调的各方甚至可以不必同时存在,因为一个进程在ZooKeeper中留下一条消息,在该进程结束后,另外一个进程还可以读取这条消息。
ZooKeeper是一个资源库:ZooKeeper提供了一个关于通用协调模式实现和方法的开源共享存储库,能使程序员免于编写这类通用的协议。所有人都能够对这个资源库进行添加和改进,随着时间的推移,会使每个人都从中收益。
ZooKeeper是高性能的:在它的诞生地Yahoo!公司,对于以写为主的工作负载来说,ZooKeeper的基准吞吐量已超过每秒10000个操作;对于常规的以读为主的工作负载来说,吞吐量更是高出好几倍。
三、ZooKeeper的数据模型
理解ZooKeeper的一种方法是将其看做一个具有高可用性的文件系统。但这个文件系统中没有文件和目录,而是统一使用节点(node)的概念,称为znode。znode既可以保存数据(如同文件),也可以保存其他znode(如同目录)。所有的znode构成一个层次化的数据结构。
Persistent Nodes:永久有效地节点,除非client显式的删除,否则一直存在
Ephemeral Nodes:临时节点,仅在创建该节点client保持连接期间有效,一旦连接丢失,zookeeper会自动删除该节点
Sequence Nodes:顺序节点,client申请创建该节点时,zk会自动在节点路径末尾添加递增序号,这种类型是实现分布式锁,分布式queue等特殊功能的关键
ZooKeeper是Hadoop的正式子项目,与Hadoop密切相关但却没有任何依赖。它是一个针对大型应用提供高可用的数据管理、应用程序协调服务的分布式服务框架,基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,提供的功能包括:配置维护、统一命名服务、状态同步服务、集群管理等。
四、ZooKeeper典型使用场景
4.1 数据发布与订阅
4.1.1 特性、用法
发布与订阅即所谓的配置管理,顾名思义就是将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如:全局的配置信息、地址列表等。
4.1.2 具体用法
索引信息和集群中机器节点状态放在zk的一些指定节点,供各个客户端订阅使用。
系统日志(经处理后)存储,这些日志通常2-3天后清除。
应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用,获取最新的配置信息。
业务逻辑中需要用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度。
系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露出接口,例如JMX接口,有了zk后,只要将这些信息存放到zk节点上即可。
4.2 命名服务
4.2.1 特性、用法
这个主要是作为分布式命名服务,通过调用zk的create node api,能够很容易创建一个全局唯一的path,可以将这个path作为一个名称。
4.3 分布通知、协调
4.3.1 特性、用法
ZooKeeper中特有的watcher注册于异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对zk上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点内容),其中一个系统update了znode,那么另一个系统能够收到通知,并做出相应处理。
使用ZooKeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
4.3.2 具体用法
心跳检测机制:检测系统和被测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台做的一些操作,实际上是修改了zk上某些节点的状态,而zk就把这些变化通知给它们注册watcher的客户端,即推送系统,于是做出相应的推送任务。
工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时指导任务进度。
4.4 分布式锁
4.4.1 特性、用法
分布式锁,主要得益于ZooKeeper保证数据的强一致性,即zk集群中任意节点(一个zk server)上系统znoe的数据一定相同。
锁服务可以分为两类:
保持独占锁:所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看做是一把锁,通过create znode的方式来实现。所有客户端都去创建/distribute_lock节点,最终成功创建的那个客户端也即拥有了这把锁。
控制时序锁:所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。与保持独占锁的做法类似,不同点是/distribute_lock已经预先存在,客户端在它下面创建临时有序节点(可以通过节点控制属性控制:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。zk的父节点(/distribute_lock)维持一份sequence,保证子节点创建的时序性,从而形成每个客户端的全局时序。
4.5 集群管理
4.5.1 特性、用法
- 集群机器监控:这通常用于那种对集群中机器状态、机器在线率有较高要求的场景,能够快速对集群中机器变化做出响应。这样的场景中,往往有一个监控系统,实时监测集群机器是否存活。过去的做法通常是:监控系统通过某种手段(比如ping)定时检测每个机器、或每个机器定时向监控系统发送心跳信息。这种做法存在两个弊端:1.集群中机器有变动的时候,牵连修改的东西比较多。2.有一定的延迟。利用ZooKeeper,可以实现另一种集群机器存活性监控系统:a.客户端在节点x上注册watcher,如果x的子节点发生变化,会通知该客户端。b.创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,该节点就会消失。例如:监控系统在/clusterServers节点上注册一个watcher,以后每动态加机器,就往/culsterServer下创建一个EPHEMERAL类型的节点:/clusterServer/{hostname}。这样,监控系统就能实时知道机器的增减情况,至于后续处理就是监控系统的业务了。
- Master选举:在分布式环境中,相同的业务应用分布在不同的机器上,有些业务逻辑(例如一些耗时的计算、网络I/O处理),往往需要让整个集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以减少重复劳动、提高性能。利用ZooKeeper的强一致性,能够保证在分布式高并发情况下节点创建的全局唯一性,即:同时有多个客户端请求创建/currentMaster节点,最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很轻易的在分布式环境中进行集群选取了。另外,这种场景演化一下,就是动态Master选举。这就要用到 EPHEMERAL_SEQUENTIAL类型节点的特性了。上文中提到,所有客户端创建请求,最终只有一个能够创建成功。在这里稍微变化下,就是允许所有请求都能够创建成功,但是得有个创建顺序,于是所有的请求最终在zk上创建结果的一种可能情况是这样: /currentMaster/{sessionId}-1、/currentMaster/{sessionId}-2、/currentMaster/{sessionId}-3……。每次选取序列号最小的那个机器作为Master,如果这个机器挂了,由于他创建的节点会马上消失,那么之后最小的那个机器就是Master了。
4.5.2 具体用法
在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成,然后同步到集群中其它机器。
Master选举的容灾措施是,可以随时进行手动指定master,就是说应用在zk在无法获取master信息时,可以通过比如http方式,向一个地方获取master。
4.6 分布式队列
4.6.1 特性、用法
队列方面,有两种方式:一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。
对于先进先出队列,和分布式锁服务中的控制时序场景基本原理一致,这里不再赘述。
第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在/queue这个znode下预先建立一个/queue/num节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进行。这个时候,凡是其中一个子任务完成(就绪),那么就去/taskList下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当/taskList发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。