ZooKeeper 程序员指南

开发使用 ZooKeeper 的分布式应用程序

Introduction

本文档是针对希望创建利用 ZooKeeper 协调服务的分布式应用程序的开发人员的指南。它包含概念和实践信息。

本指南的前四部分介绍了各种 ZooKeeper 概念的高级讨论。这些对于理解 ZooKeeper 的工作方式以及如何使用它都是必要的。它不包含源代码,但确实假定您熟悉与分布式计算相关的问题。第一组中的部分是:

接下来的四个部分提供了实用的编程信息。这些是:

本书以appendix结尾,其中包含指向其他与 ZooKeeper 相关的有用信息的链接。

本文档中的大多数信息被编写为可作为独立参考资料进行访问。但是,在启动第一个 ZooKeeper 应用程序之前,您可能至少应该阅读ZooKeeper 数据模型ZooKeeper 基本操作上的章节。

ZooKeeper 数据模型

ZooKeeper 具有分层的名称空间,非常类似于分布式文件系统。唯一的区别是,名称空间中的每个节点都可以具有与其关联的数据以及子级。就像拥有一个文件系统一样,该文件系统也允许文件成为目录。到节点的路径始终表示为规范,绝对,斜杠分隔的路径。没有相对参考。遵循以下约束,可以在路径中使用任何 unicode 字符:

  • 空字符( u0000)不能是路径名的一部分。 (这会导致 C 绑定出现问题.)

  • 以下字符不能使用,因为它们不能很好地显示或以混乱的方式呈现:\ u0001-\ u001F 和\ u007F

  • \u009F.

  • 不允许使用以下字符:\ ud800-uF8FF,\ uFFF0-uFFFF。

  • “。”字符可以用作其他名称的一部分,但“。”和“ ..”不能单独用于指示路径上的节点,因为 ZooKeeper 不使用相对路径。以下内容将无效:“/a/b /./ c”或“ /a/b/../c”。

  • 令牌“ zookeeper”被保留。

ZNodes

ZooKeeper 树中的每个节点都称为* znode *。 Znodes 维护一个统计信息结构,其中包括用于数据更改和 acl 更改的版本号。 stat 结构也有时间戳。版本号和时间戳一起使 ZooKeeper 可以验证缓存并协调更新。 znode 的数据每次更改时,版本号都会增加。例如,每当 Client 端检索数据时,它也会接收数据的版本。并且,当 Client 端执行更新或删除时,它必须提供要更改的 znode 数据的版本。如果它提供的版本与数据的实际版本不匹配,则更新将失败。 (可以忽略此行为.)

Note

Note

在分布式应用程序工程中,“节点”一词可以指代通用主机,服务器,集合体的成员,Client 端进程等。在 ZooKeeper 文档中,“ znodes *”指的是数据节点。 服务器是指组成 ZooKeeper 服务的机器; 仲裁对等体是指组成集合的服务器;Client 端是指使用 ZooKeeper 服务的任何主机或进程。

Znodes 是程序员访问的主要实体。它们具有几个 Feature,在这里值得一提。

Watches

Client 端可以在 znodes 上设置监视。对该 znode 的更改将触发手表,然后清除手表。监视触发时,ZooKeeper 向 Client 端发送通知。有关手表的更多信息,请参见ZooKeeper Watches部分。

Data Access

原子地读取和写入存储在名称空间中每个 znode 上的数据。读取将获取与 znode 关联的所有数据字节,而写入将替换所有数据。每个节点都有一个访问控制列表(ACL),用于限制谁可以做什么。

ZooKeeper 并非设计为通用数据库或大型对象存储。相反,它 Management 协调数据。这些数据可以采用配置,状态信息,集合点等形式。各种形式的协调数据的共同属性是它们相对较小:以千字节为单位。 ZooKeeperClient 端和服务器实现具有健全性检查,以确保 znode 的数据少于 1M,但数据应比平均少得多。在相对大的数据量上进行操作将导致某些操作比其他操作花费更多的时间,并且会影响某些操作的延迟,因为需要更多时间才能通过网络将更多数据移动到存储介质上。如果需要大数据存储,则处理此类数据的通常方式是将其存储在大容量存储系统(例如 NFS 或 HDFS)上,并存储指向 ZooKeeper 中存储位置的指针。

Ephemeral Nodes

ZooKeeper 还具有短暂节点的概念。只要创建 znode 的会话处于活动状态,这些 znode 就存在。会话结束时,将删除 znode。由于这种行为,短暂的 znode 不允许有孩子。

序列节点-唯一命名

创建 znode 时,您还可以要求 ZooKeeper 在路径末尾附加一个单调递增的计数器。该计数器对于父级 znode 是唯一的。计数器的格式为%010d-填充为 0(零)的 10 位数字(计数器以这种方式格式化以简化排序),即“ 0000000001”。有关此功能的使用示例,请参见Queue Recipe。注意:用于存储下一个序列号的计数器是父节点维护的带符号的 int(4 字节),当计数器的计数值超过 2147483647(结果为“ -2147483648”)时,计数器将溢出。

Container Nodes

在 3.5.3 中添加

ZooKeeper 具有容器 znodes 的概念。容器 znode 是特殊用途的 znode,可用于诸如领导者,锁等配方。当删除容器的最后一个子代时,容器将成为服务器将来要删除的候选对象。

给定此属性,您应该准备在容器 znodes 中创建子级时获取 KeeperException.NoNodeException。即在容器 znode 内创建子 znode 时,请始终检查 KeeperException.NoNodeException 并在容器 znode 发生时重新创建。

TTL Nodes

在 3.5.3 中添加

创建 PERSISTENT 或 PERSISTENT_SEQUENTIAL znode 时,可以选择为 znode 以毫秒为单位设置 TTL。如果 znode 在 TTL 内未修改且没有子代,它将成为服务器将来某个时候要删除的候选者。

注意:TTL 节点必须通过“系统”属性启用,因为默认情况下它们是禁用的。有关详情,请参见Administrator's Guide。如果尝试创建没有设置正确的 System 属性的 TTL 节点,则服务器将抛出 KeeperException.UnimplementedException。

在 ZooKeeper 中的时间

