Zookeeper原理及使用场景

       Zookeeper是Hadoop生态系统中非常重要的组件,它的主要功能是为分布式系统提供一致性协调服务,与之对应的Goole的类似服务叫Chubby。

一、Zookeeper基本原理

1、数据模式(zookeeper-tree)

   如图所示,zookeeper数据模型的结构与unix文件系统很类似,整体上可以看作一树,每个节点称为一个znode。每个znode都可以通过其路径唯一标识,比如上图红第三层的第一个znode,它的路径是/app1/c1。在每个znode上可存储少量数据(默认是1M,可以通过配置修改,通常不建议在znode上存储大量的数据),这个特性非常有用。另外每个znode上还存储了其acl信息。

 

2、重要概念

1)znode

       znode根据其自身的特性,可以分为下面两类:

       Regular znode:常规型znode,用户需要显示的创建、删除

       Ephemeral znode:临时型znode,用户创建它之后,可以显式删除,也可以在创建它的session结束后,由zookeeper server自动删除

       znode还有一个sequential的特性,如果创建的时候指定的话,该znode的名字后面会自动append一个不断增加的sequenceNo。

2)Session

     client与zookeeper之间的通信,需要创建一个session,这个session会有一个超时时间。因为zookeeper集群会吧clien的session的信息持久化,所以session没超时之前,client与zookeeper server的连接可以在各个zookeeper server之间透明地移动。

    在实际的应用中,如果Client与Server之间的通信足够频繁,Session的维护就不需要其它额外的消息了。否则,ZooKeeper Client会每t/3 ms发一次心跳给Server,如果Client 2t/3 ms没收到来自Server的心跳回应,就会换到一个新的ZooKeeper Server上。这里t是用户配置的Session的超时时间。

3 )Watcher 
        ZooKeeper支持一种Watch操作,Client可以在某个ZNode上设置一个Watcher,来Watch该ZNode上的变化。如果该ZNode上有相应的变化,就会触发这个Watcher,把相应的事件通知给设置Watcher的Client。需要注意的是,ZooKeeper中的Watcher是一次性的,即触发一次就会被取消,如果想继续Watch的话,需要客户端重新设置Watcher。这个跟epoll里的oneshot模式有点类似。

3. ZooKeeper特性 

1) 读、写(更新)模式 
      在ZooKeeper集群中,读可以从任意一个ZooKeeper Server读,这一点是保证ZooKeeper比较好的读性能的关键;写的请求会先Follower到Leader,然后由Leader来通过ZooKeeper中的原子广播协议,将请求广播给所有的Follower,Leader收到一半以上的写成功的Ack后,就认为该写成功了,就会将该写进行持久化,并告诉客户端写成功了。

2)WAL和Snapshot 
       和大多数分布式系统一样,ZooKeeper也有WAL(Write-Ahead-Log),对于每一个更新操作,ZooKeeper都会先写WAL, 然后再对内存中的数据做更新,然后向Client通知更新结果。另外,ZooKeeper还会定期将内存中的目录树进行Snapshot,落地到磁盘上,这个跟HDFS中的FSImage是比较类似的。这么做的主要目的,一当然是数据的持久化,二是加快重启之后的恢复速度,如果全部通过Replay WAL的形式恢复的话,会比较慢。

3) FIFO 
        对于每一个ZooKeeper客户端而言,所有的操作都是遵循FIFO顺序的,这一特性是由下面两个基本特性来保证的:一是ZooKeeper Client与Server之间的网络通信是基于TCP,TCP保证了Client/Server之间传输包的顺序;二是ZooKeeper Server执行客户端请求也是严格按照FIFO顺序的。

4) Linearizability 
        在ZooKeeper中,所有的更新操作都有严格的偏序关系,更新操作都是串行执行的,这一点是保证ZooKeeper功能正确性的关键。


二、Zookeeper client API

         下面是一些常用的API:


create(path, data, flags): 创建一个ZNode, path是其路径,data是要存储在该ZNode上的数据,flags常用的有: PERSISTEN, PERSISTENT_SEQUENTAIL, EPHEMERAL, EPHEMERAL_SEQUENTAIL
delete(path, version): 删除一个ZNode,可以通过version删除指定的版本, 如果version是-1的话,表示删除所有的版本
exists(path, watch): 判断指定ZNode是否存在,并设置是否Watch这个ZNode。这里如果要设置Watcher的话,Watcher是在创建ZooKeeper实例时指定的,如果要设置特定的Watcher的话,可以调用另一个重载版本的exists(path, watcher)。以下几个带watch参数的API也都类似
getData(path, watch): 读取指定ZNode上的数据,并设置是否watch这个ZNode
setData(path, watch): 更新指定ZNode的数据,并设置是否Watch这个ZNode
getChildren(path, watch): 获取指定ZNode的所有子ZNode的名字,并设置是否Watch这个ZNode
sync(path): 把所有在sync之前的更新操作都进行同步,达到每个请求都在半数以上的ZooKeeper Server上生效。path参数目前没有用
setAcl(path, acl): 设置指定ZNode的Acl信息
getAcl(path): 获取指定ZNode的Acl信息

