ZooKeeper程序员指南
Introduction
本文当是针对想要以zookeeper协调服务为基础创建分布式应用程序的开发人员指南。包含概念和实用信息。
指南的前四部分展示各种zookeeper概念跟高层次的讨论,这些对于理解zookeeper如何工作和如何使用它是必要的。指南不包含源码,但是它假定读者对分布式计算相关问题是熟悉的。前四部分如下:
- The ZooKeeper Data Model(数据模型)
- ZooKeeper Sessions
- ZooKeeper Watches
- Consistency Guarantees(一致性保证)
后四部分提供了使用的编程信息,如下:
- Building Blocks: A Guide to ZooKeeper Operations Bindings(构建模块:ZooKeeper操作指南)
- Program Structure, with Simple Example [tbd](程序结构和简单例子)
- Gotchas: Common Problems and Troubleshooting(陷阱:常见问题和故障排除)
本书最后附有一个附录,其中包含与其他有用的ZooKeeper相关信息的链接。
本文档中编写大部分内容可作为独立的参考资料。然而,在开始开发第一个zookeeper程序之前,您至少应该阅读ZooKeeper数据模型和ZooKeeper基本操作的章节。此外,简单编程示例有助于理解ZooKeeper客户端应用程序的基本结构。
The ZooKeeper Data Model
zookeeper具有类是于分布式文件系统的层级命名空间,唯一的不同是命名空间里的node可以有自己相关的数据和子节点。这就象一个允许一个文件也是目录的文件系统,node的路径总是一个典型的,绝对的,用斜线分隔的路径。没有相对路径。node路径中使用的unicode字符受到一下限制:
- 空字符(\u0000)不能做为路径的组成部分。(这会导致c Binding出问题)
- 以下字符无法使用,因为它们显示不好,或以令人乱码的方式呈现:\u0001 - \u0019 and\u007F - \u009F。
- 以下字符禁止使用:\ud800 -uF8FFF,\uFFF0 - uFFFF。
- “.” 字符可用于路径名字的组成部分,但是”.”和”..”不能单独标识节点的路径,因为zookeeper不使用相对路径。以下将是无效的:”/a/b/./c” 或者 “/a/b/../c”.
- zookeeper是保留字段。
ZNodes
Zookeeper树中的每个节点称为znode,znode维护着一个包含数据更改()版本号和acl(Access Control List)更改的stae结构,stat结构也包含时间戳。版本号和时间戳一起可让zookeeper验证缓存和协调服务。每次znode数据更改时,版本号增加。例如,每当客户端检索数据的时候,也会收到数据的版本号。当客户端执行更新和删除操作时,必须提供正在更改的znode的数据版本号。如果提供版本号与实际的版本不符合,更新失败。
Note
在分布式应用工程中,node节点可以表示一个通用主机,一台服务器,整体的成员,客户端进程等。
在zookeeper文旦中,znode指的是数据节点,server是指组成zookeeper服务的机器,quorum
peers是指构成整体的服务器,client是任指使用zookeeper服务的任何主机或者进程。
znode是程序员需要注意的主要抽象,Znodes有几个特性值得在此提及。
Watches
client可以在znode设置watch,对znode的跟改会触发watch,然后清除watch。当watch触发,zookeeper会发送通知到client。有关watch的更多信息可以在ZooKeeper Watches一节中找到。
Data Access
存储在命名空间的所有znode的数据都是以原子的方式读取和写入的。读取是获取znode相关的所有数据,写入是替换znode的数据。每个znode的访问控制列表(ACL)限制了谁可以做什么。
zookeeper不是被设计成通用数据库或者大对象存储。相反,它用于管理协调数据。这些数据可以以配置,状态信息,rendezvous()形式出现。各种形式的协调数据的共同特点是它们相对较小:以千字节为单位。Zookeeper客户端和服务器实现具有健全的检查措施以保证znode保存数据小余1m,但数据应该比平均数据少得多。在相对较大的数据下操作会导致一些操作比其他操作占用更多时间,并且会影响某些操作的延迟,因为将更多数据通过网络传输到存储介质需要额外的时间。如果大数据存储的需要,处理这类数据通常存储在大容量存储系统通中,例如NFS或者HDFS,并在zookeeper中保存其存储位置信息。
Ephemeral Nodes(临时节点)
Zookeeper也具有临时节点的概念,只要创建znode的session处于活动状态,这些znode就会存在。当session结束,znode被删除。由于这个特性,这类znode不允许存在子节点。
Sequence Nodes – Unique Naming
创建znode时,您还可以请求ZooKeeper将一个单调增加的计数器附加到路径末尾。该计数器对父节点znode是唯一的。该计数器的格式为%010d,即具由0(零)填充的10个数字(计数器以这种方式被格式化以简化排序),即”0000000001”。有关此功能的示例使用,请参阅Queue Recipe。 计数器用于存储下一个由父节点维护的带符号整数(4字节)的序列号,计数器在增加到2147483647以上时会溢出(结果名称为“ -2147483647”)。
Time In Zokeeper
Zookeeper通过多种方式跟踪时间:
Zxid
每次对zookeeper状态的改变都会收到一个Zixd(ZooKeeper Transaction Id)形式的戳,这暴露了ZooKeeper所有更改的总排序。每个更改都会有一个唯一的zxid,如果zxid1小于zxid2,那么zxid1发生在zxid2之前。Version numbers
对节点的每次更改都会导致该节点的版本号增加。三个版本号是:version(znode数据变更的次数),cversion(子znode数据变更的次数)和 aversion(znode的acl变更的次数)。Ticks (tick不知道如何解释)
当使用多服务器ZooKeeper时,服务器使用tick定义事件的时间,例如状态上传,会话超时,连接超时等。tick时间仅通过最小会话超时间接暴露(2被tick时间),如果客户端请求的会话超时低于最小会话超时,则服务器将通知客户端会话超时实际上是最小会话超时。
- Real time
除了在znode创建和znode修改中将时间戳放入stat结构中之外,ZooKeeper根本不使用实时或时钟时间。
Zookeeper Stat Structure
ZooKeeper中每个znode的Stat结构由以下字段组成:
czxid
此znode创建时的zxid.mzxid
此znode最后更改的zxidpzxid
此节点的子节点最后一次更改的zxidctime
此znode创建时的时间(以毫秒为单位)。mtime
此znode最后一次修改时的时间(以毫秒为单位)。version
znode节点数据变更次数cversion
子节点变更的次数。aversion
znode的ACl更改的次数。ephemeralOwner
如果znode是临时节点,则此znode的所有者的session id。 如果它不是短暂节点,则它将为零。dataLength
此znode数据字段的长度。numChildren
这个znode的子节点数量。
ZooKeeper Sessions
ZooKeeper客户端通过使用语言绑定创建服务句柄来建立与ZooKeeper服务的会话。一旦创建,句柄就会以CONNECTING状态开始,并且客户端库尝试连接到组成ZooKeeper服务的服务器之一,此时它将切换到CONNECTED状态。在正常操作过程中将处于这两种状态之一,如果发生不可恢复的错误,例如会话过期或身份验证失败,或者应用程序明确关闭了句柄,则句柄将移至CLOSED状态。下图显示了ZooKeeper客户端可能的状态转换:
要创建客户端session,应用程序代码必须提供一个以逗号分隔的host:port组成的链接字符串,每一个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”后缀可以附加到连接字符串。这样可以使客户端运行命令以这个”chroot”后缀为根目录执行(类似于unix chroot命令)。使用例子类似于:”127.0.0.1:4545/app/a” or “127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a”,客户端将以”/app/a”为根目录,并且所有的路径都将相对于这个路径。例如对“foo/bar”的getting/setting/等…操作在“/app/a/foo/bar”上运行(从服务器的角度来看)。此功能在 multi-tenant环境中特别有用,特定ZooKeeper服务的每个用户可以不同路径为根路径。这使得重用更简单,每个用户可以将他/她的应用程序以“/”为根,而实际位置(比如/app/a)可以在部署时确定。
当客户端获得ZooKeeper服务的句柄时,ZooKeeper将创建一个ZooKeeper会话,以64位数字表示,它将分配给客户端。如果客户端连接到不同的ZooKeeper服务器,Session ID作为连接握手的一部分被发送。作为一项安全措施,服务器未session id创建一个所有服务器都可以验证的密码,当客户端建立会话时,密码将随着会话ID发送到客户端。每当客户端重新建立与新服务器的会话时,密码将随着会话ID发送到服务端。
用于创建ZooKeeper会话的ZooKeeper客户端库调用的参数之一是以毫秒为单位的会话超时,客户端请求超时,服务端响应的超时时间可以有客户端设置。目前的实现方式要求超时至少为tickTime的2倍(在服务器配置中设置),最大为tickTime的20倍。ZooKeeper客户端API允许访问协商的超时。
当客户端(会话)从ZK服务集群分区后,它将开始搜索会话创建期间指定的服务器列表。最终,当客户端和至少一个服务器之间的连接重新建立时,会话将再次转换到“connected”状态(如果在会话超时值内重新连接),或者它将转换到“expired”状态(如果在会话超时后重新连接)。不建议创建新的会话对象(c building中的新ZooKeeper.class或zookeeper句柄)以断开连接。ZK客户端库将为您处理重新连接。特别是,我们在客户端库中内置了启发式函数,以处理诸如“羊群效应”等问题……只有在您通知会话过期(强制)时才创建新会话。
会话过期由ZooKeeper集群本身管理,而不是由客户端管理。当ZK客户端与集群建立会话时,它提供了上面详述的“timeout”值。该值由群集用于确定客户端的会话何时到期。当集群在指定的会话超时期限内没有从客户端收到响应(即无心跳)时,会发生过期。在会话到期时,群集将删除该会话拥有的任何/所有临时节点,并立即通知任何/所有连接的客户端该更改(任何人都在监测到这些znode)。此时,过期会话的客户端仍与群集断开连接,直到/除非能够重新建立与群集的连接,否则不会通知会话过期。客户
端将保持断开连接状态,直到与群集重新建立TCP连接,此时过期会话的观察者将收到“会话过期”通知。
过期会话的观察者看到过期会话的状态转换示例:
‘connected’:会话已建立并且客户端正在与群集通信(客户端/服务器通信正常运行)
….客户机从集群分区
‘disconnected’:客户端与集群失去连接
过一段时间,在’timeout’期间集群到期会话之后,客户端看不到任何东西,因为它与集群断开连接
过一段时间,客户端重新获得了与集群的网络级连接
‘expired’:最终客户端重新连接到群集,然后通知到期
ZooKeeper会话建立调用另一个参数是默认观察者。当客户端发生任何状态变化时,通知观察者。例如,如果客户端失去与服务器的连接,客户端将会收到通知,或者客户端的会话过期等等,他的观察者应该认为初始状态被断开(即,在客户端lib将任何状态改变事件发送给观察者之前)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。
与服务器的链接一旦成功建立(connected),客户端库产生connectionloss 的基本有两种情况,
当执行同步或异步操作并且以下之一成立时:
- 应用程序在不再活动或者不再有效的会话上执行操作
- 当该服务器有挂起的操作时,ZooKeeper客户端与服务器断开连接,即存在挂起的异步调用。
3.2.0版本增加 – SessionMovedException. 有一个内部异常通常对客户端不可见,叫做SessionMovedException,这个异常产生的原因是已经在不同服务器上重新建立会话的连接上收到请求。这个异常的正常原因是客户端向服务器发送请求,但网络数据包延迟,因此客户端超时并连接到新的服务器。当延迟的数据包到达第一台服务器时,旧服务器检测到会话已经移动,并关闭客户端连接。客户通常不会看到这个错误,因为他们没有从这些旧连接中读取(旧连接通常关闭)。其中一个客户端将重新建立连接,第二个客户端将断开连接)。一种情况是在两个客户端尝试使用保存的session id和密码重新建立相同连接情况下可以被观察到。其中一个客户端将重新建立连接,第二个客户端将断开连接(导致尝试无限期地重新建立其连接或者会话)。
ZooKeeper Watches
ZooKeeper中的所有读取操作- getData(),getChildren(),和exists()-可以选择将watch设置为副作用。Zookeeper watch的定义:watch 是一次触发,当设置watch的数据发生变化时发送给设置watch客户端。在watch定义中要考虑三个要点:
One-time trigger
当数据发生变化时,一个watch事件将被发送到客户端。例如,如果客户端执行getData(“/znode1”,true),并且稍后更改或删除/znode1的数据,客户端会收到/znode1的watch事件。如果/znode1再次更改,将没有事件被发送,直到客户端执行了其他的读取操作并设置了watch。Sent to the client
这意味着一个事件正在前往客户的途中,但在变更操作的成功返回代码到达发起更改的客户端之前,可能无法到达客户端。watch异步发送给watcher。Zookeeper提供排序保证:一个客户将永远不会看到它已经设置了watch的变化,直到它第一次看到watch事件。网络延迟或其他因素可能导致不同的客户端在不同时间查看watch并返回更新代码。关键是不同客户看到的一切都会有一致的顺序。成功的create()会触发设置的data watch以及父节点的子节点的child watch。The data for which the watch was set
这指的是节点改变的不同方式。这有助于理解ZooKeeper维护的两个手表清单:data watches and child watches。getData()和exists()设置data watches,getChildren()设置child watchs。 另外,有助于根据返回的数据类型设置watch的理解。getData()和exists()返回相关关znode数据的信息,而getChildren()返回一个子节点列表。由此可知,setData()将触发设置的data watches(假定设置成功), 一个成功的delete()将触发一个data watch和一个child watch(因为不能有更多的子节点),以便为父节点删除一个znode以及一个子监视。
Wathes在客户端连接的服务器本地维护。这可以让手表轻量化,便于设置,维护和派送。当客户端连接到新的服务器时,watch将会触发任何会话事件。与服务器断开连接时watch不会收到。当客户重新连接时,任何先前注册的catch将被重新注册并在需要时触发。有一种情况会导致手表丢失:如果znode在断开连接时被创建并删除,则表示尚未创建的znode的存在将被忽略。
Semantics of Watches
可以通过三个调用来设置watch读取Zookeeper的状态信息:exsits,getData和getChildren。以下列表详细介绍了watch可触发的事件以及启用启用方式:
Created event:
通过调用exists方法启用.Deleted event:
通过调用exists,getData和getChildren方法启用。Changed event:
通过调用exists和getData方法启用.Child event:
通过调用getChildren方法启用.
What ZooKeeper Guarantees about Watches
关于watches,ZooKeeper维护这些保证:
- watches相对于其他事件,其他手表和异步回复进行排序。ZooKeeper客户端库确保按顺序分派所有内容。
- 客户端在看到与该znode相对应的新数据之前,会看到它正在监视的znode的监视事件。
- 来自ZooKeeper的watch事件的顺序对应于ZooKeeper服务所看到的更新顺序。
Things to Remember about Watches
- watch是一次触发的,如果你想得到未来变更的通知,必须设置新的watch。
- 由于watch是一次触发并且在获得事件和发送新请求之间存在延迟,无法可靠的看到Zookeeper中节点发生的每一个变化。准备好处理获得事件和再次设置watch之间znode多次更改的情况(你可以不关心,但至少可以意识到它可能会发生。)
- watch对象或者方法/上下文对只会根据指定的通知触发一次。例如,对于同一个文件,如果同一watch对象被注册为exists和getData调用,然后该文件被删除,对于这个文件,仅在删除通知时才会调用watch对象一次。
- 当从服务器断开连接时(例如,当服务器失败时),在连接恢复之前是不能收到任何watch的。由于这个原因,会话事件被发送到所有优秀的watch处理程序。使会话进入安全模式:断开连接不会受到任何事件,所以你的处理应该保守地采取这种模式。
ZooKeeper access control using ACLs
Zookeeper通过znode(ZooKeeper数据树的data znode)的ACL控制数据访问权限。ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止针对节点的各种操作以及这些位所适用的范围。与标准UNIX权限不同,ZooKeeper节点不受user(文件所有者),group和world(其他)的三个标准作用域的限制。ZooKeeper没有znode所有者的概念,一个ACL指定了一组ID和与这些ID相关的权限。
注意,ACL适用于特定的znode,特别是它不应用到其子节点,例如,如果/app只能通过ip:172.16.16.1读取,并且/app/status是任何人可读的,ACL不是递归的。
Zokeeper支持可插入认证方案,通过表格scheme:id的方式指定Id,其中scheme是id对应的认证方案,例如,ip:172.16.16.1是地址为172.16.16.1的主机的ID。
当客户端连接到zookeepr进行身份验证时,zookeepr将与客户端对应的所有ID与客户端链接相关联。当客户端尝试访问节点时,会根据znodes的ACL检查这些ID,ACL由(scheme:expression,perms)对组成。expression具体格式在schema中体现,例如,(ip:19.22.0.0/16,READ)为IP地址以19.22开头的任何客户端授予读取权限。
ACL Permissions
ZooKeeper支持以下权限:
- CREATE:你可以创建一个子节点。
- READ:您可以从节点获取数据并列出其子项。
- WRITE:您可以设置节点的数据。
- DELETE:你可以删除一个子节点。
- ADMIN:您可以设置权限。
用于更细粒度的访问控制,CREATE和DELETE权限已从WRITE权限中分离出来。CREATE和DELETE的情况如下:
您希望A能够在ZooKeeper节点上执行一组操作,但无法CREATE和DELETE子项。
不带DELETE的CREATE:客户端通过在父目录中创建ZooKeeper节点来创建请求。您希望所有客户端都能够添加,但只有请求处理器才能删除。(有点像文件的APPEND权限。)
此外,由于ZooKeeper没有文件所有者的概念,但具有ADMIN权限。在某种意义上,ADMIN权限指定实体为所有者。ZooKeeper不支持LOOKUP权限(在目录上执行权限位以允许您LOOKUP,即使您无法列出目录)。每个人都隐含有LOOKUP权限。这使您可以统计一个节点,但仅此而已。(问题是,如果您想在不存在的节点上调用zoo_exists(),则没有权限检查。)
Builtin ACL Schemes
ZooKeeeper具有以下内置schema:
- world 它下面只有一个id, 叫anyone, world:anyone代表任何人。
- auth 它不需要id, 只要是通过身份验证的user都有权限
- digest取username:password字符串生成MD5哈希值,然后将其用作ACL ID标识。 通过以明文形式发送username:password来完成身份验证。在ACL中使用时,对应的id为username:BASE64(SHA1(password))
- ip 使用客户端主机IP作为ACL ID标识。ACL表达式的形式为addr/bits,其中addr的最高有效位与客户端主机IP的最高有效位相匹配。比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段
ZooKeeper C client API
(…略…)
Pluggable ZooKeeper authentication
ZooKeeper运行在各种不同的环境中,具有各种不同的身份验证方案,因此它具有完全可插入的身份验证框架。即使内置身份验证方案也使用可插入身份验证框架。
要了解身份验证框架的工作原理,首先您必须了解两个主要的身份验证操作。框架首先必须验证客户端。这通常在客户端连接到服务器后立即完成,包括验证从客户端发送或收集的信息并将其与连接关联。框架处理的第二个操作是查找ACL中与客户端对应的条目。 ACL条目是
Consistency Guarantees
ZooKeeper是一个高性能,可扩展的服务。读取和写入操作的设计都很快,但读取速度比写入速度快。原因是在读取的情况下,ZooKeeper可以提供较旧的数据,而这又是由于ZooKeeper的一致性保证:
Sequential Consistency(顺序一致性)
来自客户端的更新将按照它们发送的顺序进行应用。
Atomicity(原子性)
更新成功或失败 - 没有其他结果。
Single System Image(单系统镜像)
无论连接到哪个服务器,客户端都会看到相同的服务视图。
Reliability(可靠性)
更新一旦应用了,它就会一直持续到客户端覆盖更新。这个保证有两个推论:
- 如果客户端获得成功的返回码,则会应用更新。在某些故障(通信错误,超时等)时,客户端将不知道更新是否已应用,我们采取措施尽量减少故障,但保证仅在成功返回代码时呈现。(这在Paxos中称为单调性条件)
- 任何更新操作是通过读取请求或成功更新被客户端发现,在从服务器故障中恢复时将永远不会回滚。
使用这些一致性保证,使在ZooKeeper客户端建立更高层次的功能非常简单,如领导选举,barriers,队列和read/write完全可撤销的锁。请参阅Recipes and Solutions了解更多详情。
Note
有时开发者错误的认为ZooKeeper实际上并没做这样的保证,这是:
同时保持一致的跨客户端视图
ZooKeeper不保证在每个实例中,两个不同的客户端将具有相同的ZooKeeper数据视图。 由于网络延迟等因素,一个客户端可能会在另一个客户端收到更改通知之前执行更新。考虑两个客户端A和B的情况。如果客户端A将znode/a的值从0设置为1,然后告诉客户端B读取/a,则客户端B可以读取旧值0,具体取决于它连接到哪个服务器。如果客户端A和客户端B读取相同的值很重要,则客户端B在执行读取操作之前应该先从ZooKeeper API方法调用sync()方法。
因此,ZooKeeper本身并不保证所有服务器都会同步发生更改,但ZooKeeper原语可用于构建提供有用客户端同步的更高级别的函数。(有关更多信息,请参阅ZooKeeper Recipes)。
Bindings
ZooKeeper客户端库有两种语言:Java和C,以下各节描述了这些。
Java Binding
有两个包构成ZooKeeper Java绑定:org.apache.zookeeper和org.apache.zookeeper.data。组成ZooKeeper的其余软件包,在内部使用或者是服务器实现的一部分。org.apache.zookeeper.data包由生成的类组成,这些类仅用作容器。
ZooKeeper Java客户端使用的主类是ZooKeeper类。它的两个构造函数不同之处,只是一个可选的会话ID和密码。ZooKeeper支持跨进程实例的会话恢复。 Java程序可以将其会话标识和密码保存到稳定的存储中,重新启动并恢复该程序早期实例使用的会话。
当一个ZooKeeper对象被创建时,也会创建两个线程:IO线程和事件线程。有IO都发生在IO线程上(使用Java NIO)。所有事件回调都发生在事件线程上。会话维护(例如重新连接到ZooKeeper服务器并维护心跳)在IO线程上完成。同步方法的响应也在IO线程中处理。事件线程处理异步方法和监视事件的所有响应。这个设计有几件事要注意:
- 所有异步调用和观察者回调的完成都将按顺序进行。调用者可以执行他们希望的任何处理,但在此期间不会处理其他回调。
- 回调不会阻止IO处理线程或处理同步调用。
- 异步调用可能不会以正确的顺序返回。例如,假设客户端执行以下处理:发出一个异步读取节点/a,并将watch设置为true,然后在读取的完成回调中执行同步读取/a。(也许不是很好的做法,但也不是非法的,它只是一个简单的例子。)请注意,如果在异步读取和同步读取之间的改变/a,客户端会在同步读取收响应之前,客户端库就收到了/a改变的watch事件。但是因为正在进行的回掉函数回调阻塞了事件队列,在监视事件处理之前,同步读取将返回/a新值。
最后,关闭相关的规则是非长简单直接的:如果ZooKeeper对象关闭或接收到致命事件(SESSION_EXPIRED和AUTH_FAILED),则ZooKeeper对象将变为无效。结束时,两个线程关闭,zookeeper句柄的任何进一步访问都是未定义的行为,应该避免。
C Binding
(…略…)
Installation
(…略…)
Building Your Own C Client
(…略…)
Building Blocks: A Guide to ZooKeeper Operations
本节将考察开发人员可以针对ZooKeeper服务器执行的所有操作。它比本手册中的早期概念章节更低级别的信息,但比ZooKeeper API参考级别更高。它涵盖了以下主题
- Connecting to ZooKeeper
Handling Errors
Java和C客户端绑定可能会报告错误。Java客户端绑定通过抛出KeeperException来执行此操作,对该异常调用code()将返回特定的错误代码。C客户端绑定返回枚举ZOO_ERRORS中定义的错误代码。API回调指明两种语言绑定的结果代码。有关可能的错误及其含义的完整详细信息,请参阅API文档(适用于Java的javadoc,适用于C的doxygen)。
Connecting to ZooKeeper
Read Operations
Write Operations
Handling Watches
Miscelleaneous ZooKeeper Operations
Program Structure, with Simple Example
Gotchas: Common Problems and Troubleshooting
现在已经了解ZooKeeper。它的速度很快,很简单,你的应用程序可以工作,但是等等……有什么不对。以下是ZooKeeper用户陷入的一些误区:
如果您正在使用watch,则必须查找connected的watch事件。当一个ZooKeeper客户端与服务器断开连接时,在重新连接之前,您不会收到更改通知,如果你正在观察一个znode的存在,如果znode在断开连接时被创建和删除,您将错过该事件。
您必须测试ZooKeeper服务器故障。只要大多数服务器处于活动状态,ZooKeeper服务就可以承受失败继续服务。要问的问题是:您的应用程序可以处理它吗?在真实环境中,客户端与ZooKeeper的连接可能会中断(ZooKeeper服务器故障和网络分区是连接丢失的常见原因)。ZooKeeper客户端库负责恢复连接并让你知道发生了什么,但是你必须确保你恢复你的状态和失败的任何未完成的请求。了解您在测试环境中是否正确使用,而不是在生产中-使用由多个服务器组成的ZooKeeper服务进行测试,并使其重新启动。
客户端使用的ZooKeeper服务器列表必须与每个ZooKeeper服务器都有的列表匹配。如果客户列表是ZooKeeper服务器真实列表的子集,虽然不是最佳的,但也可以工作,但如果客户端列出的ZooKeeper服务器不在ZooKeeper集群中,则不适用。
请注意放置事务日志的位置。ZooKeeper中性能最重要的部分是事务日志。ZooKeeper必须在事务返回响应之前将事务同步到存储介质。专用的事务日志设备是实现良好性能的关键。日志放在繁忙的设备上会对性能产生不利影响,如果您只有一个存储设备,请将跟踪文件放在NFS上并增加snapshotCount;它不能消除这个问题,但它可以缓解这个问题。
正确设置Java最大堆大小,这对避免交换是非常重要的,不必要地进入磁盘,几乎肯定会导致性能降低。请记住,在ZooKeeper中,一切都是有序的,所以如果一个请求触及磁盘,所有其他排队的请求都会命中该磁盘。
为了避免交换,尝试将堆大小设置为您拥有的物理内存量减去操作系统和高速缓存所需的量,确定配置的最佳堆大小的最佳方法是运行负载测试。如果由于某种原因不能,保守估计并选择一个远低于可能导致机器交换的限制的数字。例如,在4G机器上,3G堆是保守估计。
除正式文档外,ZooKeeper开发人员还有其他几种信息来源。
ZooKeeper Whitepaper
雅虎通过对ZooKeeper设计和性能的权威讨论。
API Reference
雅虎通过对ZooKeeper设计和性能的权威讨论。
ZooKeeper Talk at the Hadoup Summit 2008
雅虎的Benjamin Reed对ZooKeeper的视频介绍
Barrier and Queue Tutorial
Flavio Junqueira出色的Java教程,使用ZooKeeper实现简单的barriers 和生产者 - 消费者队列。
ZooKeeper - A Reliable, Scalable Distributed Coordination System
odd Hoff撰写的一篇文章(2008年7月15日)
ZooKeeper Recipes
使用ZooKeeper对各种同步解决方案的实现进行伪级别讨论:事件处理,队列,锁定和两阶段提交。