ZooKeeper 通过多种方式跟踪时间:

  • Zxid 对 ZooKeeper 状态的每次更改都会以* zxid *(ZooKeeperTransactionID)的形式接收戳记。这将向 ZooKeeper 公开所有更改的总 Sequences。每个更改将具有唯一的 zxid,并且如果 zxid1 小于 zxid2,则 zxid1 在 zxid2 之前发生。

  • 版本号 对节点的每次更改都会导致该节点的版本号之一增加。这三个版本号分别是版本(对 znode 的数据进行更改的次数),转换(对 znode 的子级进行更改的次数)和厌恶(对 znode 的 ACL 进行更改的次数)。

  • 滴答声 当使用多服务器 ZooKeeper 时,服务器使用滴答声来定义事件的计时,例如状态上载,会话超时,对等体之间的连接超时等。滴答时间仅通过最小会话超时(2 次)间接公开。滴答时间);如果 Client 端请求的会话超时时间小于最小会话超时时间,则服务器将告知 Client 端该会话超时时间实际上是最小会话超时时间。

  • 实时 ZooKeeper 根本不使用实时或时钟时间,只是在创建 znode 和修改 znode 时将时间戳记入 stat 结构中。

ZooKeeper 统计结构

ZooKeeper 中每个 znode 的 Stat 结构由以下字段组成:

  • czxid 导致创建此 znode 的更改的 zxid。

  • mzxid 上次修改此 znode 的更改的 zxid。

  • pzxid 上次修改此 znode 子级的更改的 zxid。

  • ctime 创建此 znode 时从纪元开始的时间(以毫秒为单位)。

  • mtime 自上次修改此 znode 以来的时间(以纪元为单位)。

  • 版本号此 znode 的数据更改数。

  • cversion 此 znode 的子级更改的数量。

  • 版本 此 znode 的 ACL 的更改数量。

  • ephemeralOwner 如果 znode 是一个临时节点,则此 znode 的所有者的会话 ID。如果它不是临时节点,则它将为零。

  • dataLength 此 znode 的数据字段的长度。

  • numChildren 此 znode 的子级数。

ZooKeeper Sessions

ZooKeeperClient 端通过使用语言绑定创建服务的句柄来与 ZooKeeper 服务构建会话。创建句柄后,该句柄将开始处于 CONNECTING 状态,并且 Client 端库尝试连接到组成 ZooKeeper 服务的服务器之一,此时它将切换为 CONNECTED 状态。在正常操作期间,Client 端句柄将处于这两种状态之一。如果发生不可恢复的错误,例如会话到期或身份验证失败,或者如果应用程序显式关闭了句柄,则该句柄将移至 CLOSED 状态。下图显示了 ZooKeeperClient 端可能的状态转换:

State transitions

要创建 Client 端会话,应用程序代码必须提供一个连接字符串,其中包含用逗号分隔的 host:port 对列表,每个对对应于 ZooKeeper 服务器(例如“ 127.0.0.1:4545”或“ 127.0.0.1:3000,127.0.0.1” :3001,127.0.0.1:3002“)。 ZooKeeperClient 端库将选择一个任意服务器并尝试连接到该服务器。如果此连接失败,或者 Client 端由于任何原因与服务器断开连接,则 Client 端将自动尝试列表中的下一个服务器,直到(重新)构建连接为止。

在 3.2.0 中添加 :可选的“ chroot”后缀也可以附加到连接字符串中。这将在解释相对于该根目录的所有路径时运行 Client 端命令(类似于 unix 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”,Client 端将以该目录为根“/app/a”,所有路径都相对于此根目录-即,获取/设置/等...“/foo/bar”将导致操作在“/app/a/foo/bar”上运行(来自服务器角度)。此功能在多租户环境中特别有用,在该环境中,特定 ZooKeeper 服务的每个用户都可以有不同的根。这使重用变得更加简单,因为每个用户都可以将自己的应用程序编码为“ /”,而实际位置(例如/ app/a)可以在部署时确定。

当 Client 端获得 ZooKeeper 服务的句柄时,ZooKeeper 会创建一个分配给 Client 端的 ZooKeeper 会话(表示为 64 位数字)。如果 Client 端连接到其他 ZooKeeper 服务器,它将发送会话 ID 作为连接握手的一部分。为了安全起见,服务器会为任何 ZooKeeper 服务器都可以验证的会话 ID 创建一个密码。当 Client 端构建会话时,该密码将与会话 ID 一起发送给 Client 端。每当 Client 端与新服务器重新构建会话时,Client 端都会使用会话 ID 发送此密码。

创建一个 ZooKeeper 会话的 ZooKeeperClient 端库调用的参数之一是会话超时(以毫秒为单位)。Client 端发送请求的超时,服务器以其可以给 Client 端的超时作为响应。当前实现要求超时至少是 tickTime 的 2 倍(在服务器配置中设置),最大是 tickTime 的 20 倍。 ZooKeeperClient 端 API 允许访问协商的超时。

当 Client 端(会话)从 ZK 服务群集中进行分区时,它将开始搜索在会话创建过程中指定的服务器列表。最终,当 Client 端与至少一台服务器之间的连接重新构建时,会话将再次转换为“已连接”状态(如果在会话超时值内重新连接)或将转换为“已过期”状态(如果会话超时后重新连接)。不建议创建一个新的会话对象(在 c 绑定中一个新的 ZooKeeper.class 或 zookeeper 句柄)来断开连接。 ZKClient 端库将为您处理重新连接。特别是,我们在 Client 端库中内置了启发式方法,以处理“群效应”等问题。仅在收到会话到期通知时(强制性),才创建一个新会话。

会话到期由 ZooKeeper 群集本身 Management,而不由 Client 端 Management。当 ZKClient 端与群集构建会话时,它将提供上面详述的“超时”值。群集使用此值来确定 Client 端会话何时过期。当群集在指定的会话超时时间内(例如,无心跳)未收到 Client 端的消息时,就会发生过期。在会话期满时,群集将删除该会话拥有的任何/所有临时节点,并立即将该更改通知任何/所有连接的 Client 端(任何监视这些 znode 的人)。此时,过期会话的 Client 端仍与群集断开连接,除非/除非它能够重新构建与群集的连接,否则会话过期将不会收到通知。Client 端将保持断开连接状态,直到与群集重新构建 TCP 连接为止,届时,过期会话的观察者将收到“会话过期”通知。

过期会话的观察者可以看到过期会话的状态转换示例:

  • “已连接”:会话已构建,Client 端正在与集群通信(Client 端/服务器通信正常运行)

  • ....Client 端已从群集中分区

  • “断开连接”:Client 端失去与群集的连接

  • ....时间已过去,在“超时”时间段之后,群集使会话过期,由于 Client 端与群集断开连接,因此 Client 端看不到任何内容

  • ....随着时间的流逝,Client 端重新获得了与群集的网络级连接

  • 'expired':最终 Client 端重新连接到集群,然后通知其到期

ZooKeeper 会话构建调用的另一个参数是默认观察者。当 Client 端发生任何状态更改时,将向观察者通知。例如,如果 Client 端失去与服务器的连接,则将通知 Client 端,或者 Client 端的会话到期,等等。此观察者应考虑将初始状态断开(即,在任何状态更改事件之前,将事件发送给观察者)。Client 端库)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。