三、Zookeeper典型应用场景

1、命名服务(NameService)

        分布式应用中,通常需要一套完备的命令机制,既能产生唯一表示,又方便人识别和记忆。我们知道,每个znode都可以由其路径唯一标识,路径本身也比较简洁直观,另外znode上还可以存储少量数据,这些都是实现统一的nameservice的基础,下面以hdfs中实现nmeservice为例,来说明实现nameservice的基本步骤:

目标:通过简单的名字来访问指定的HDFS机群
定义命名规则:这里要做到简洁易记忆。下面是一种可选的方案: [serviceScheme://][zkCluster]-[clusterName],比如hdfs://lgprc-example/表示基于lgprc ZooKeeper集群的用来做example的HDFS集群
配置DNS映射: 将zkCluster的标识lgprc通过DNS解析到对应的ZooKeeper集群的地址
创建ZNode: 在对应的ZooKeeper上创建/NameService/hdfs/lgprc-example结点,将HDFS的配置文件存储于该结点下
用户程序要访问hdfs://lgprc-example/的HDFS集群,首先通过DNS找到lgprc的ZooKeeper机群的地址,然后在ZooKeeper的/NameService/hdfs/lgprc-example结点中读取到HDFS的配置,进而根据得到的配置,得到HDFS的实际访问入口

2、配置管理(configuration management)

      在分布式系统中,通常遇到这样的场景,某个job的很多实例在运行,他们在运行时大多数配置项是相同的,如果想要统一改某个配置,一个个实例去改,是比较低效,也是比较容易出错的方式,通过zookeeper可以很好的解决这样的问题,下面的基本的步骤:

将公共的配置内容放到ZooKeeper中某个ZNode上,比如/service/common-conf
所有的实例在启动时都会传入ZooKeeper集群的入口地址,并且在运行过程中Watch /service/common-conf这个ZNode
如果集群管理员修改了了common-conf,所有的实例都会被通知到,根据收到的通知更新自己的配置,并继续Watch /service/common-conf

3、组员管理(Group Membership)

     在典型的master-slave结构的分布式系统中,master需要作为‘总管’来管理所有的slave,当有slave加入,或者有slave宕机,maser都需要感知这个事情,然后做出对应的调整,以便不影响整个集群对外提供服务,以Hbase为例,Hmaster管理了所有的regionserver,当有新的regionserver加入的时候,hmaster需要分配一些region到该regionserver,让其提供服务;当有regionserver宕机是,hmaster需要将regionserver之前的服务region都重新分配到当前正在提供的其他的regionserver上,以便不影响客户端的正常访问,下面是这种场景下使用zookeeper的基本步骤:

Master在ZooKeeper上创建/service/slaves结点,并设置对该结点的Watcher
每个Slave在启动成功后,创建唯一标识自己的临时性(Ephemeral)结点/service/slaves/${slave_id},并将自己地址(ip/port)等相关信息写入该结点
Master收到有新子结点加入的通知后,做相应的处理
如果有Slave宕机,由于它所对应的结点是临时性结点,在它的Session超时后,ZooKeeper会自动删除该结点
Master收到有子结点消失的通知,做相应的处理

4、简单互斥锁(simple lock)

     我们知道,在传统的应用程序中,线程、进程同步,都可以通过操作系统提供的机制来完成。但是在分布式系统中,多个进程之间的同步,操作系统层面就无能为力了。这个时候就需要像zookeeper这样的分布式的协调(coordination)服务来协助完成同步,下面使用zookeeper实现简单的互斥锁的步骤,这个可以和线程间同步的mutex做类比来理解:

多个进程尝试去在指定的目录下去创建一个临时性(Ephemeral)结点 /locks/my_lock
ZooKeeper能保证,只会有一个进程成功创建该结点,创建结点成功的进程就是抢到锁的进程,假设该进程为A
其它进程都对/locks/my_lock进行Watch
当A进程不再需要锁,可以显式删除/locks/my_lock释放锁;或者是A进程宕机后Session超时,ZooKeeper系统自动删除/locks/my_lock结点释放锁。此时,其它进程就会收到ZooKeeper的通知,并尝试去创建/locks/my_lock抢锁,如此循环反复

5、读写锁

     我们知道,读写锁跟互斥锁相比不同的地方是,它分为了渡河写两种模式,多个读可以并发执行,但写和读、写都互斥,不能同时执行、。利用zookeeper,在上面的基础上,稍微修改也可以实现传统读写锁的语义,下面是基本的步骤:

每个进程都在ZooKeeper上创建一个临时的顺序结点(Ephemeral Sequential) /locks/lock_${seq}
${seq}最小的一个或多个结点为当前的持锁者,多个是因为多个读可以并发
需要写锁的进程,Watch比它次小的进程对应的结点
需要读锁的进程,Watch比它小的最后一个写进程对应的结点
当前结点释放锁后,所有Watch该结点的进程都会被通知到,他们成为新的持锁者,如此循环反复

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值