ZooKeeper的学习

来源:互联网 发布:药品查询软件下载 编辑:程序博客网 时间:2024/06/07 00:18

1、背景介绍

1.1、认识zookeeper

    ZooKeeper原意为动物园管理员。游客可以根据动物园提供的向导图到不同的场馆观赏各种类型的动物,而不是像走在原始丛林里,心惊胆颤的被动物所观赏。为了让各种不同的动物呆在它们应该呆的地方,而不是相互串门,或是相互厮杀,就需要动物园管理员按照动物的各种习性加以分类和管理,这样我们才能更加放心安全的观赏动物。    回到企业级应用系统中,随着信息化水平的不断提高,企业级系统变得越来越庞大臃肿,性能急剧下降,客户抱怨频频。拆分系统是目前我们可选择的解决系统可伸缩性和性能问题的唯一行之有效的方法。但是拆分系统同时也带来了系统的复杂性——各子系统不是孤立存在的,它们彼此之间需要协作和交互,这就是我们常说的分布式系统。各个子系统就好比动物园里的动物,为了使各个子系统能正常为用户提供统一的服务,必须需要一种机制来进行协调——这就是ZooKeeper(动物园管理员)。

1.2、为什么使用zookeeper?

    由于局部故障的原因,写一个分布式应用是非常困难的。一个消息通过网络在两个节点之间传递时,网络如果发生故障,发送方并不知道接收方是否接收到了这个消息。它可能在网络故障迁就收到了此消息,也可能没有收到,又或者可能接收方的进程死了。发送方了解情况的唯一方法就是再次连接接收方,并向它进行询问。这就是局部故障:根本不知道操作是否失败。因此,大部分分布式应用需要一个主控、协调控制器来管理物理分布的子进程。目前,大部分应用需要开发私有的协调程序,缺乏一个通用的机制。协调程序的反复编写浪费,且难以形成通用、伸缩性好的协调器。协调服务非常容易出错,并很难从故障中恢复。例如:协调服务很容易处于竞态、甚至死锁。    Zookeeper的设计目的,是为了减轻分布式应用程序所承担的协调任务。Zookeeper并不能阻止局部故障的发生,因为它们的本质是分布式系统。它当然也不会隐藏局部故障。ZooKeeper的目的就是提供一些工具集,用来建立安全处理局部故障的分布式应用。    ZooKeeper是一个分布式小文件系统,并且被设计为高可用性。通过选举算法和集群复制可以避免单点故障。由于是文件系统,所以即使所有的ZooKeeper节点全部挂掉,数据也不会丢失,重启服务器之后,数据即可恢复。另外ZooKeeper的节点更新是原子的,也就是说更新不是成功就是失败。通过版本号,ZooKeeper实现了更新的乐观锁。当版本号不相符时,则表示待更新的节点已经被其他客户端提前更新了,而当前的整个更新操作将全部失败。当然所有的一切ZooKeeper已经为开发者提供了保障,我们需要做的只是调用API。与此同时,随着分布式应用的的不断深入,需要对集群管理逐步透明化监控集群和作业状态,可以充分利ZK的独有特性。

1.3、zk的应用

    ZooKeeper本质上是一个分布式的小文件存储系统。原本是Apache Hadoop的一个组件,现在被拆分为一个hadoop的独立子项目,在Hbase(Hadoop的另外一个被拆分出来的子项目,用于分布式环境下的超大数据量的DBMS)中也用到了ZooKeeper集群。             Hadoop,使用Zookeeper的事件处理确保整个集群只有一个NameNode,存储配置信息等.    Hbase,使用Zookeeper的事件处理确保整个集群只有一个HMaster,察觉HRegionServer联机和宕机,存储访问控制列表等。

2、分布式协调技术

    分布式协调技术 主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成"脏数据"的后果。                            图1-1 分布式系统