Client 端发送的请求使会话保持活动状态。如果会话空闲一段时间会使会话超时,则 Client 端将发送 PING 请求以使会话保持活动状态。此 PING 请求不仅允许 ZooKeeper 服务器知道 Client 端仍处于活动状态,而且还允许 Client 端验证其与 ZooKeeper 服务器的连接仍处于活动状态。 PING 的时间足够保守,以确保有合理的时间检测到无效连接并重新连接到新服务器。

成功构建(连接)到服务器的连接后,基本上有两种情况,当同步或同步时,Client 端库会生成连接丢失(c 绑定中的结果代码,Java 中的异常-有关绑定的详细信息,请参阅 API 文档)。执行异步操作,并且保留以下条件之一:

  • 应用程序在不再有效/有效的会话上调用操作

  • 当对该服务器有待处理的操作时,即存在一个待处理的异步调用,ZooKeeperClient 端将从服务器断开连接。

已在 3.2.0 中添加-SessionMovedException 。Client 端通常看不见内部异常,称为 SessionMovedException。发生此异常的原因是,在连接上收到了针对已在其他服务器上重新构建的会话的请求。导致此错误的正常原因是 Client 端向服务器发送请求,但是网络数据包被延迟,因此 Client 端超时并连接到新服务器。当延迟的数据包到达第一台服务器时,旧服务器将检测到会话已移动,并关闭 Client 端连接。Client 端通常不会看到此错误,因为它们不会从那些旧 Connecting 读取。 (通常会关闭旧的连接.)可以看到这种情况的一种情况是,两个 Client 端尝试使用保存的会话 ID 和密码来重新构建相同的连接。其中一个 Client 端将重新构建连接,而第二个 Client 端将被断开连接(导致该对尝试无限期地重新构建其连接/会话)。

更新服务器列表 。我们允许 Client 端通过提供一个新的以逗号分隔的 host:port 对列表来更新连接字符串,每个对分别对应于 ZooKeeper 服务器。该函数调用概率负载平衡算法,该算法可能导致 Client 端与当前主机断开连接,以期在新列表中实现每个服务器的预期统一连接数。如果 Client 端连接的当前主机不在新列表中,则此调用将始终导致连接断开。否则,将基于服务器数量是增加还是减少以及增加多少来做出决定。

例如,如果以前的连接字符串包含 3 个主机,而现在列表中包含这 3 个主机和 2 个其他主机,则连接到 3 个主机中的每个主机的 40%的 Client 端将移动到其中一个新主机,以平衡负载。该算法将导致 Client 端以 0.4 的概率断开其与之连接的当前主机的连接,在这种情况下,将导致 Client 端连接至随机选择的 2 个新主机之一。

另一个示例-假设我们有 5 台主机,现在更新列表以删除其中 2 台主机,连接到其余 3 台主机的 Client 端将保持连接状态,而连接到 2 台删除主机的所有 Client 端将需要移动到其中一台 3 个主机,随机选择。如果断开连接,Client 端将进入一种特殊模式,在该模式下,Client 端将选择一个新服务器来使用概率算法进行连接,而不仅仅是轮询。

在第一个示例中,每个 Client 端决定以 0.4 的概率断开连接,但是一旦做出决定,它将尝试连接到随机的新服务器,并且只有当它无法连接到任何新服务器时,它才会尝试连接到旧服务器。那些。找到服务器或尝试新列表中的所有服务器且连接失败后,Client 端将返回到正常操作模式,在该模式下,Client 端从 connectString 中选择任意服务器并尝试连接至该服务器。如果失败,它将 continue 循环尝试其他随机服务器。 (请参见上文最初用于选择服务器的算法)

ZooKeeper Watches

ZooKeeper 中的所有读取操作- getData()getChildren()exists() -都可以选择将手表设置为副作用。这是 ZooKeeper 对手表的定义:手表事件是一次触发,发送给设置手表的 Client 端,该事件在设置手表的数据发生更改时发生。在手表的定义中,需要考虑三个关键点:

  • 一次性触发器 数据更改后,一个监视事件将发送给 Client 端。例如,如果 Client 端执行 getData(“/znode1”,true),然后/ znode1 的数据被更改或删除,则 Client 端将获得/ znode1 的监视事件。如果/ znode1 再次更改,则除非 Client 端进行了另一次读取来设置新的监视,否则不会发送任何监视事件。

  • 发送给 Client 端 这意味着一个事件正在传递给 Client 端,但是在更改操作成功返回代码到达发起更改的 Client 端之前,可能不会到达 Client 端。手表被异步发送给观察者。 ZooKeeper 提供了 Order 保证:Client 端在第一次看到监视事件之前,将永远不会看到为其设置了监视的更改。网络延迟或其他因素可能导致不同的 Client 端在不同的时间看到监视并从更新中返回代码。关键是不同 Client 看到的所有内容将具有一致的 Sequences。

  • 为其设置手表的数据 这是指节点可以更改的不同方式。可以将 ZooKeeper 视为维护两个手表列表:数据手表和 child 手表。 getData()和 exist()设置数据监视。 getChildren()设置 child 手表。或者,可以考虑根据返回的数据类型设置手表。 getData()和 exist()返回有关节点数据的信息,而 getChildren()返回子级列表。因此,setData()将触发数据监视是否设置了 znode(假设设置成功)。成功的 create()将触发对正在创建的 znode 的数据监视,以及对父 znode 的子监视。成功的 delete()将同时触发要删除的 znode 的数据监视和子监视(因为不可能有更多的子监视)以及父 znode 的子监视。

手表在 Client 端连接到的 ZooKeeper 服务器上本地维护。这使手表的重量可以轻巧地设置,维护和调度。当 Client 端连接到新服务器时,将监视所有会话事件。与服务器断开连接时,不会收到手表。当 Client 端重新连接时,任何先前注册的手表将被重新注册并在需要时触发。通常,所有这些都是透明发生的。在某些情况下,可能会丢失监视:如果在断开连接时创建并删除了 znode,则会丢失尚未存在的 znode 的监视。

手表的语义

我们可以使用三个读取 ZooKeeper 状态的调用来设置手表:exist,getData 和 getChildren。以下列表详细说明了手表可以触发的事件以及启用它们的调用:

  • 已创建的事件: 已启用,并调用存在。

  • 已删除事件: 通过调用 exist,getData 和 getChildren 启用。

  • 更改的事件: 通过调用 exist 和 getData 启用。

  • Child 事件: 通过调用 getChildren 启用。

Remove Watches

