ZooKeeper开发者指南(三)

引言html

这个文档是为了想利用ZooKeeper的协调服务来建立分布式应用的开发者提供的指南。它包括概念和实践的信息。node

这个文档的一开始的的四部分呈现了不一样ZooKeeper高级概念的的讨论。理解Zookeeper是怎么工做的和如何使用它同等重要。它不包含源码,可是它确实假设你熟悉分布式计算的问题。在这第一组的部分是:算法

  • ZooKeeper数据模型
  • ZooKeeper会话
  • ZooKeeper监听
  • 一致性保证

下面的四部分提供了实践编程信息。他们是:数据库

  • 构造阻塞:ZooKeeper操做的指南
  • 绑定
  • 程序结构,和简单的例子
  • 陷阱:常见问题和故障排除

本书最后的附录包含其它有用的ZooKeeper相关的信息express

本文档的大部分信息能够做为独立的参考材料。然而,在开始你第一个ZooKeeper应用以前,你应该至少阅读ZooKeeper数据模型和ZooKeeper基本操做的章节。同时,简单的编程例子对于理解ZooKeeper客户端应用的基本结构是不帮助的。apache

ZooKeeper数据模型编程

ZooKeeper有一个层次结构的命名空间,就像是一个分布的文件系统。惟一的不一样是每个节点能够有一个和它关联的数据,孩子节点也是。它好像是有一个文件系统容许文件是一个目录。节点的路径老是表达为一个典型的绝对的斜线分隔的路径:没有相对路径。任何nuicode字符能够被用在路径中同时受如下的约束:缓存

  • null字符(\u0000)多是路径名字的一部分。(在C绑定里面有问题)
  • 下面的字符不能被使用由于它们不能被很好的展现,或渲染得很混乱: \u0001 - \u001F和\u007F - \u009F
  • 下面的字段不容许:\ud800 - uF8FF, \uFFF0 - uFFFF
  • "."字符能够被用来做为名字的一部分。可是"."和".."不能单独被用来表示一个节点的路径,由于ZooKeeper不能用相对路径。下面的将会是不合法的:"/a/b/./'c"或者”/a/b/../c“.
  • "zookeeper"是保留字

ZNodes
每个ZooKeeper树中的每个节点被称为znode。Znodes维护了一个包括数据改变,acl改变的版本号的数据结构。这个数据结构一样也有时间戳。版本号和时间戳,容许ZooKeeper校验缓存和协调更新。每次znode的数据改变,版本号相应地增长。例如,安全

每当客户端检索数据,它一样也收到数据的版本号。当客户端执行一个更新或删除,它必须提供正在改变的znode的版本号。若是它提供了版本号和实际的版本号不匹配,更新将会失败。(这个行为能够被覆盖。更多信息请参考...)服务器

注意

在分布式应用的工程中,单词node被能够称为一个真实的主机,一个服务器,一个集群中的成员,一个客户进程,等等。在ZooKeeper的文档中,znodes指的是数据节点。Servers指的是组成ZooKeeper服务的机器;quorum peers指的是组成集群的servers;

client指的是任何使用ZooKeeper服务的主机或进程。

Znodes是开发者主要访问的实体。他们有一些在这里值得提起的特性。

Watches

客户端能够在znodes上设置监视器(watches)。这个znode的改变将触发这个监视器而后清除这个监视器。当一个监视器被触发,ZooKeeper给客户端发送一个通知。更多关于监视器的信息能够在ZooKeeper Watches部分找到。

Data Access

存储在命名空间中的每个znode的数据被原子性地读和写。读获取全部跟这个znode关联的全部数据,写替换全部的数据。每个节点有一个访问控制列表(ACL)限制谁能够作和能够作什么 。

ZooKeeper不是设计用来做为一个数据库或大对象的存储。相反地,它管理协调数据。这些数据能够是配置,状态信息等等。不一样形式的协调数据有一个共同的特性就是它们相对来讲数据量小:以千字节为单位。ZooKeeper客户端和服务端实现必需检查确保znode的数据小于1M,可是数据平均来讲应该小于这个值。对相对大的数据的操做将引发一些操做耗费比其它更多的时间而且将影响一些操做的延迟,由于须要更多的时间在网络上移动数据和移动到存储媒介上。若是须要存储大数据,一般处理这种数据的模式是把它们存储在容量存储系统上。例如NFS或HDFS,而且在ZooKeeper上存储指针。