图1-1

    在这图中有三台机器,每台机器各跑一个应用程序。然后我们将这三台机器通过网络将其连接起来,构成一个系统来为用户提供服务,对用户来说这个系统的架构是透明的,他感觉不到我这个系统是一个什么样的架构。那么我们就可以把这种系统称作一个分布式系统。    那我们接下来再分析一下,在这个分布式系统中如何对进程进行调度,我假设在第一台机器上挂载了一个资源,然后这三个物理分布的进程都要竞争这个资源,但我们又不希望他们同时进行访问,这时候我们就需要一个协调器,来让它们有序的来访问这个资源。这个协调器就是我们经常提到的那个锁,比如说"进程1"在使用该资源的时候,会先去获得锁,"进程1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。这个分布式锁也就是我们分布式协调技术实现的核心内容,那么如何实现这个分布式呢,那就是我们后面要讲的内容。

3、分布式锁的实现

3.1、面临的问题

    在看了图1-1所示的分布式环境之后,有人可能会感觉这不是很难。无非是将原来在同一台机器上对进程调度的原语,通过网络实现在分布式环境中。是的,表面上是可以这么说。但是问题就在网络这,在分布式系统中,所有在同一台机器上的假设都不存在:因为网络是不可靠的。    比如,在同一台机器上,你对一个服务的调用如果成功,那就是成功,如果调用失败,比如抛出异常那就是调用失败。但是在分布式环境中,由于网络的不可 靠,你对一个服务的调用失败了并不表示一定是失败的,可能是执行成功了,但是响应返回的时候失败了。还有,A和B都去调用C服务,在时间上 A还先调用一些,B后调用,那么最后的结果是不是一定A的请求就先于B到达呢? 这些在同一台机器上的种种假设,我们都要重新思考,我们还要思考这些问题给我们的设计和编码带来了哪些影响。还有,在分布式环境中为了提升可靠性,我们往 往会部署多套服务,但是如何在多套服务中达到一致性,这在同一台机器上多个进程之间的同步相对来说比较容易办到,但在分布式环境中确实一个大难题。    所以分布式协调远比在同一台机器上对多个进程的调度要难得多,而且如果为每一个分布式应用都开发一个独立的协调程序。一方面,协调程序的反复编写浪 费,且难以形成通用、伸缩性好的协调器。另一方面,协调程序开销比较大,会影响系统原有的性能。所以,急需一种高可靠、高可用的通用协调机制来用以协调分 布式应用。

3.2 分布式锁的实现者

    目前,在分布式协调技术方面做得比较好的就是Google的Chubby还有Apache的ZooKeeper他们都是分布式锁的实现者。有人会问 既然有了Chubby为什么还要弄一个ZooKeeper,难道Chubby做得不够好吗?不是这样的,主要是Chbby是非开源的,Google自家 用。后来雅虎模仿Chubby开发出了ZooKeeper,也实现了类似的分布式锁的功能,并且将ZooKeeper作为一种开源的程序捐献给了 Apache,那么这样就可以使用ZooKeeper所提供锁服务。而且在分布式领域久经考验,它的可靠性,可用性都是经过理论和实践的验证的。所以我们 在构建一些分布式系统的时候,就可以以这类系统为起点来构建我们的系统,这将节省不少成本,而且bug也将更少。

4、zookeeper的介绍

4.1、zookeeper的概述

    ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它提供了一项基本服务:分布式锁服务。由于ZooKeeper的开源特性,后来我们的开发者在分布式锁的基础上,摸索了出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。    ZooKeeper性能上的特点决定了它能够用在大型的、分布式的系统当中。从可靠性方面来说,它并不会因为一个节点的错误而崩溃。除此之外,它严格的序列访问控制意味着复杂的控制原语可以应用在客户端上。ZooKeeper在一致性、可用性、容错性的保证,也是ZooKeeper的成功之处,它获得的一切成功都与它采用的协议——Zab协议是密不可分的。    前面提到了那么多的服务,比如分布式锁、配置维护、组服务等,那它们是如何实现的呢,我相信这才是大家关心的东西。ZooKeeper在实现这些服务时,首先它设计一种新的数据结构——Znode,然后在该数据结构的基础上定义了一些原语,也就是一些关于该数据结构的一些操作。有了这些数据结构和原语还不够,因为我们的ZooKeeper是工作在一个分布式的环境下,我们的服务是通过消息以网络的形式发送给我们的分布式应用程序,所以还需要一个通知机制——Watcher机制。那么总结一下,ZooKeeper所提供的服务主要是通过:数据结构+原语+watcher机制,三个部分来实现的。