我们可以通过调用 removeWatches 来删除在 znode 上注册的手表。另外,即使没有服务器连接,ZooKeeperClient 端也可以通过将本地标志设置为 true 来在本地删除手表。以下列表详细说明了成功删除手表后将触发的事件。

  • Child Remove 事件: Watcher,它是通过调用 getChildren 来添加的。

  • 数据删除事件: 监视程序是通过对 exist 或 getData 的调用而添加的。

ZooKeeper 对手表的保证

关于手表,ZooKeeper 保持以下保证:

  • 监视是针对其他事件,其他监视和异步答复而排序的。 ZooKeeperClient 端库可确保按 Sequences 分派所有内容。

  • Client 端将看到它正在监视的 znode 的监视事件,然后才能看到与该 znode 对应的新数据。

  • ZooKeeper 中监视事件的 Sequences 与 ZooKeeper 服务看到的更新 Sequences 相对应。

手表须知

  • 手表是一次触发。如果您收到监视事件,并且希望收到有关将来更改的通知,则必须设置另一个监视。

  • 由于监视是一次触发,并且在获取事件和发送新请求以获取监视之间存在延迟,因此您无法可靠地看到 ZooKeeper 中节点发生的每项更改。准备处理 znode 在获取事件和重新设置手表之间多次更改的情况。 (您可能不在乎,但至少意识到可能会发生.)

  • 对于给定的通知,监视对象或功能/上下文对将仅被触发一次。例如,如果为一个存在注册了相同的监视对象,并且对同一文件进行了 getData 调用,然后删除了该文件,则监视对象将仅使用该文件的删除通知被调用一次。

  • 与服务器断开连接时(例如,服务器发生故障时),直到重新构建连接后,您才能获得任何监视。因此,会话事件将发送到所有出色的监视处理程序。使用会话事件进入安全模式:断开连接后,您将不会收到事件,因此您的进程应在该模式下谨慎行事。

使用 ACL 的 ZooKeeper 访问控制

ZooKeeper 使用 ACL 来控制对其 znode(ZooKeeper 数据树的数据节点)的访问。 ACL 实现与 UNIX 文件访问权限非常相似:它使用权限位来允许/禁止对节点及其所应用范围的各种操作。与标准 UNIX 权限不同,ZooKeeper 节点不受用户(文件所有者),组和世界(其他)的三个标准范围的限制。 ZooKeeper 没有 znode 所有者的概念。而是,ACL 指定一组 ID 和与这些 ID 相关联的权限。

还请注意,ACL 仅与特定的 znode 有关。特别是它不适用于 child。例如,如果*/app 仅可被 ip:172.16.16.1 读取,并且/app/status 可被世界读取,则任何人都可以阅读/app/status *; ACL 不是递归的。

ZooKeeper 支持可插入身份验证方案。使用* scheme:expression 形式指定 ID,其中 scheme 是 ID 所对应的身份验证方案。有效表达式集由方案定义。例如, ip:172.16.16.1 是使用 ip 方案的地址为 172.16.16.1 的主机的 ID,而 digest:bob:password 是名称为的用户的 ID bob 使用 digest *方案。

当 Client 端连接到 ZooKeeper 并对其进行身份验证时,ZooKeeper 会将与 Client 端相对应的所有 ID 与 Client 端连接相关联。当 Client 端尝试访问节点时,将根据 znodes 的 ACL 检查这些 ID。 ACL 由*(scheme:expression,perms)*对组成。 * expression 的格式特定于该方案。例如,对(ip:19.22.0.0/16,READ)对 IP 地址以 19.22 开头的任何 Client 端提供 READ *许可。

ACL Permissions

ZooKeeper 支持以下权限:

  • 创建 :您可以创建一个子节点

  • 读取 :您可以从节点获取数据并列出其子节点。

  • WRITE :您可以为节点设置数据

  • 删除 :您可以删除一个子节点

  • ADMIN :您可以设置权限

  • CREATE DELETE 权限已脱离 WRITE *权限,以实现更细粒度的访问控制。 * CREATE DELETE *的情况如下:

您希望 A 能够在 ZooKeeper 节点上进行设置,但不能* CREATE DELETE *子级。

创建而不删除:Client 端通过在父目录中创建 ZooKeeper 节点来创建请求。您希望所有 Client 端都能添加,但是只有请求处理器可以删除。 (这有点像文件的 APPEND 权限.)

另外,由于 ZooKeeper 没有文件所有者的概念,因此具有* ADMIN 权限。在某种意义上, ADMIN *权限将实体指定为所有者。 ZooKeeper 不支持 LOOKUP 权限(在目录上执行权限位以允许您进行 LOOKUP,即使您无法列出目录)。每个人都隐式具有 LOOKUP 权限。这使您可以统计节点,但仅此而已。 (问题是,如果要在不存在的节点上调用 zoo_exists(),则没有检查权限。)

  • ADMIN 权限在 ACL 方面也具有特殊作用:为了检索 znode 用户的 ACL,必须具有 READ ADMIN 权限,但没有 ADMIN *权限,摘要哈希值将被屏蔽。

内置 ACL 方案

ZooKeeeper 具有以下内置方案:

  • world 有一个 ID,* anyone *,代表任何人。

  • auth 是一种特殊的方案,它会忽略任何提供的表达式,而是使用当前的用户,凭据和方案。当持久化 ACL 时,ZooKeeper 服务器将忽略提供的任何表达式(无论是* user 还是 SASL 认证,还是 user:password 都是 DIGEST 认证)。但是,仍必须在 ACL 中提供该表达式,因为 ACL 必须与 scheme:expression:perms *形式匹配。提供此方案是为了方便,因为它是用户创建 znode 然后将对该 znode 的访问限制为仅该用户的常见用例。如果没有经过身份验证的用户,则使用身份验证方案设置 ACL 将失败。

  • digest 使用* username:password 字符串生成 MD5 哈希,然后将其用作 ACL ID 标识。通过以明文形式发送 username:password 来完成认证。在 ACL 中使用时,表达式将是 username:base64 编码 SHA1 密码 digest *。

  • ip 使用 Client 端主机 IP 作为 ACL ID 身份。 ACL 表达式的格式为* addr/bits ,其中 addr 的最高有效 bits 与 Client 端主机 IP 的最高有效* bits *相匹配。

  • x509 使用 Client 端 X500 主体作为 ACL ID 身份。 ACL 表达式是 Client 端的确切 X500 主体名称。使用安全端口时,将自动对 Client 端进行身份验证,并设置其 x509 方案的身份验证信息。

ZooKeeper CClient 端 API