Ephemeral Nodes

 ZooKeeper也有短暂节点的概念。这些节点只要建立它的会话是活跃的就会一直存在。当会话结束的时候,这个节点就会被删除。由于这样的行为特性,短暂的节点不容许有孩子节点。

Sequence Nodes - 惟一命名

当建立一个znode你也能够要求ZooKeeper追加一个单调递增的计数器在路径的结尾。这个计数器对父节点来讲是惟一的。这个计数器的格式是%010d -- 也就是说10位数和0(数字0)衬垫(不足10位补0)(计算器被格式化为这种形式是为了简化存储)。也就是"<path>0000000001"。参考Queue Recipe获取这个特性的例子。注意:被用来存储下一个序列数字的计算器是一个被父节点维护的有符号的(signed)int,计算器将会溢出当增长超过2147483647(产生一个名字”<path>-2147483647“)。

Time in ZooKeeper

ZooKeeper以多种方式记录时间:

  • Zxid

每次改变ZooKeeper状态将会收到一个zxid形式的标记(ZooKeeper的事务Id)。这暴露了ZooKeeper的全部改变的总序列。每个改变将有一个惟一和zxid而且若是zxid1比zxid2小那么zxid1在zxid2以前发生(happend before zxid2).

  • 版本号

一个节点每改变一次将引发 这个节点的版本号增长一次。这三个版本号是version(znode的数据改变的次数),cversion(znode字节点的改变的次数),和aversion(znode节点的ACL改变的次数)。

  • Ticks

当使用多服务器的ZooKeeper,服务器之间使用ticks来定义好比状态上传,会话超时,节点以前是链接超时等等的时间。tick时间只经过最小的会话超时时间来暴露(2倍的tick时间);若是一个客户端请求会话超时小于最小的会话超时时间,那么服务端将会告诉客户端真实的会话超时时间为最小的会话超时时间。

  • 真实时间(Real time)

ZooKeeper不使用真实的时间或时钟时间,除了把时间戳在znode建立和修改的时候加入到数据结构。

ZooKeeper数据结构

 

ZooKeeper中的每个znode的数据结构是由下面的字段组成的:

  • czxid

建立znode时的zxid

  • mzxid

最后修改znode的zxid

  • ctime

从znode建立以来的毫秒时间

  • mtime

从znode最后修改以来的毫秒时间

  • version

znode数据改变的次数

  • cversion

znode孩子节点改变的次数

  • aversion

znode的ACL改变的次数

  • ephemeralOwner

若是是一个短暂节点,这个值就是znode的拥有者的会话id。若是不是短暂节点,它的值为0。

  • dataLength

znode的数据字段的长度

  • numChildren

znode孩子节点的数量

ZooKeeper Sessions

一个ZooKeeper客户端经过使用一个语言绑定建立一个握手来创建和ZooKeeper服务的会话。一旦创建,处理器以CONNECTING状态开始而且客户端库试图链接组成ZooKeeper服务的其中一个服务端,这时它就变成CONNECTED状态。在正常操做下将会处于这二者之中的状态。若是发生不可恢复的错误,例如会话过时或受权失败,若是若是应用显式地关闭了此次握手。此次握手将会变成CLOSED状态。下面的图表展现了ZooKeeper客户端可能出现的状态转换。

为了建立客户端会话应用代码必需提供一个包括以逗号分割主机:端口(host:port)列表对组成的链接字符串,每个对应一个ZooKeeper服务端(例如:"127.0.0.1:4545"或者”127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002“)。ZooKeeper客户端库将挑选一个任一的服务端而且试图进行链接。若是这个链接失败,或者若是由于任何缘由客户端断开链接,客户端就自动地尝试链接列表中的下一个服务端,直到(从新)创建一个链接。

3.2.0新加 一个可选的"chroot"后缀能够加入到链接字符串。这将会运行客户端命令并相对于这个root解释全部的路径(和nuix的chroot命令类似)。若是使用例子看起来像这样:"127.0.0.1:4545/app/a"或者"127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a"。这里客户端将会以"/app/a"为根目录而且全部的路径将会是相对于这个根目录 - 例如 getting/setting/等等"/foo/bar"结果将是在”/app/a/foo/bar"上操做(从服务端的角度来看)。这个特色在一个特定ZooKeeper服务的每个用户是不一样根目录的多用户环境是很是有用的。这使重用变得更简单由于每个用户能够改变他的/她的应用使它好像在"/"的根目录下,同时真实的路径(也就是 /app/a)能够被肯定在部署的时候。