4.2、zookeeper的设计目标

    分布式环境下的程序和活动为了达到协调一致目的,通常具有某些共同的特点,例如,简单性、有序性等。ZooKeeper不但在这些目标的实现上有自身特点,并且具有独特优势。下面我们将简述ZooKeeper的设计目标。    (1)简单化    ZooKeeper允许各分布式进程通过一个共享的命名空间相互联系,该命名空间类似于一个标准的层次型的文件系统:由若干注册了的数据节点构成(用Zookeeper的术语叫znode),这些节点类似于文件和目录。典型的文件系统是基于存储设备的,文传统的文件系统主要用于存储功能,然而ZooKepper的数据是保存在内存中的。也就是说,可以获得高吞吐和低延迟。ZooKeeper的实现非常重视高性能、高可靠,以及严格的有序访问。    高性能保证了ZooKeeper可以用于大型的分布式系统,高可靠保证了ZooKeeper不会发生单点故障,严格的顺序访问保证了客户端可以获得复杂的同步操作原语。    (2)健壮性    就像ZooKeeper需要协调的分布式系统一样,它本身就是具有冗余结构,它构建在一系列主机之上,叫做一个”ensemble”。    构成ZooKeeper服务的各服务器之间必须相互知道,它们维护着一个状态信息的内存映像,以及在持久化存储中维护着事务日志和快照。只要大部分服务器正常工作,ZooKeeper服务就能正常工作。    客户端连接到一台ZooKeeper服务器。客户端维护这个TCP连接,通过这个连接,客户端可以发送请求、得到应答,得到监视事件以及发送心跳。如果这个连接断了,客户端可以连接到另一个ZooKeeper服务器。    (3)有序性    ZooKeeper给每次更新附加一个数字标签,表明ZooKeeper中的事务顺序,后续操作可以利用这个顺序来完成更高层次的抽象功能,例如同步原语。    (4)速度优势    ZooKeeper特别适合于以读为主要负荷的场合。ZooKeeper可以运行在数千台机器上,如果大部分操作为读,例如读写比例为10:1,ZooKeeper的效率会很高。

4.3、zookeeper的集群

    zk集群如下图3-1所示。这是实际应用的一个场景,该ZooKeeper集群当中一共有5台服务器,有两种角色Leader和Follwer,5台服务器连通在一起,客户端有分别连在不同的ZK服务器上。如果当数据通过客户端1,在左边第一台Follower服务器上做了一次数据变更,它会把这个数据的变化同步到其他所有的服务器,同步结束之后,那么其他的客户端都会获得这个数据的变化。

这里写图片描述

                                图3-1    通常Zookeeper由2n+1台服务器组成,每个服务器都知道彼此的存在。每个服务器都维护的内存状态镜像以及持久化存储的事务日志和快照。为了保证Leader选举能过得到多数的支持,所以ZooKeeper集群的数量一般为奇数。对于2n+1台服务器,只要有n+1台(大多数)服务器可用,整个系统保持可用。

4.3.1、集群中的角色

    在zk集群当中,集群中的服务器角色有两种:Leader和Learner。其中,Learner角色又分为Observer和Follower,具体功能如下:    1、领导者(leader),负责进行投票的发起和决议,更新系统状态;    2、学习者(learner),包括跟随者(follower)和观察者(observer);    3、follower用于接受客户端请求并向客户端返回结果,在选主过程中参与投票;    4、Observer可以接受客户端请求,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度;    5、客户端(client),请求发起方;

4.3.2、Zookeeper的读写机制和保证及特点

(1)ZooKeeper的读写机制        Zookeeper是一个由多个服务器组成的集群;        只有一个leader,有多个follower;        每个server保存一份数据副本;        全局数据一致;        分布式读写;        更新请求转发,由leader实施;(2)ZooKeeper的保证    ZooKeeper运行非常快而且简单。虽然它的目标是构建更加复杂服务(例如同步)的基础,但它提供了一些保证,如下:    1、顺序一致性:来自于客户端的更新,根据发送的先后被顺序实施。    2、唯一的系统映像:尽管客户端连接到不同的服务器,但它们看到的一个唯一(一致性)的系统服务,客户端无论连接到哪个server,数据视图都是一致的。    3、可靠性:一旦实施了一个更新,就会一直保持那种状态,直到客户端再次更新它,同时数据更新原子性,一次数据更新要么成功,要么失败。    4、及时性:在一个确定的时间内,客户端看到的系统状态是最新的。(3)ZooKeeper特点    最终一致性:客户端不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。    可靠性:具有简单、健壮、良好的性能,如果消息m被一台服务器接受,那么它将被所有的服务器接受。    实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。 但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。    等待无关(wait-free):慢的或者失效的client,不得干预快速的client的请求,使得每个client都能有效的等待。    原子性:更新只能成功或者失败,没有中间状态。    顺序性:包括全局有序和偏序两种:    全局有序:是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;    偏序:是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面