ZooKeeper C 库提供以下常量:

    • const * * int * ZOO_PERM_READ; //可以读取节点的值并列出其子节点
    • const * * int * ZOO_PERM_WRITE; //可以设置节点的值
    • const * * int * ZOO_PERM_CREATE; //可以创建孩子
    • const * * int * ZOO_PERM_DELETE; //可以删除子级
    • const * * int * ZOO_PERM_ADMIN; //可以执行 set_acl()
    • const * * int * ZOO_PERM_ALL; //以上所有标志或在一起

以下是标准的 ACL ID:

  • 结构 ID ZOO_ANYONE_ID_UNSAFE; //('世界','任何人')

    • struct * ID ZOO_AUTH_IDS; //('auth','')

ZOO_AUTH_IDS 空标识字符串应解释为“创建者的标识”。

ZooKeeperClient 端带有三个标准 ACL:

    • struct * ACL_vector ZOO_OPEN_ACL_UNSAFE; //(ZOO_PERM_ALL,ZOO_ANYONE_ID_UNSAFE)
    • struct * ACL_vector ZOO_READ_ACL_UNSAFE; //(ZOO_PERM_READ,ZOO_ANYONE_ID_UNSAFE)
    • struct * ACL_vector ZOO_CREATOR_ALL_ACL; //(ZOO_PERM_ALL,ZOO_AUTH_IDS)

ZOO_OPEN_ACL_UNSAFE 对所有 ACL 完全免费开放:任何应用程序都可以在节点上执行任何操作,并且可以创建,列出和删除其子级。 ZOO_READ_ACL_UNSAFE 是任何应用程序的只读访问权限。 CREATE_ALL_ACL 授予节点创建者所有权限。创建者必须已通过服务器身份验证(例如,使用“ * digest *”方案),然后才能使用此 ACL 创建节点。

以下 ZooKeeper 操作处理 ACL:

    • int * * zoo_add_auth (zhandle_t * zh, const * * char * 方案, const * * char * * cert,* int * certLen,void_completion_t 完成,* const * * void * *数据);

应用程序使用 zoo_add_auth 函数向服务器进行身份验证。如果应用程序要使用不同的方案和/或身份进行身份验证,则可以多次调用该功能。

    • int * * zoo_create (zhandle_t * zh, const * * char * * path,* const * * char * * value,* int * valuelen,* const * * struct * ACL_vector * acl,* int 标志, char * * realpath,* int * max_realpath_len);

zoo_create(...)操作将创建一个新节点。 acl 参数是与节点关联的 ACL 的列表。父节点必须设置 CREATE 权限位。

    • int * * zoo_get_acl (zhandle_t * zh, const * * char * * path,* struct * ACL_vector * acl,* struct * Stat * stat);

此操作返回节点的 ACL 信息。该节点必须具有 READ 或 ADMIN 权限集。没有 ADMIN 许可,摘要哈希值将被屏蔽。

    • int * * zoo_set_acl (zhandle_t * zh, const * * char * * path,* int 版本, const * * struct * ACL_vector * acl);

此功能用新列表替换节点的 ACL 列表。该节点必须具有 ADMIN 权限集。

以下是一个示例代码,该示例代码使用上述 API 使用“ * foo *”方案对自身进行身份验证,并创建一个仅具有创建权限的临时节点“/xyz”。

Note

Note

这是一个非常简单的示例,旨在显示如何与 ZooKeeper ACL 进行专门的交互。有关 CClient 端实现的示例,请参阅* .../trunk/zookeeper-client/zookeeper-client-c/src/cli.c *

#include <string.h>
#include <errno.h>

#include "zookeeper.h"

static zhandle_t *zh;

/**
 * In this example this method gets the cert for your
 *   environment -- you must provide
 */
char *foo_get_cert_once(char* id) { return 0; }

/** Watcher function -- empty for this example, not something you should
 * do in real code */
void watcher(zhandle_t *zzh, int type, int state, const char *path,
         void *watcherCtx) {}

int main(int argc, char argv) {
  char buffer[512];
  char p[2048];
  char *cert=0;
  char appId[64];

  strcpy(appId, "example.foo_test");
  cert = foo_get_cert_once(appId);
  if(cert!=0) {
    fprintf(stderr,
        "Certificate for appid [%s] is [%s]\n",appId,cert);
    strncpy(p,cert, sizeof(p)-1);
    free(cert);
  } else {
    fprintf(stderr, "Certificate for appid [%s] not found\n",appId);
    strcpy(p, "dummy");
  }

  zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

  zh = zookeeper_init("localhost:3181", watcher, 10000, 0, 0, 0);
  if (!zh) {
    return errno;
  }
  if(zoo_add_auth(zh,"foo",p,strlen(p),0,0)!=ZOK)
    return 2;

  struct ACL CREATE_ONLY_ACL[] = {{ZOO_PERM_CREATE, ZOO_AUTH_IDS}};
  struct ACL_vector CREATE_ONLY = {1, CREATE_ONLY_ACL};
  int rc = zoo_create(zh,"/xyz","value", 5, &CREATE_ONLY, ZOO_EPHEMERAL,
                  buffer, sizeof(buffer)-1);

  /** this operation will fail with a ZNOAUTH error */
  int buflen= sizeof(buffer);
  struct Stat stat;
  rc = zoo_get(zh, "/xyz", 0, buffer, &buflen, &stat);
  if (rc) {
    fprintf(stderr, "Error %d for %s\n", rc, __LINE__);
  }

  zookeeper_close(zh);
  return 0;
}

可插拔 ZooKeeper 身份验证

ZooKeeper 在具有各种不同身份验证方案的各种不同环境中运行,因此它具有完全可插拔的身份验证框架。甚至内置的身份验证方案也使用可插入身份验证框架。

要了解身份验证框架的工作原理,首先必须了解两个主要的身份验证操作。框架首先必须验证 Client 端。通常在 Client 端连接到服务器后立即执行此操作,包括验证从 Client 端发送或收集的有关 Client 端的信息,并将其与连接相关联。框架处理的第二个操作是在 ACL 中查找与 Client 端相对应的条目。 ACL 条目为\ < idspec, permissions >对。 * idspec *可以是与连接相关联的身份验证信息的简单字符串匹配,也可以是对该信息进行评估的表达式。匹配由身份验证插件的实现决定。这是身份验证插件必须实现的接口:

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 服务器使用身份验证插件返回的方案来确定该方案适用于哪些 ID。

当 Client 端发送与连接关联的身份验证信息时,将调用* handleAuthentication 。Client 端指定信息所对应的方案。 ZooKeeper 服务器将信息传递给身份验证插件,该插件的 getScheme 与 Client 端传递的方案匹配。如果 handleAuthentication 的实现者确定信息不正确,则通常会返回错误,或者使用 cnxn.getAuthInfo()。add(new Id(getScheme(),data))*将信息与连接关联。