当客户端获得一个到服务端句柄,ZooKeeper建立一个ZooKeeper会话,以64位的数字表示,分配给客户端。若是客户端链接到不一样的ZooKeeper服务端,它将会发送会话id做为链接握手中的一部分。做为一个安全措施,服务端为会话Id建立一个密码任何ZooKeeper服务端能够用来校验。这个密码和会话id被发送到客户端当链接创建的时候。当和新的服务端从新创建链接的时候客户端发送这个密码和会话id到服务端。

ZooKeeper客户端库建立ZooKeeper会话的参数中的一个是以毫秒表示的会话超时时间。客户端发送一个请求超时时间,服务端在超时时间内回复客户端。目前的实现要求这个超时时间最小是2位的tickTime(在服务端的配置文件里设置),最多20倍的tickTime.ZooKeeper客户端API容许访问这个超时时间。当一个客户端(会话)被ZK服务集群隔离开来,它将开始搜索在会话创建里的服务器列表。最终,当客户端和任一服务端的链接从新创建,这个会话将要么再次转变为“connected”状态(若是在会话超时时间内创建链接)要么转变为"过时"状态(若是在会话超时后创建链接)。不建议为断开创建一个新的会话对象(一个新的ZooKeeper.class或zookeeper句柄)。ZK客户端将为你处理重连。特别地咱们把启发式算法内建到客户端库来处理像"羊群效应"(herd effect),等等,只有当你被通知了会话到期了才新建一个会话(强制性的)。

会话到期时间被ZooKeeper集群自己管理,而不是客户端。当客户端跟ZK集群创建一个会话,它提供一个"过时时间"值。这个值被集群决定客户端会话何时过时。过时发生当集群不能听到客户端的消息时在指定的会话超时周期内(也就是没有心跳)。当会话过时集群将会删除全部的属于这个会话的短暂节点并通知全部的连接的客户端这一改变(任何监视这些节点的客户端)。这时会话过时的会话的客户端仍然和集群处于断开状况。它将不会被通知会话过时只到/除非它和集群从新创建连接。客户端一直处于断开状态只到它和集群从新创建连接。这时过时会话的监视器将会收到"会话过时"通知。

对于一个会话过时的状态转换能够被过时会话的监视器看到的例子:

  1. 'connected':会话被创建而且客户端正在和集群通讯(客户端/服务端的通讯正在正常地操做)
  2. .... 客户端被集群隔离
  3. 'disconnected':客户端已经和集群失去连接
  4. .... 时间流逝,在'超时时间'周期事后集群使会话过时,客户端不会看到任何东西由于它已经和集群失去连接了
  5. .... 时间流逝,客户端恢复了和集群的网络层的链接
  6. 'expired':最终客户端恢复了和集群的连接,而后它被通知到已通过期。

另外一个ZooKeeper的会话创建和参数是默认的监视器。监视器被通知当在客户端发生任何改变。例如若是客户端失去了服务端的链接它将会被通知,或者客户端的会话过时,等等。这个监视器应该考虑初始状态到失去链接的状态(也就是说在任何状态改变前事务被客户端库送到观察者)。在一个新链接的状况下,第一个送给观察者的事件一般是会话创建事件。

会话经过客户端发送请求保持存活。若是会话空闲一段超时会话的时间,客户端将发送一个PING请求来保持会话是活着的。这个PING请求不只使ZooKeeper服务端知道客户端是仍然存活着,它也使客户端检验到ZooKeeper服务端的链接仍然活跃。PING的时机是至关保守以使确保合理的时间来检测一个死去的链接和从新链接一个新的服务端。

一旦到服务端的链接成功地创建(connected)这里有最基本的客户端库产生链接的两个例子,当或者同步或异步的操做被执行而且下面的其中一个持有:

  1. 应用在一个不在存活会话上调用一个操做
  2. ZooKeeper客户端和一个服务端断开链接当还有后续的操做做用到这个服务端,也就是说还有后续的异步调用。