5、zookeeper的数据模型

ZooKeeper拥有一个层次的命名空间,这个和标准的文件系统非常相似,如下图4-1 所示。

这里写图片描述

            图4-1 ZooKeeper数据模型与文件系统目录树

5.1、znode

    ZooKeeper树中的每个节点被称为Znode。和文件系统的目录树一样,ZooKeeper树中的每个节点可以拥有子节点。不同的是ZooKeeper命名空间中的Znode,兼具文件和目录两种特点。

5.1.1、引用方式

    Zonde由路径标注,ZooKeeper中被表示成有反斜杠分割的Unicode字符串,如同Unix中的文件路径。路径必须是绝对的,因此他们必须由反斜杠来字符开头。除此以外,他们必须是唯一的,也就是说每一个路径只有一个表示,因此这些路径不能改变。

5.1.2、数据访问

    ZooKeeper中的每个节点存储的数据要被原子性的操作。也就是说读操作将获取与节点相关的所有数据,写操作也将替换掉节点的所有数据。另外,每一个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。

5.1.3、Znode的结构

每个Znode由3部分组成:(1)、stat:此为状态信息, 描述该Znode的版本, 权限等信息(2)、data:与该Znode关联的数据(3)、children:该Znode下的子节点    既像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分,并可以具有子znode。用户对znode具有增、删、改、查等操作(权限允许的情况下)。    znode具有原子性操作,每个znode的数据将被原子性地读写,读操作会读取与znode相关的所有数据,写操作会一次性替换所有数据。zookeeper并没有被设计为常规的数据库或者大数据存储,相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。zooKeeper的服务器和客户端都被设计为严格检查并限制每个znode的数据大小至多1M,当时常规使用中应该远小于此值。

5.1.4、节点类型

    ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。    (1)、临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的Znode都会绑定到一个客户端会话,但它们对所有的客户端还是可见的。另外,ZooKeeper的临时节点不允许拥有子节点。    (2)、永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。

5.1.5、顺序节点

    当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。当计数值大于2的32次方-1时,计数器将溢出。

5.1.6、观察

    客户端可以在节点上设置watch,我们称之为监视器。当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。

5.2、ZooKeeper中的时间

ZooKeeper有多种记录时间的形式,其中包含以下几个主要属性:

5.2.1、Zxid

    导致ZooKeeper节点状态改变的每一个操作都将使节点接收到一个zxid格式的时间戳,并且这个时间戳全局有序。也就是说,也就是说,每个对节点的改变都将产生一个唯一的zxid。如果zxid1的值小于zxid2的值,那么zxid1所对应的事件发生在zxid2所对应的事件之前。实际上,ZooKeeper的每个节点维护着三个zxid值,为别为:cZxid、mZxid、pZxid。        cZxid: 是与该节点的创建时间所对应的Zxid格式时间戳;        mZxid:是与该节点的修改时间所对应的Zxid格式时间戳;        pZxid:是与该节点的子节点(或该节点)的最近一次创建 / 删除所对应的时间戳 (只与本节点 / 该节点的子节点有关;与孙子节点无关)    实现中zxid是一个64位的数字,它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是个递增计数。

5.2.2、版本号

对节点的每一个操作都将致使这个节点的版本号增加。每个节点维护着三个版本号,分别为:version :节点数据版本号cversion :子节点版本号aversion :节点所拥有的ACL版本号

5.3、节点的属性结构

一个节点自身拥有表示其状态的许多重要属性,如下图4-1所示。

这里写图片描述

                        图4-1 Znode节点属性结构

5.4、Zonde总结