身份验证插件涉及设置和使用 ACL。为 znode 设置 ACL 后,ZooKeeper 服务器会将条目的 id 部分传递给* isValid(String id)方法。由插件验证 ID 是否具有正确的格式。例如, ip:172.16.0.0/16是有效的 ID,但 ip:host.com 不是。如果新的 ACL 包含“ auth”条目,则使用 isAuthenticated *来查看是否应将与连接相关联的该方案的身份验证信息添加到 ACL 中。某些方案不应包含在 auth 中。例如,如果指定了 auth,则不将 Client 端的 IP 地址视为应添加到 ACL 的 ID。

ZooKeeper 在检查 ACL 时调用* matches(String id,String aclExpr)。它需要将 Client 端的身份验证信息与相关的 ACL 条目进行匹配。要查找适用于 Client 端的条目,ZooKeeper 服务器将找到每个条目的方案,如果该方案有来自该 Client 端的身份验证信息,则将使用 id 调用 matches(String id,String aclExpr)。设置为先前通过 handleAuthentication aclExpr 设置为 ACL 条目 ID 的身份验证信息。身份验证插件使用自己的逻辑和匹配方案来确定 id 是否包含在 aclExpr *中。

有两个内置的身份验证插件:* ip digest 。其他插件可以使用系统属性进行添加。在启动时,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 *,则仅使用一个。同样,所有服务器都必须定义相同的插件,否则使用插件提供的身份验证方案的 Client 端将无法连接到某些服务器。

Consistency Guarantees

ZooKeeper 是一项高性能,可扩展的服务。读取和写入操作都被设计为快速的,尽管读取比写入快。原因是在读取的情况下,ZooKeeper 可以提供较旧的数据,这又是由于 ZooKeeper 的一致性保证:

  • Sequences 一致性:来自 Client 端的更新将按照发送的 Sequences 应用。

    • Atomicity *:更新成功或失败-没有部分结果。
  • 单个系统映像:无论 Client 端连接到哪个服务器,Client 端都将看到该服务的相同视图。

  • 可靠性:一旦应用了更新,该更新将一直持续到 Client 端覆盖该更新为止。此保证有两个推论:

  • 如果 Client 获得成功的返回码,则更新将被应用。在某些故障(通信错误,超时等)上,Client 端将不知道更新是否已应用。我们将采取措施以最大程度地减少失败,但保证仅包含成功的返回码。 (在 Paxos 中称为“单调性条件”.)

  • 从服务器故障中恢复时,Client 端通过读取请求或成功更新看到的任何更新都不会回滚。

  • 及时:保证系统的 Client 视图在一定的时间范围内(数十秒)是最新的。Client 端将在此范围内看到系统更改,或者 Client 端将检测到服务中断。

使用这些一致性可以确保仅在 ZooKeeperClient 端上就可以轻松构建高层功能,例如领导者选举,障碍,队列和读/写可撤销锁(无需对 ZooKeeper 进行任何添加)。有关更多详细信息,请参见食谱和解决方案

Note

Note