3.2.0新加 -- SessionMovedException.有一个内部的异常叫作SessionMovedException,一般不被客户端看到。这个异常发生由于在一个链接中收到一个请求,这个会话已经被链接到一个不一样的服务端。这个错误的一般缘由是一个服务端发送一个请求到服务端,可是网络包有延迟,全部客户端超时而且链接到一个新的服务端。当延迟的数据包到达了第一个服务端,老的服务端检测到这个会话已经移动 了,而且关闭客户端链接。客户端一般不会看到这个错误由于他们不会从这个老的链接中读数据(老的链接一般已经关闭)。这个条件能够被看到的状况是当两个客户端试图从新创建相同链接用一个保存的会话id和密码。其中一个客户端将从新创建链接而另外一个将被断开链接(致使试图从新链接它的会话的对无期限地)

Updating the list of servers 咱们容许一个客户端经过一个新的逗号分隔的host:port列表对来更新链接字符串,这每个对应一个ZooKeeper服务端。这个功能调用 一个几率的负载均衡逻辑,这个逻辑可能引发客户端和它的当前主机断开链接,以便达到新列表的每个服务端都有均一链接数。若是当前链接的主机不在新的列表里面,那么此次调用将老是引发这个链接被丢弃。不然,这个决定是基因而否服务端的数量是增长了仍是减少了和增长减少了多少。

例如,若是选择的链接字符串包括3个主机而且如今 的列表包含3个主机和2个新主机。3个主机中的每个主机的40%将转移到新主机中的一台为了平衡压力。这个逻辑将致使这个客户端有40%的几率丢掉当前链接的主机,而且在这个例子当中这个客户端链接到2个新主机中的一个,随机选择。

另外一个例子 -- 假如咱们有5个主机,如今更新这个列表来删除其中两个主机,剩下的3个主机的链接仍然保持链接,而后全部链接到删除的两个主机的链接将须要移动到剩下3台中的一台,随机选择。若是链接被丢掉,客户端移动到一个特殊的模式,它利用几率算法来选择一个新的主机,而不只仅是轮询。

在第一个例子中,第一个客户端有40%的几率决定断掉链接,若是这个决定肯定,它将试图随机链接一个新主机,而且只有它不能链接到任何一个新主机的时候,它将试图链接老的主机。在找到一个服务端以后,或者尝试了全部新列表中的服务端以后而且链接失败,客户端返回普通操做模式,它从链接字符串选择任一一个服务端而且尝试链接它。若是失败,它将轮训地尝试不一样的主机。(参考上面开始选择服务端的逻辑)

ZooKeeper Watches

全部ZooKeeper中的读操做 - getData(),getChildren(),和exist() - 有一个设置监视器选项。这里是ZooKeeper的监视器的定义:一个监视器事件是一次性触发,发送给设置这个监视器的客户端,这个事件当设置监视器的数据发生改变的时候发生。监视器的定义有三个关键点须要考虑:

  • 一次性触发

一个监听事件将会被发给客户端在数据已经改变的时候。例如,若是一个客户端作了一个getData("/znode1", true)操做,而后/znode1的数据被改变或删除,客户端将等到一个/znode1的监听事件。若是/znode1再次改变,将没有监听事件被发生,除非客户端作了另外一个读操做而且设置 一个新的监视器。

  • 发送给客户端

这意为着一个事件正在发送给客户端的路上,可是可能尚未到达客户端在成功返回以前改变操做到达客户端以前。监视器被异步地发送给监听听。ZooKeper提供了一个顺序保证:一个客户端将不会看到它设置监视器的数据的改变直接它看到了监视事件。网络延迟或其它因素可能致使不一样看到监视器而且返回代码在不一样的时候点。关键点是不一样的客户端看到的任务东西都是有顺序的。

  • 设置监视器的数据

这是指一个节点能够以不一样的方式改变。它有助于把ZooKeeper想像成一个维护两个监视器的列表:数据监视器和孩子监视器。getData()和exists()设置数据监视器。getChildren()设置孩子监视器。另外,它可能有帮助的根据数据返回的类型想像 正在设置的监视器。getData()和exixts()返回关于这个节点的信息,然而getChildred()返回一个孩子的列表。所以,setData()将触发给znode设置的数据监视器(假设成功设置)。一个成功的create()将触发正在被建立的这个znode的数据监视器,父节点的孩子监视器。一个成功的delete()将触发正在被删除的znode的数据监视器和孩子监视器(由于没有了孩子节点)同时也触发这个被删除节点的父节点的孩子触发器。

