一个ZK集群称为“ZooKeeper ensemble”。
-
数据模型
每个节点在zookeeper中叫做znode,并且其有一个唯一的路径标识,如/p_3节点的标识就为/app1/p_3。
Znode可以有子znode,并且znode里可以存数据,但是EPHEMERAL类型的节点不能有子节点。
Znode中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据就需要带上版本。
Znode 可以是临时节点,一旦创建这个znode的客户端与服务器失去联系,这个znode也将自动删除,Zookeeper的客户端和服务器通信采用长连接方式,每个客户端和服务器通过心跳来保持连接,这个连接状态称为session,如果znode是临时节点,这个session失效,znode也就删除了。
Znode的目录名可以自动编号,如App1已经存在,再创建的话,将会自动命名为App2。
Znode可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的集中管理,集群管理,分布式锁等等。
-
特点
顺序一致性:按照客户端发送请求的顺序更新数据。
原子性:更新要么成功,要么失败,不会出现部分更新。
单一性 :无论客户端连接哪个server,都会看到同一个视图。
可靠性:一旦数据更新成功,将一直保持,直到新的更新。
实时性:客户端会在一个确定的时间内得到最新的数据。
注意:Zookeeper提供的一致性是弱一致性,首先数据的复制有如下规则:ZooKeeper确保对znode树的每一个修改都会被复制到集合体中超过半数的机器上。那么就有可能有节点的数据不是最新的而被客户端访问到。并且会有一个时间点,在集群中是不一致的。
也就是Zookeeper只保证最终一致性(数据可以在十几秒Sync到各个节点),但是实时的一致性可以由客户端调用自己来保证,通过调用sync()方法。
-
应用场景
1、命名服务(Naming Service)
命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见 的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。阿里巴巴集团开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表。在Dubbo实现中:服务提供者在启动 的时候,向ZK上的指定节点/dubbo/${serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。服务消费者启动的时候,订阅/dubbo/${serviceName}/providers目录下的提供者URL地址, 并向/dubbo/${serviceName} /consumers目录下写入自己的URL地址。注意,所有 向ZK上注册的地址都是临时节点,这样就能够保证服务提供者和消费者能够自动感应资源的变化。 另外,Dubbo还有针对服务粒度的监控,方法是订阅/dubbo/${serviceName}目录下所有提供者和消费者的信息。
2、数据发布与订阅(配置中心)
ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。使用方法通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系
统能够收到通知,并作出相应处理。心跳检测机制:检测系统和被检测系统之间并不直接关联起来,而是通过zk上某个节点关联,大大减少系统耦合。
系统调度模式:某系统有控制台和推送系统两部分组成,控制台的职责是控制推送系统进行相应的推送工作。管理人员在控制台作的一些操作,实际上是修改了ZK上某些节点的状态,而ZK就把这些变化通知给他们注册Watcher的客户端,即推送系统,于是,作出相应的推送任务。
工作汇报模式:一些类似于任务分发系统,子任务启动后,到zk来注册一个临时节点,并且定时将自己的进度进行汇报(将进度写回这个临时节点),这样任务管理者就能够实时知道任务进度。
使用zookeeper来进行分布式通知和协调能够大大降低系统之间的耦合。
注意:Watcher是一次性的,触发之后如果想继续关注需要重新设置监听。
3、集群管理
可以构建集群机器存活性监控系统。
4、Master选举
在搜索系统中,如果集群中每个机器都生成一份全量索引,不仅耗时,而且不能保证彼此之间索引数据一致。因此让集群中的Master来进行全量索引的生成,然后同步到集群中其它机器。
解决单点失效。
5、 分布式锁
分布式锁,这个主要得益于ZooKeeper为我们保证了数据的一致性。锁服务可以分为两类,一个是 保持独占,另一个是 控制时序。
所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。
控制时序,就是所有视图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序了。做法和上面基本类似,只是这里 /distribute_lock 已经预先存在,客户端在它下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL来指定)。如果我们在指
定的路径上创建顺序节点,则Zookeeper会自动的在我们给定的path上加上一个顺序编号。这个特性就是实现分布式锁的关键。假设我们有几个节点共享一个资源,我们这几个节点都想争用这个资源,那我们就都向某个路径创建临时顺序节点。然后顺序最小的那个就获得锁,然后如果某个节点释放
了锁,那顺序第二小的那个就获得锁,以此类推,这样一个分布式的公平锁就实现了。
6、分布式队列
队列方面,简单地讲有两种,一种是常规的先进先出队列,另一种是要等到队列成员聚齐之后的才统一按序执行。对于第一种先进先出队列,和分布式锁服务中的控制时序场景基本原理一致,这里不再赘述。 第二种队列其实是在FIFO队列的基础上作了一个增强。通常可以在 /queue 这个znode下预先建立一个/queue/num 节点,并且赋值为n(或者直接给/queue赋值n),表示队列大小,之后每次有队列成员加入后,就判断下是否已经到达队列大小,决定是否可以开始执行了。这种用法的典型场景是,分布式环境中,一个大任务Task A,需要在很多子任务完成(或条件就绪)情况下才能进 行。这个时候,凡是其中一个子任务完成(就绪),那么就去 /taskList 下建立自己的临时时序节点(CreateMode.EPHEMERAL_SEQUENTIAL),当 /taskList 发现自己下面的子节点满足指定个数,就可以进行下一步按序进行处理了。
7、负载均衡这里说的负载均衡是指软负载均衡。在分布式环境中,为了保证高可用性,通常同一个应用或同一个服务的提供方都会部署多份,达到对等服务。而消费者就须要在这些对等的服务器中选择一个来执行相关的业务逻辑,其中比较典型的是消息中间件中的生产者,消费者负载均衡。
消息中间件中发布者和订阅者的负载均衡,linkedin开源的KafkaMQ和阿里开源的 metaq都是通过zookeeper来做到生产者、消费者的负载均衡。这里以metaq为例如讲下:
生产者负载均衡:metaq发送消息的时候,生产者在发送消息的时候必须选择一台broker上的一个分区来发送消息,因此metaq在运行过程中,会把所有broker和对应的分区信息全部注册到ZK指定节点上,默认的策略是一个依次轮询的过程,生产者在通过ZK获取分区列表之后,会按照brokerId和
partition的顺序排列组织成一个有序的分区列表,发送的时候按照从头到尾循环往复的方式选择一个分区来发送消息。
-
安装配置
安装略(分布式,伪集群)。 启动时需要至少启动一半以上机器才行,否则会一直抛错。
配置说明:
tickTime=2000
initLimit=5
syncLimit=2
dataDir=/Users/apple/zookeeper/data
dataLogDir=/Users/apple/zookeeper/logs
clientPort=2181
server.0=127.0.0.1:8880:7770
server.1=127.0.0.1:8881:7771
server.2=127.0.0.1:8882:7772
tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。
initLimit::zookeeper集群中的包含多台server,,其中一台为leader,,集群中其余的server为follower.。initLimit参数配置初始化连接时, follower和leader之间的最长心跳时间.。此时该参数设置为5, 说明时间限制为5倍tickTime, 即5*2000=10000ms=10s。
syncLimit::该参数配置leader和follower之间发送消息, 请求和应答的最大时间长度.。此时该参数设置为2,说明时间限制为2倍tickTime,即4000ms。
server.X=A:B:C 其中X是一个数字,表示这是第几号server。A是该server所在的IP地址。B配置该server和集群中的leader交换消息所使用的端口。 C配置选举leader时所使用的端口。 如果配置的是伪集群模式,则各个server的B、C参数必须不同。
dataDir:就是把内存中的数据存储成快照文件snapshot的目录,同时myid也存储在这个目录下(myid中的内容为本机server服务的标识)。写快照不需要单独的磁盘,而且是使用后台线程进行异步写数据到磁盘,因此不会对内存数据有影响。默认情况下,事务日志也会存储在这里。建议同时配置参数dataLogDir,事务日志的写性能直接影响zk性能。
dataLogDir:事务日志输出目录。尽量给事务日志的输出配置单独的磁盘或是挂载点,这将极大的提升ZK性能。 由于事务日志输出时是顺序且同步写到磁盘,只有从磁盘写完日志后才会触发follower和leader发回事务日志确认消息(zk事务采用两阶段提交),因此需要单独磁盘避免随机读写和磁盘缓存导致事务日志写入较慢或存储在缓存中没有写入。
clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。
minSessionTimeout~maxSessionTimeout:Session超时时间限制,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间。
注意:Session超时时间由服务器和ZK客户端共同决定,建议最大超时时间不要太短,以避免频繁连接超时情况(HBase使用的3分钟)。
-
客户端工具
Apache Curator: Curator是Netflix开源的一套ZooKeeper客户端框架。Netflix在使用ZooKeeper的过程中发现ZooKeeper自带的客户端太底层,应用方在使用的时候需要自己处理很多事情,于是在它的基础上包装了一下, 提供了一套更好用的客户端框架。
Curator几个组成部分 :
1、Client: 是ZooKeeper客户端的一个替代品, 提供了一些底层处理和相关的工具方法
2、Framework: 用来简化ZooKeeper高级功能的使用, 并增加了一些新的功能, 比如管理到ZooKeeper集群的连接,重试处理
3、Recipes: 实现了通用ZooKeeper的recipe,该组件建立在Framework的基础之上
Curator实现ZooKeeper的所有recipe(除了两段提交) ,比如领导者选举、锁、Barrier等等
4、Utilities:各种ZooKeeper的工具类
5、Errors:异常处理,连接, 恢复等
6、Extensions: recipe扩展
101tec:I0Itec-zkClient并不是一个非常大众化的工具,也没有提供完美的功能列表,不过它的简单和已用对我们的一些常规应用,似乎已经足够了。I0Itec特性一览:
1、提供了zookeeper断链重连的特性:这个特性似乎每个开发者都会设计,而且代码风格几乎"如出一辙",在大部分zookeeper使用场景中,我们都要求它能够在断链的时候,重新建立连接,无论session失效与否。
2、便捷的event监听器机制:向znode节点注册watch,每个开发者都使用过,尽管watch机制并不能确保数据变更的实时性,watch-event属于“即发即失”,因为我们需要得到event时候,再去注册一遍,这也是一个非常繁琐的事情,I0Itec- zkClient提供了event-listener的小技巧,可以帮助我们解 脱。
3、zookeeper异常处理:ZooKeeper中繁多的Exception,以及每个Exception所需要关注的事情各有不同,你应该记得那一堆try-catch给你带来的烦恼,I0Itec简单的做了封装。
4、data序列化:简单的data序列化(Serialzer/Deserialzer)
-
可视化工具
淘宝ZooKeeper监控工具taokeeper ZooKeeper for eclipse插件 ZooInspector
-
集群扩展
高可用性是指一半以上机器可用时服务可用,比如5台ZK服务器组成的集群3台可用时ZK服务可用。建议配置奇数台ZK服务器。 节点多了Leader选举非常耗时,投票压力增大,吞吐量下降,可以通过引入observer节点解决这个问题。
-
性能
Zookeeper 被设计为高性能。但实际是否如此呢?在雅虎研发中心的 Zookeeper 开发团队的研究结果表明的确如此。(参见下图:Zookeeper 吞吐量随读写比的变化)。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致在所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
-
ZooKeeper不适合做什么
不能做存储使用,单个节点最大存储4M。 -