有时,开发人员错误地假设了另一个保证,即 ZooKeeper 确实没有做到。这是:同时一致的跨 Client 端视图:ZooKeeper 不能保证在每个实例的时间,两个不同的 Client 端将具有相同的 ZooKeeper 数据视图。由于诸如网络延迟之类的因素,一个 Client 端可能会在另一 Client 端收到更改通知之前执行更新。考虑两个 Client 端 A 和 B 的情况。如果 Client 端 A 将 znode/a 的值从 0 设置为 1,然后告诉 Client 端 B 读取/ a,则 Client 端 B 可能读取旧值 0,具体取决于哪个服务器。它连接到。如果 Client 端 A 和 Client 端 B 读取相同的值很重要,则 Client 端 B 应该在执行读取之前从 ZooKeeper API 方法中调用 sync() 方法。因此,ZooKeeper 本身不能保证所有服务器上的更改都是同步发生的,但是 ZooKeeperPrimitives 可以用来构造更高级别的功能,以提供有用的 Client 端同步。 (有关更多信息,请参见ZooKeeper Recipes

Bindings

ZooKeeperClient 端库有两种语言:Java 和 C。以下各节对此进行了描述。

Java Binding

ZooKeeper Java 绑定有两个软件包:org.apache.zookeeper 和 org.apache.zookeeper.data 。构成 ZooKeeper 的其余软件包在内部使用或作为服务器实现的一部分。 org.apache.zookeeper.data 软件包由生成的类组成,这些类仅用作容器。

ZooKeeper JavaClient 端使用的主要类是 ZooKeeper 类。它的两个构造函数的区别仅在于可选的会话 ID 和密码。 ZooKeeper 支持跨流程实例的会话恢复。 Java 程序可以将其会话 ID 和密码保存到稳定的存储器中,然后重新启动并恢复该程序的较早实例所使用的会话。

创建 ZooKeeper 对象时,还将创建两个线程:IO 线程和事件线程。所有 IO 都发生在 IO 线程上(使用 Java NIO)。所有事件回调都在事件线程上发生。会话维护(例如重新连接到 ZooKeeper 服务器和维护心跳)在 IO 线程上完成。同步方法的响应也在 IO 线程中处理。对异步方法和监视事件的所有响应都在事件线程上处理。有一些事情要注意这种设计的结果:

  • 异步调用和监视程序回调的所有完成将按 Sequences 进行,一次完成。调用方可以执行他们希望的任何处理,但是在此期间将不处理其他回调。

  • 回调不会阻止 IO 线程的处理或同步调用的处理。

  • 同步呼叫可能不会以正确的 Sequences 返回。例如,假设 Client 端进行了以下处理:发出对节点 /a 的异步读取,并将* watch *设置为 true,然后在读取的完成回调中进行对 /a 的同步读取 。 (也许不是很好的做法,但也不是非法的,这举了一个简单的例子.)请注意,如果异步读取和同步读取之间的 /a 发生更改,则 Client 端库将收到该监视事件说 /a 在同步读取的响应之前已更改,但是由于完成回调阻止了事件队列,因此在处理监视事件之前,同步读取将以新值 /a 返回。

最终,与关闭相关的规则很简单:关闭 ZooKeeper 对象或收到致命事件(SESSION_EXPIRED 和 AUTH_FAILED),ZooKeeper 对象将失效。最后,两个线程关闭,对 Zookeeper 句柄的任何进一步访问都是未定义的行为,应避免。

Client 端配置参数

以下列表包含 JavaClient 端的配置属性。您可以使用 Java 系统属性来设置任何这些属性。有关服务器属性,请检查Management 指南中的服务器配置部分。 ZooKeeper Wiki 还具有关于ZooKeeper SSL 支持ZooKeeper 的 SASL 身份验证的有用页面。

    • zookeeper.sasl.client *:将值设置为 false 以禁用 SASL 身份验证。默认为 true
    • zookeeper.sasl.clientconfig *:指定 JAAS 登录文件中的上下文密钥。默认值为“Client 端”。
    • zookeeper.server.principal *:指定启用 Kerberos 身份验证时,连接到 Zookeeper 服务器时,Client 端用于身份验证的服务器主体。如果提供了此配置,则 ZooKeeperClient 端将不使用以下任何参数来确定服务器主体:zookeeper.sasl.client.username,zookeeper.sasl.client.canonicalize.hostname,zookeeper.server.realm 注意: config 参数仅适用于 ZooKeeper 3.5.7、3.6.0
    • zookeeper.sasl.client.username *:传统上,主体分为三部分:主要部分,实例和领域。典型的 Kerberos V5 主体的格式为 primary/instance @ REALM。 zookeeper.sasl.client.username 指定服务器主体的主要部分。默认值为“ zookeeper”。实例部分是从服务器 IP 派生的。最后,服务器的主体是 username/IP @ realm,其中 username 是 zookeeper.sasl.client.username 的值,IP 是服务器 IP,而 realm 是 zookeeper.server.realm 的值。
    • zookeeper.sasl.client.canonicalize.hostname *:如果未提供 zookeeper.server.principal 参数,则 ZooKeeperClient 端将尝试确定 ZooKeeper 服务器主体的“实例”(主机)部分。首先,它使用提供的主机名作为 ZooKeeper 服务器连接字符串。然后,它尝试通过获取属于该地址的标准域名来“规范化”该地址。您可以通过设置禁用此“规范化”:zookeeper.sasl.client.canonicalize.hostname = false
    • zookeeper.server.realm *:服务器主体的领域部分。默认情况下,它是 Client 端主体领域。
    • zookeeper.disableAutoWatchReset *:此开关控制是否启用自动手表重置。Client 端默认在会话重新连接期间自动重置手表,此选项允许 Client 端通过将 zookeeper.disableAutoWatchReset 设置为 true 来关闭此行为。
    • zookeeper.client.secure *:如果要连接到服务器安全 Client 端端口,则需要在 Client 端上将此属性设置为“ true”。这将使用具有指定凭据的 SSL 连接到服务器。请注意,它需要 NettyClient 端。
    • zookeeper.clientCnxnSocket *:指定要使用的 ClientCnxnSocket。可能的值为 org.apache.zookeeper.ClientCnxnSocketNIO 和 org.apache.zookeeper.ClientCnxnSocketNetty 。默认值为 org.apache.zookeeper.ClientCnxnSocketNIO 。如果要连接到服务器的安全 Client 端端口,则需要在 Client 端上将此属性设置为 org.apache.zookeeper.ClientCnxnSocketNetty **。
    • zookeeper.ssl.keyStore.location 和 zookeeper.ssl.keyStore.password *:指定到 JKS 的文件路径,该文件包含用于 SSL 连接的本地凭据,以及用于解锁文件的密码。
    • zookeeper.ssl.trustStore.location 和 zookeeper.ssl.trustStore.password *:指定包含要用于 SSL 连接的远程凭据的 JKS 的文件路径,以及用于解锁文件的密码。
    • jute.maxbuffer *:它指定来自服务器的传入数据的最大大小。默认值为 4194304 字节,或仅为 4 MB。这确实是一个健全性检查。 ZooKeeper 服务器旨在存储和发送千字节量级的数据。如果传入数据长度大于此值,则引发 IOException。
    • zookeeper.kinit *:指定 kinit 二进制文件的路径。默认值为“/usr/bin/kinit”。

C Binding

C 绑定具有一个单线程和多线程库。多线程库最易于使用,并且与 Java API 最相似。该库将创建一个 IO 线程和一个事件分发线程,用于处理连接维护和回调。通过暴露多线程库中使用的事件循环,单线程库允许 ZooKeeper 在事件驱动的应用程序中使用。

该软件包包括两个共享库:zookeeper_st 和 zookeeper_mt。前者仅提供异步 API 和回调,以集成到应用程序的事件循环中。该库存在的唯一原因是支持平台,因为* pthread *库不可用或不稳定(即 FreeBSD 4.x)。在所有其他情况下,应用程序开发人员应与 zookeeper_mt 链接,因为它同时支持 Sync 和 Async API。

Installation

如果要通过从 Apache 存储库中签出构建 Client 端,请遵循以下概述的步骤。如果您是从 apache 下载的项目源软件包中构建的,请跳至步骤 3

  • 从 ZooKeeper 顶层目录(* .../trunk )中运行ant compile_jute。这将在 .../trunk/zookeeper-client/zookeeper-client-c *下创建一个名为“ generated”的目录。

  • 将目录更改为* .../trunk/zookeeper-client/zookeeper-client-c *并运行autoreconf -if以引导 autoconf ,automake 和 libtool .确保已安装 autoconf 版本 2.59 或更高版本.跳至步骤 4 **。

  • 如果您是从项目源程序包构建的,请将源 tarball 和 cd 解压缩/解压缩到* zookeeper-x.x.x/zookeeper-client/zookeeper-client-c *目录。

  • 运行./configure <your-options>以生成 makefile。以下是 configure Util 支持的一些选项,它们在此步骤中可能有用:

  • --enable-debug启用优化并启用调试信息编译器选项。 (默认情况下禁用.)

  • --without-syncapi禁用 Sync API 支持; zookeeper_mt 库将不会构建。 (默认情况下启用.)

  • --disable-static不要构建静态库。 (默认情况下启用.)

  • --disable-shared不要构建共享库。 (默认情况下启用.)

Note

Note

有关运行 configure 的一般信息,请参阅 INSTALL。 1.运行makemake install来构建库并安装它们。 1.要为 ZooKeeper API 生成 doxygen 文档,请运行make doxygen-doc。所有文档都将放置在名为 docs 的新子文件夹中。默认情况下,此命令仅生成 HTML。有关其他文档格式的信息,请运行./configure --help

构建自己的 CClient

为了能够在您的应用程序中使用 ZooKeeper C API,您必须记住

  • 包括 ZooKeeperHeaders:#include <zookeeper/zookeeper.h>

  • 如果要构建多线程 Client 端,请使用-DTHREADED编译器标志进行编译以启用该库的多线程版本,然后针对* zookeeper_mt *库进行链接。如果要构建单线程 Client 端,请不要使用-DTHREADED进行编译,并确保针对 the_zookeeper_st_library 进行链接。

Note

Note

有关 CClient 端实现的示例,请参阅* .../trunk/zookeeper-client/zookeeper-client-c/src/cli.c *

构建基块:ZooKeeper 操作指南

本节调查开发人员可以对 ZooKeeper 服务器执行的所有操作。它是比本手册前面的概念章更低级别的信息,但比《 ZooKeeper API 参考》更高的级别。

Handling Errors

Java 和 CClient 端绑定都可能报告错误。 JavaClient 端绑定通过抛出 KeeperException 来做到这一点,在异常上调用 code()将返回特定的错误代码。 CClient 端绑定返回如枚举 ZOO_ERRORS 中定义的错误代码。 API 回调指示两种语言绑定的结果代码。有关可能的错误及其含义的完整详细信息,请参阅 API 文档(Javadoc 为 Javadoc,doxygen 为 C)。

连接到 ZooKeeper

在开始之前,您必须设置一个正在运行的 Zookeeper 服务器,以便我们可以开始开发 Client 端。对于 CClient 端绑定,我们将使用带有 C 语言编写的简单示例的多线程库(zookeeper_mt)。要与 Zookeeper 服务器构建连接,我们使用 C API-* zookeeper_init *,并带有以下签名:

int zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);
      • host *:以 host:port 格式连接到 Zookeeper 服务器的连接字符串。如果有多个服务器,请在指定 host:port 对之后使用逗号作为分隔符。例如:“ 127.0.0.1:2181,127.0.0.1:3001,127.0.0.1:3002”
    • fn *:监视程序功能,用于在触发通知时处理事件。
    • recv_timeout *:会话到期时间(以毫秒为单位)。
    • clientid *:我们可以为新会话指定 0.如果先前已经构建了会话,我们可以提供该 Client 端 ID,它将重新连接到该先前的会话。
    • context *:可以与 zkhandle_t 处理程序关联的上下文对象。如果不使用,可以将其设置为 0.
    • flags *:在初始化中,我们可以将其保留为 0.