监视器被维护在客户端链接的ZooKeeper服务端的本地。这使监视器设置,维护,和分发都很轻量。当一个客户端链接到一个新的服务端,监视器将会触发任何会话事件。监视器将不会被收到当正在和服务端处于断开状态。当一个客户端从新链接,先前注册的监视器将被从新注册若是须要被触发。一般这些透明地发生。有一种状况一个监视器可能丢失:尚未建立的znode的存在性的监视器将被丢失若是znode被建立而且删除在处于断开链接的时候。

监视器的语义

咱们有三个读取ZooKeeper状态的的调用能够设置监视器:exists(),getData(),和getChildren().下面的列表详细说明了一个监视器能够触发的事件和能够触发的调用:

  • 建立事件:

调用exists()时候触发。

  • 删除事件:

调用exists(),getData(),和getChildren()的时候触发。

  • 改变事件:

调用exists()和getData()的时候触发

  • 孩子事件:

调用getChildren的时候触发

删除监视器

咱们可能调用removeWatches来删除注册到一个znode的监视器。同时,ZooKeeper客户端能够在本地删除监视器即便没有服务器跟它链接经过设置本地标志为true.下面的列表详细描述了将被触发的事件在成功删除监视器以后。

  • 孩子删除事件

调用getChildren增长的监视器

  • 数据删除事件

调用exists或getData增长的监视器

有关Watches的ZooKeeper担保

关于监视器,ZooKeeper维护三个担保

  • 监视器被排序和其它事件,其它监视器和异步回复。ZooKeeper客户端库确保分发的全部事件 都是有序的。
  • 一个客户端将看到它正在监视的znode的监视事件在它看到这个znode的新整数以前。
  • ZooKeeper的监视事件的顺序和被ZooKeeper服务端的看到的更新顺序一一对应。

关于监视器须要记住的事

  • 监视器是一次性触发器;若是你获得一个监听事件,而且你想在之后的改变时被通知,你必须设置另外一个监视器。
  • 由于监视器是一次性触发器而且在获取事件和发送新请求来获取一个监视器以前有延迟。你不能可靠地看到ZooKeeper中的znode的每个改变。请准备处理在获取事件和设置再次设置监视器之间znode改变不少次的状况。(你能够不关心,但最少知道它可能发生)。
  • 一个监视器对象,或者函数/上下文对,对于一个给定的通知将会被触发一次。例如,若是给相同的文件的exists和getData调用设置相同的监视器对象,而且这个文件随后被删除,那么这个监视器对象将只被触发一次这个文件删除的通知。
  • 当你和一个服务端断开链接(例如,当服务端失败),你将不会获得 任何监视器只到链接被从新创建。由于这个缘由会话事件被发送给全部未完成的监视器处理器。使用会话事件进入一个安全模式:你将不会收到事件在断开链接的时候,因此你的进程在这个模式下应该保守地采起行动。

使用ACLs的ZooKeeper的访问控制

ZooKeeper使用ACLs来控制对znodes的访问(ZooKeeper的数据树的数据节点)。ACL的实现和UNIX文件访问权限很是类似:它用权限位来容许/不容许对应节点的不一样的操做和权限位应用的范围。和标准的NUIX权限不一样的是,ZooKeeper节点没有对用户,组,傲世界(其它的)的三个标准的范围限制(文件的拥有者)。ZooKeeper没有znode拥有者这种概念。相反地,ACL指定了一组id和这些id关联的权限。

也注意ACL只适用一些特定的znode.特别地它没有应用到孩子。例如,若是/app 只对ip:172.16.16.1可读而且/app/status是全局可读的,任何人将能够读取/app/status;ACL是不可递归的。

ZooKeeper支持可插拔的认证方案。Ids被以cheme:id的形式指定,这里scheme是id对象的认证方案。例如, ip:172.16.16.1是主机172.16.16.1的id.

当客户端链接到一个ZooKeeperu而且认证了它本身,ZooKeeper把客户端链接关联到这个客户端对应的id上。这个ids根据znode的ACLs被检查当客户端试图访问一个节点的时候。ACLs被以成对的(scheme:expression, perms)组成。expression的格式对scheme来讲是特定的。例如,(ip:19.22.0.0/16, READ)对任何ip地址以19.22开头的客户端有读权限。

ACL权限

ZooKeeper支持以下的权限:

  • CREATE:你能够建立一个节点
  • READ:你能够从这个节点和这个节点的孩子列表获取数据
  • WRITE:你能够设置一个节点的数据
  • DELETE:你能够删除这个节点
  • ADMIN:你能够设置权限