(1)znode中的数据可以有多个版本,在查询该znode数据时就需要带上版本信息。如:set path version / delete path version(2)znode可以是临时znode,由create -e 生成的节点,一旦创建这个znode的client与server断开连接,该znode将被自动删除。client和server之间通过heartbeat来确认连接正常,这种状态称之为session,断开连接后session失效。(3)临时znode不能有子znode。(4)znode可以自动编号,由create -s 生成的节点,例如在 create -s /app/node 已存在时,将会生成 /app/node00***001节点。(5)znode可以被监控,该目录下某些信息的修改,例如节点数据、子节点变化等,可以主动通知监控注册的client。事实上,通过这个特性,可以完成许多重要应用,例如配置管理、信息同步、分布式锁等等。

6、ZooKeeper服务中操作

在ZooKeeper中有9个基本操作,如下图5-1所示:

这里写图片描述

                    图5-1 ZooKeeper类方法描述    更新ZooKeeper操作是有限制的。delete或setData必须明确要更新的Znode的版本号,我们可以调用exists找到。如果版本号不匹配,更新将会失败。    更新ZooKeeper操作是非阻塞式的。因此客户端如果失去了一个更新(由于另一个进程在同时更新这个Znode),它可以在不阻塞其他进程执行的情况下,选择重新尝试或进行其他操作。    尽管ZooKeeper可以被看做是一个文件系统,但是处于便利,摒弃了一些文件系统的操作原语。因为文件非常的小并且使整体读写的,所以不需要打开、关闭或是寻地的操作。

7、Watch触发器

7.1、 watch概述

    ZooKeeper可以为所有的读操作设置watch,这些读操作包括:exists()、getChildren()及getData()。watch事件是一次性的触发器,当watch的对象状态发生改变时,将会触发此对象上watch所对应的事件。watch事件将被异步地发送给客户端,并且ZooKeeper为watch机制提供了有序的一致性保证。理论上,客户端接收watch事件的时间要快于其看到watch对象状态变化的时间。

7.2、watch类型

ZooKeeper所管理的watch可以分为两类:    (1)、数据watch(data  watches):getData和exists负责设置数据watch    (2)、 孩子watch(child watches):getChildren负责设置孩子watch我们可以通过操作返回的数据来设置不同的watch:    (1)、getData和exists:返回关于节点的数据信息    (2)、 getChildren:返回孩子列表因此      (1)、一个成功的setData操作将触发Znode的数据watch    (2)、 一个成功的create操作将触发Znode的数据watch以及孩子watch    (3)、一个成功的delete操作将触发Znode的数据watch以及孩子watch

7.3、watch注册与处触发

这里写图片描述

            图 6-1 watch设置操作及相应的触发器(1)、exists操作上的watch,在被监视的Znode创建、删除或数据更新时被触发。(2)、getData操作上的watch,在被监视的Znode删除或数据更新时被触发。在被创建时不能被触发,因为只有Znode一定存在,getData操作才会成功。(3)、getChildren操作上的watch,在被监视的Znode的子节点创建或删除,或是这个Znode自身被删除时被触发。可以通过查看watch事件类型来区分是Znode,还是它的子节点被删除:NodeDelete表示Znode被删除,NodeDeletedChanged表示子节点被删除。    Watch由客户端所连接的ZooKeeper服务器在本地维护,因此watch可以非常容易地设置、管理和分派。当客户端连接到一个新的服务器时,任何的会话事件都将可能触发watch。另外,当从服务器断开连接的时候,watch将不会被接收。但是,当一个客户端重新建立连接的时候,任何先前注册过的watch都会被重新注册。Zookeeper的watch实际上要处理两类事件:    (1)、连接状态事件(type=None, path=null)        这类事件不需要注册,也不需要我们连续触发,我们只要处理就行了。    (2)、节点事件        节点的建立,删除,数据的修改。它是one time trigger,我们需要不停的注册触发,还可能发生事件丢失的情况。    上面2类事件都在Watch中处理,也就是重载的process(Event event)节点事件的触发,通过函数exists,getData或getChildren来处理这类函数,有双重作用:    (1)、注册触发事件    (2)、函数本身的功能        函数的本身的功能又可以用异步的回调函数来实现,重载processResult()过程中处理函数本身的的功能。
原创粉丝点击