我们将演示 Client 端在成功连接后输出“ Connected to Zookeeper”的信息,否则将显示错误消息。让我们调用以下代码* zkClient.cc *:

#include <stdio.h>
#include <zookeeper/zookeeper.h>
#include <errno.h>
using namespace std;

// Keeping track of the connection state
static int connected = 0;
static int expired   = 0;

// *zkHandler handles the connection with Zookeeper
static zhandle_t *zkHandler;

// watcher function would process events
void watcher(zhandle_t *zkH, int type, int state, const char *path, void *watcherCtx)
{
    if (type == ZOO_SESSION_EVENT) {

        // state refers to states of zookeeper connection.
        // To keep it simple, we would demonstrate these 3: ZOO_EXPIRED_SESSION_STATE, ZOO_CONNECTED_STATE, ZOO_NOTCONNECTED_STATE
        // If you are using ACL, you should be aware of an authentication failure state - ZOO_AUTH_FAILED_STATE
        if (state == ZOO_CONNECTED_STATE) {
            connected = 1;
        } else if (state == ZOO_NOTCONNECTED_STATE ) {
            connected = 0;
        } else if (state == ZOO_EXPIRED_SESSION_STATE) {
            expired = 1;
            connected = 0;
            zookeeper_close(zkH);
        }
    }
}

int main(){
    zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);

    // zookeeper_init returns the handler upon a successful connection, null otherwise
    zkHandler = zookeeper_init("localhost:2181", watcher, 10000, 0, 0, 0);

    if (!zkHandler) {
        return errno;
    }else{
        printf("Connection established with Zookeeper. \n");
    }

    // Close Zookeeper connection
    zookeeper_close(zkHandler);

    return 0;
}

使用前面提到的多线程库编译代码。

> g++ -Iinclude/ zkClient.cpp -lzookeeper_mt -o Client

运行 Client 端。

> ./Client

如果连接成功,那么从输出中,您应该会看到“已连接到 Zookeeper”以及 Zookeeper 的 DEBUG 消息。

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

所以现在您知道了 ZooKeeper。快速,简单,您的应用程序可以运行,但是请稍候...出了点问题。以下是 ZooKeeper 用户的一些陷阱:

  • 如果您使用的是手表,则必须查找已连接的手表事件。当 ZooKeeperClient 端与服务器断开连接时,除非重新连接,否则您不会收到更改通知。如果您正在查看 znode 的存在,那么当断开连接时创建并删除 znode 时,您将错过该事件。

  • 您必须测试 ZooKeeper 服务器故障。只要大多数服务器处于活动状态,ZooKeeper 服务都可以经受住故障。要问的问题是:您的应用程序可以处理吗?在现实世界中,Client 端与 ZooKeeper 的连接可能会断开。 (ZooKeeper 服务器故障和网络分区是造成连接断开的常见原因.)ZooKeeperClient 端库负责恢复连接并让您知道发生了什么,但是必须确保恢复状态和所有失败的未完成请求。在测试实验室而不是生产环境中查找是否正确–使用由几台服务器组成的 ZooKeeper 服务进行测试,然后使它们重新启动。

  • Client 端使用的 ZooKeeper 服务器列表必须与每个 ZooKeeper 服务器具有的 ZooKeeper 服务器列表匹配。如果 Client 端列表是 ZooKeeper 服务器的真实列表的子集,则事情可以进行,尽管不是最佳的,但如果 Client 端列出的 ZooKeeper 服务器不在 ZooKeeper 集群中,则事情就不会起作用。

  • 请注意将事务日志放在何处。 ZooKeeper 最关键的性能部分是事务日志。 ZooKeeper 必须先将事务同步到媒体,然后才能返回响应。专用的事务日志设备是保持良好性能的关键。将日志放在繁忙的设备上会对性能产生不利影响。如果只有一个存储设备,则将跟踪文件放在 NFS 上,并增加 snapshotCount;它不能消除问题,但可以缓解问题。

  • 正确设置 Java 最大堆大小。 避免交换非常重要.不必要地进入磁盘几乎肯定会降低您的性能。请记住,在 ZooKeeper 中,所有内容都是有序的,因此,如果一个请求命中磁盘,则所有其他排队的请求都命中磁盘。为避免交换,请尝试将堆大小设置为拥有的物理内存量,减去 os 和缓存所需的数量。确定配置最佳堆大小的最佳方法是运行负载测试。如果由于某种原因您不能这样做,请保守估计,并选择一个远低于会导致机器交换的限制的数字。例如,在 4G 机器上,从 3G 堆开始是一个保守的估计。

其他信息的链接

在正式文档之外,还有许多其他信息可用于 ZooKeeper 开发人员。