对于细粒度的访问控制CREATE和DELETE权限已经被WRITE权限给打破。下面是CREATE和DELETE的例子:

你但愿A能够设置ZooKeeper节点的值,可是不能建立或删除子节点。

没有DELETE的CREATE:客户端建立请求经过在父节点建立ZooKeeper节点。你想全部的客户端能够增长,可是只有请求进程能够删除。(这就好像文件的APPEND权限)

同时,ADMIN权限在这里是由于ZooKeeper没有一个文件全部者的概念。在某种意义上ADMIN权限指明了拥有者。ZooKeeper不支持LOOKUP权限(在目录上的执行权限位容许你LOOKUP即便你不能罗列这个目录)。每个人显式地拥有LOOKUP权限。这容许你一个节点,可是不会有更多。(问题是,若是你想在不存在的节点上调用zoo_exists(),没有任何权限检查)

内置的ACL方案

ZooKeeper有如下内置的方案:

world有一个单独的id,anyone,这表明任何人。

auth 不使用任何id,表示任何受权的用户

digest 使用一个 username:password字符串来生成 一个MD5哈希。而后被用来做为ACL ID标示。受权经过发送一个明文username:password来完成。当在ACL中使用这个表达式将会是username:base64 encoded SHA1 password digest

ip 用客户端的主机ip做为ACL ID标示。ACl表达式addr/bits,这里的addr的前bits位和客户端主机ip的前bits位来匹配。

-----------------------------------------------------这里有一段关于c语言的部分被省略了--------------------------------------------------------------------

可插拔的ZooKeeper认证

ZooKeeper运行不一样的认证方案的不一样的环境中,因此它有一个彻底可插拔的认证框架。甚至内置的认证方案也是用的这个可插拔的认证框架。

为了理解这个认证框架是怎么工做的,首先你必须理解两个主要的认证操做。框架首先必需认证客户端。这个一般一旦客户端链接服务端的时候被完成而且包含从客户端发送的校验信息而且把这些和链接关联起来。第二个被框架处理的操做是在ACL中找一个对应这个客户端的条目。ACL条目是<idspec, permissions> 对。idspec多是一个简单的string和匹配这个链接关联的认证信息或者它多是一对这个信息计算的表达式。这取决于认证插件作这个匹配的具体实现。

这里是认证插件必须实现的接口:

 

public interface AuthenticationProvider {
    String getScheme();
    KeeperException.Code handleAuthentication(ServerCnxn cnxn, byte authData[]);
    boolean isValid(String id);
    boolean matches(String id, String aclExpr);
    boolean isAuthenticated();
}

 

第一个方法getScheme返回标示这个插件的字符串。由于 咱们直接多种认证的方法,一个认证证书或idspec将老是以scheme为前缀。ZooKeeper服务端使用经过认证插件返回的scheme来决定scheme应用到那一个id。

handleAuthentication被调用当一个客户端发送被关联到这个链接的认证信息时。客户端指定这个信息对应的scheme.ZooKeeper服务端传递这个信息给认证插件。插件 getScheme匹配被客户端传过来的scheme。handleAuthentication的实现者一般将返回一个错误若是它肯定信息是坏的,或者它将把这个信息和这个链接关联起来使用cnxn.getAuthInfo().add(new Id(getScheme(), data))

认证插件被参与到设置和使用ACLs,当一个ACL被设置给一个znode,ZooKeeper服务端将传递条目的id部分给 isValid(String id) 方法。它取决于插件来验证id是不是一个正确的形式。例如,ip:172.16.0.0/16 是一个合法的id,可是ip:host.com不是。若是新ACL包含一个"auth"条目,isAuthenticated 被用来查看是否跟这个链接关联的这个scheme的认证信息应该被加到这个ACL。一些schemes不该该包含在auth。例如,客户端的IP地址不被认为应该被做为id加入到ACL若是auth被指定。

ZooKeeper在检查ACL的时候调用matches(String id, String aclExpr)。它须要这个客户端的认证信息和相关的ACL条目匹配。为了找一个应用这个客户端了条目,ZooKeeper服务端将找到每个条目的scheme而且若是有这个scheme的认证信息,matches(String id, String aclExpr)将被调用,而后id赋值给先前被handleAuthentication和aclExpr设置ACL条件的id被加入到链接的认证信息。认证插件使用它本身的逻辑而且匹配scheme来决定是否id被包含在aclExpr。

这里有两个内置的认证插件:ipdigest。其它的插件可使用系统属性被加入。在启动时ZooKeeper服务端将寻找以"zookeeper.authProvider."开头的系统属性而且解析这些属性的值为认证插件的类名。这个属性可使用 -Dzookeeeper.authProvider.X=com.f.MyAuth或者像下面同样在服务端的配置文件里增长一个条目被设置:

authProvider.1=com.f.MyAuth
authProvider.2=com.f.MyAuth2

应该注意来保证属性的后缀是惟一的。若是是重复的例如 -Dzookeeeper.authProvider.X=com.f.MyAuth -Dzookeeper.authProvider.X=com.f.MyAuth2。只有一个将被使用。同时全部的服务端必须有相同的插件定义,不然客户端使用插件提供的认证方案将会有问题。

一致性保证

ZooKeeper是一个高性能,可扩展的服务。读和写操做都被设计得很快。这的缘由是在读取的状况下,ZooKeeper能够服务老的数据,反过来也是由于ZooKeeper的一致性保证:

顺序的一致性

从客户端来的更新将被按照它们发送的顺序应用。

原子性

更新要么成功要么失败 -- 没有部分结果。

单系统镜像

客户端将会看到服务端相同的视图无论它链接的是那一个服务端

可靠性

一旦更新被应用,它将会被留存到直到一个客户端覆盖这个更新。这个担保有两个结果:

  1. 若是客户端获得 一个成功的返回结果,这个更新已经被应用。在一些失败(通讯错误,超时,等等)客户端将不会知道是否更新被应用。咱们采起措施来减少这种失败,可是这担保是在成功返回的时候出现。(在Paxos中这被称为单调性条件)。
  2. 任务被客户端看到的更新,带过读请求或成功更新,将永远不会被回滚当从服务端失败恢复过来的时候。

时效性

系统的客户端视图在必定的时限内(几十秒的顺序)保证是最新的。要么系统改变将被客户端看到,要么客户端将检测到服务端过时。

利用这些一致性保证,很是容易构建高级别的功能,例如领导者选举,屏障,队列和读写可撤销锁在ZooKeeper客户端(对ZooKeeper来讲不须要更多)。更详细的信息请参考Recipes and Solutions

注意

有时候开发者错误地认为另外一个保证ZooKeeper没有实现。它就是:

同时一致的跨客户端视图

ZooKeeper没有及时地在每个实例上作保证,两个不一样的客户端将有ZooKeeper数据一致的视图。由于像网络延迟之样的因素,一个客户端可能在另外一个客户端获取这个改变以前执行一个更新。考虑有两个客户端A和B的场景。若是客户端A设置节点/a的值从0到1,而后告诉客户端B读取/a,客户端B可能读到老的数据0,取决于它链接是那一个服务端。若是客户端A和B读到相同的值很是重要,客户端B应该调用sync()方法在它执行读操做以前。

因此,ZooKeeper自己不保证改变在全部的服务端两步发生,可是ZooKeeper原语可能被用来构造更高级的功能来提供有用的客户端同步。(更多信息请参考ZooKeeper Recipes)。

绑定(Bindings)

ZooKeeper客户端库由两种语言:Java和C.下面的部分描述这些内容。

Java Binding

ZooKeeper的Java bingding有两个包:org.apache.zookeeper 和 org.apache.zookeeper.data。组成ZooKeeper的剩下的其它包被用来在内部使用或是服务端实现的一部分。org.apache.zookeeper.data包被生成的classes组成,这些类被简单地用做容器。

被ZooKeeper Java客户端使用的最主要类是ZooKeeper类。它的两个构造器只是在可选的session id和password上不一样。ZooKeeper支持在整个进程实例中会话恢复。Java程序能够保存它的session id和password到一个彻底的存储设备上,重启,而且恢复这个被先前程序的实现使用的会话。

当一个ZooKeeper对象被建立,两个线程也被建立:一个IO线程和一个事件线程。全部IO操做都在IO线程(NIO)上发生。全部事件调用发生在事件线程上。例如到ZooKeeper服务端的重连的会话维护和维护心跳是在IO线程上完成。同步方法的回复也是在IO线程中处理。全部异步方法的回复和监听事件在事件线程中处理。这种设计有一些事情须要注意:

  • 全部异步调用的完成和监视器回调将被按顺序完成。一次一个。调用者能够作它想作的任何处理,可是在这期间没有其它回调将被处理。
  • 回调不会阻塞IO线程的处理或者同步调用的处理。
  • 同步调用可能不以正确的顺序返回。例如,假如一个客户端这个以下的处理:发起一个对节点/a的异步读而且设置watch为true,而后在这个读完成的回调中它作了一个同步的/a读。(可能不是一个好的实践,可是也不非法,而且它只是一个简单的例子)。注意若是在异步读和同步读之间有一个改变,客户端库将会收到一个监视器事件说/a被改变在同步读以前,可是由于完成回调正在阻塞事件队列,同步的读将会返回/a的新值在监视器事件被处理以前。

最后,与关闭相关的法则是直接的:一旦一个ZooKeeper对象被关闭或者收到一个导致的事件(SESSION_EXPIRED 和 AUTH_FAILED),ZooKeeper对象变得无效。在关闭时,两个线程关闭而且任何对ZooKeeper的进一步的访问是未定义的行为而且应该被避免。

-------------------------------------有关C的内容被忽略---------------------------------------------

 

Building Blocks: ZooKeeper操做指南

本节调查一个开发人员能够对ZooKeeper服务端执行的全部操做。这相比前面的概念章节是比较低级别的信息,可是比ZooKeeper API手册高级一些。它包含这些主题:

  • Connecting to ZooKeeper

错误处理

Java和C客户端绑定均可能报告错误。Java客户端绑定经过抛出KeeperExecption来报告错误,调用异常的code()方法将返回指定的错误代码。C客户端绑定返回一个ZOO_ERRORS枚举的错误代码。API回调表示两种语言绑定的返回代码。更多信息请参考API文档关于可能的错误和它们的意思。

Connecting to ZooKeeper

Read Operations

Write Operations

Handling Watches

Miscelleaneous ZooKeeper Operations

Program Structure, with Simple Example

陷阱:常见问题和故障排除

如今你了解了ZooKeeper。它使你的应用很快,简单,可是等等。。。有一点问题。这里有一些ZooKeeper用户可能要掉进去的陷阱:

  1. 若是你正使用监视器,你必需寻找链接的监听事件。当一个ZooKeepe客户端和一个服务端断开,你将不会收到这一改变的通知直到你从新链接。若是你正监视一个znode是否存在,你将错过这一事件若是znode在你断开链接的时候被创始和删除。
  2. 你必需测试ZooKeeper服务端失败。ZooKeeper服务端可能从失效中存活只到大多数的服务端是活跃的。要问的问题是:你的应用程序能够处理它么?在真实世界一个客户端到ZooKeeper的链接可能断掉。(ZooKeeper服务端失效和网络隔离是链接丢失的常见缘由)ZooKeeper客户端库负责处理你的链接恢复和让你知道发生了什么 事,可是你必须确保你恢复了你的状态和任何失败的没有处理的请求。在测试 环境中找到是否你是对的,而不是在生产环境 - 测试由一些服务端组成的ZooKeeper服务而且重启他们。
  3. 被客户端使用的ZooKeeper服务端列表必须和每个ZooKeeper服务端拥有的ZooKeeper服务端列表匹配。若是客户端列表是真实ZooKeeper服务端列表的子集,事件能够工做,尽管不是太完美。可是不能是客户端的ZooKeeper服务端列表不在ZooKeeper集群里。
  4. 关于你在那里存放事务日志应该当心。ZooKeeper的性能最关键部分是事务日志。ZooKeeper必须在返回响应以前同步事务到一个媒介中。一个专门的事务日志设备是一向性能良好的关键。把日志放在一个比较繁忙的设置上将会影响性能。若是你只有一个存储设备,把日志文件放在NFS而且增长快照数量;它没有消除这个问题,可是它能减轻它。
  5. 正确地设置你的Java最大堆的值。避免交换是很是重要的。没必要要的进入磁盘将几乎确定地下降你的性能。记住,在ZooKeeper中,全部事件都是有序的,若是若是一个请求点击磁盘,全部的其它请求也点南磁盘。为了不磁盘交换,尝试设置你拥有的物理内在的数量,再减去操做系统和缓存须要的值。最好的决定最优的堆大小的方法是运行压力测试。若是由于一些缘由你不能运行压力测试,保守估计而且选择一个低于引发你机器交换的值。例如,在一个4G内在的机器上,3G堆大小是一个保守的估计。
相关文章
相关标签/搜索