介绍

本文档是针对希望创建利用ZooKeeper协调服务的分布式应用程序的开发人员的指南。

它包含概念和实践信息。

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

第一组中的部分是:

  • ZooKeeper 数据模型

  • ZooKeeper 会话

  • ZooKeeper 观察者

  • 一致性保证

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

这些是:

  • 构件:ZooKeeper操作指南

  • 绑定

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

本书以附录结尾,该附录包含与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”被保留。

znode

ZooKeeper树中的每个节点都称为znode。

Znodes维护一个统计信息结构,其中包括用于数据更改和acl更改的版本号。

stat结构也有时间戳。版本号和时间戳允许ZooKeeper验证缓存并协调更新。

znode的数据每次更改时,版本号都会增加。

例如,每当客户端检索数据时,它也会接收数据的版本。并且,当客户端执行更新或删除时,它必须提供要更改的znode的数据版本。如果它提供的版本与数据的实际版本不匹配,则更新将失败。 (可以忽略此行为。

  • 笔记

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

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

Watches

客户端可以在znodes上设置监视。对该znode的更改将触发Watches,然后清除Watches。监视触发时,ZooKeeper向客户端发送通知。

资料存取

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

ZooKeeper并非设计为通用数据库或大型对象存储。相反,它管理协调数据。这些数据可以采用配置,状态信息,集合点等形式。各种形式的协调数据的共同属性是它们相对较小:以千字节为单位。

ZooKeeper客户端和服务器实现具有健全性检查,以确保znode的数据少于1M,但是数据应比平均少得多。

在相对大的数据量上进行操作将导致某些操作比其他操作花费更多的时间,并且会影响某些操作的延迟,因为需要更多时间才能通过网络将更多数据移动到存储介质上。如果需要大数据存储,则处理此类数据的通常方式是将其存储在大容量存储系统(例如NFS或HDFS)上,并存储指向ZooKeeper中存储位置的指针。

临时节点(Ephemeral Nodes)

ZooKeeper还具有短暂节点的概念。只要创建znode的会话处于活动状态,这些znode就存在。会话结束时,将删除znode。由于这种行为,短暂的znode不允许有孩子。可以使用getEphemerals()API检索会话的临时列表。

  • getEphemerals()

检索会话为给定路径创建的临时节点列表。

如果路径为空,它将列出该会话的所有临时节点。用例-如果需要收集会话的临时节点列表以进行重复数据输入检查,并且以顺序方式创建节点,因此您不知道重复检查的名称,则为示例用例。

在这种情况下,可以使用getEphemerals()api获取会话的节点列表。这可能是服务发现的典型用例。

序列节点-唯一命名

创建znode时,您还可以要求ZooKeeper在路径末尾附加一个单调递增的计数器。该计数器对于父级znode是唯一的。

计数器的格式为 %010d,即填充为0(零)的10位数字(以简化排序的方式格式化计数器),即“0000000001”。

注意:用于存储下一个序列号的计数器是父节点维护的带符号的int(4字节),当计数器的计数值超过2147483647(结果为“ -2147483648”)时,计数器将溢出。

ZooKeeper 中的事件

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

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

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

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

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

ZooKeeper统计结构

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

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

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

  • pzxid最后一次修改此znode子级的更改的zxid。

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

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

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

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

  • aversion此znode的ACL的更改数。

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

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

  • numChildren此znode的子级数。

ZooKeeper 会话

ZooKeeper 客户端通过使用语言绑定创建服务的句柄来与ZooKeeper服务建立会话。

创建句柄后,该句柄将开始处于CONNECTING状态,并且客户端库尝试连接到组成ZooKeeper服务的服务器之一,此时它将切换为CONNECTED状态。

在正常操作期间,客户端句柄将处于这两种状态之一。

如果发生不可恢复的错误(例如会话到期或身份验证失败),或者应用程序显式关闭了句柄,则该句柄将移至CLOSED状态。

下图显示了ZooKeeper客户端可能的状态转换:

stat

要创建客户端会话,应用程序代码必须提供一个连接字符串,其中包含用逗号分隔的host:port对列表,每个对对应于ZooKeeper服务器(例如“ 127.0.0.1:4545”或“ 127.0.0.1:3000,127.0.0.1” :3001,127.0.0.1:3002“)。

ZooKeeper客户端库将选择一个任意服务器并尝试连接到该服务器。

如果此连接失败,或者客户端由于任何原因与服务器断开连接,则客户端将自动尝试列表中的下一个服务器,直到(重新)建立连接为止。

基本流程

当客户端获得ZooKeeper服务的句柄时,ZooKeeper会创建一个分配给客户端的ZooKeeper会话(表示为64位数字)。如果客户端连接到其他ZooKeeper服务器,它将发送会话ID作为连接握手的一部分。

为了安全起见,服务器会为任何ZooKeeper服务器都可以验证的会话ID创建一个密码。

当客户端建立会话时,该密码将与会话ID一起发送给客户端。每当客户端与新服务器重新建立会话时,客户端都会使用会话ID发送此密码。

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

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

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

状态流转

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

  1. “connected”:会话已建立,客户端正在与集群通信(客户端/服务器通信正常运行)

  2. ….客户端已从群集中分区

  3. “disconnected”:客户端失去与群集的连接

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

  5. ….随着时间的流逝,客户端重新获得了与群集的网络级连接

  6. ‘expired’:最终客户端重新连接到集群,然后通知其到期

ZooKeeper会话建立调用的另一个参数是默认观察者。当客户端中发生任何状态更改时,将通知观察者。

例如,如果客户端失去与服务器的连接,则将通知客户端,或者客户端的会话到期,等等。

此观察者应考虑将初始状态断开(即,在任何状态更改事件之前,将事件发送给观察者)。客户端库)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。

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

成功建立(连接)与服务器的连接后,基本上有两种情况,当同步或同步时,客户端库会生成连接丢失。

执行异步操作,并且保留以下条件之一:

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

  2. 当该服务器有待处理的操作时,即有一个待处理的异步调用,ZooKeeper客户端将从服务器断开连接

更新服务器列表。

我们允许客户端通过提供一个新的以逗号分隔的host:port对列表来更新连接字符串,每个对对应于ZooKeeper服务器。

该函数调用概率负载平衡算法,该算法可能导致客户端从其当前主机断开连接,以期在新列表中实现每个服务器的预期统一连接数。如果客户端连接的当前主机不在新列表中,则此调用将始终导致连接断开。否则,将基于服务器数量是增加还是减少以及增加多少来做出决定。

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

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

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

本地会话

背景:在ZooKeeper中创建和关闭会话非常昂贵,因为它们需要仲裁确认,当需要处理数千个客户端连接时,它们成为ZooKeeper集成的瓶颈。

因此,在3.5.0之后,我们引入了一种新的会话类型:本地会话,它不具有普通(全局)会话的全部功能,因此可以通过打开localSessionsEnabled来使用此功能。

ZooKeeper watches

ZooKeeper中的所有读取操作-getData(),getChildren()和exist()-都可以选择将watches设置为副作用。

这是ZooKeeper对watches的定义:watches事件是一次触发,发​​送给设置watches的客户端,该事件在设置watches的数据发生更改时发生。

在watches的定义中,需要考虑三个关键点:

(1)一次性触发器数据更改后,一个监视事件将发送到客户端。

例如,如果客户端执行 getData("/znode1", true),然后 /znode1 的数据被更改或删除,则客户端将获得 /znode1的监视事件。

如果 /znode1再次更改,则除非客户端进行了另一次读取来设置新的监视,否则不会发送任何监视事件。

(2)发送给客户端这意味着事件正在传递给客户端,但是可能未成功到达更改操作的返回码到达发起更改的客户端之前,该事件尚未到达客户端。

watches被异步发送给观察者。

ZooKeeper提供了订购保证:客户端在第一次看到监视事件之前,将永远不会看到为其设置了监视的更改。网络延迟或其他因素可能导致不同的客户端在不同的时间看到监视并从更新中返回代码。

关键是不同客户看到的所有内容将具有一致的顺序。

(3)为其设置watches的数据这是指节点可以更改的不同方式。

可以将ZooKeeper视为维护两个watches列表:数据watches和子节点watches。

getData()和exist()设置数据监视。

getChildren()设置儿童watches。

或者,可以考虑根据返回的数据类型设置watches。

getData()和exist()返回有关节点数据的信息,而getChildren()返回子级列表。因此,setData()将触发数据监视是否设置了znode(假设设置成功)。成功的create()将触发对正在创建的znode的数据监视,以及对父znode的子监视。成功的delete()将同时触发要删除的znode的数据监视和子监视(因为不能再有子监视)以及父znode的子监视。

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

3.6.0中的新增功能:客户端还可以在znode上设置永久的,递归的watches,这些watches在被触发时不会被删除,并且会以递归方式触发已注册znode以及所有子znode的更改。

watches的语义

我们可以使用三个读取ZooKeeper状态的调用来设置watches:exist,getData和getChildren。

以下列表详细说明了watches可以触发的事件以及启用它们的调用:

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

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

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

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

持久/递归 watches

3.6.0中的新增功能:上述标准watches现在有所变化,您可以将其设置为在触发时不会被移除的watches。

此外,这些监视会触发事件类型NodeCreated,NodeDeleted和NodeDataChanged,并可选地从注册该监视的znode处递归地为所有znode递归。

请注意,持久性递归监视不会触发NodeChildrenChanged事件,因为这将是多余的。

使用方法addWatch()设置持久性监视。触发语义和保证(一次性触发除外)与标准watches相同。

关于事件的唯一例外是,递归持久性观察者永远不会触发子更改事件,因为它们是多余的。

使用具有监视程序类型WatcherType.Any的removeWatches()可以删除持久监视。

ZooKeeper 对 watches的保证

关于watches,ZooKeeper保持以下保证:

监视是针对其他事件,其他监视和异步答复而排序的。

ZooKeeper客户端库可确保按顺序分派所有内容。

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

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

watches须知

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

因为标准watches是一次触发,并且在获取事件和发送新请求以获取watches之间存在延迟,所以您无法可靠地看到ZooKeeper中节点发生的每项更改。

准备处理znode在获取事件和重新设置watches之间多次更改的情况。 (您可能不在乎,但至少意识到可能会发生。)

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

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

使用ACL的ZooKeeper访问控制

ZooKeeper使用ACL来控制对其znode(ZooKeeper数据树的数据节点)的访问。

ACL实现与UNIX文件访问权限非常相似:它使用权限位来允许/禁止对节点及其所适用范围的各种操作。与标准UNIX权限不同,ZooKeeper节点不受用户(文件所有者),组和全局(其他)的三个标准范围的限制。

ZooKeeper没有znode所有者的概念。而是,ACL指定一组ID和与这些ID相关联的权限。

还请注意,ACL仅与特定的znode有关。特别是它不适用于子节点。

例如,如果 /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是使用摘要方案的名称为bob的用户的ID。

当客户端连接到ZooKeeper并对其进行身份验证时,ZooKeeper会将与该客户端相对应的所有ID与该客户端连接相关联。当客户端尝试访问节点时,将根据znodes的ACL检查这些ID。

ACL由成对的(scheme:expression,perms)组成。表达式的格式特定于该方案。

例如,该对(ip:19.22.0.0/16,READ)为IP地址以19.22开头的任何客户端提供READ权限。

ACL权限

ZooKeeper支持以下权限:

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

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

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

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

  • 管理:您可以设置权限

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

您希望A能够在ZooKeeper节点上进行设置,但不能创建或删除子级。

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

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

就ACL而言,ADMIN权限也具有特殊作用:为了检索znode用户的ACL,必须具有READ或ADMIN权限,但没有ADMIN权限,摘要哈希值将被屏蔽。

内置ACL方案

ZooKeeeper具有以下内置方案:

world(全局)只有一个ID,任何人都代表任何人。

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

digist(摘要)使用用户名:密码字符串生成MD5哈希,然后将其用作ACL ID身份。通过以明文形式发送username:password来完成身份验证。在ACL中使用时,该表达式将为username:base64编码的SHA1密码摘要。

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

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

可插拔的ZooKeeper身份验证

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

甚至内置的身份验证方案也使用可插入身份验证框架。

要了解身份验证框架的工作原理,首先必须了解两个主要的身份验证操作。框架首先必须验证客户端。通常在客户端连接到服务器后立即执行此操作,包括验证从客户端发送或收集的有关客户端的信息,并将其与连接相关联。框架处理的第二个操作是在ACL中查找与客户端相对应的条目。

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。

当客户端发送要与连接关联的身份验证信息时,将调用handleAuthentication。客户端指定信息所对应的方案。 ZooKeeper服务器将信息传递给身份验证插件,该插件的getScheme与客户端传递的方案匹配。如果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不是有效的ID。如果新的ACL包含“身份验证”条目,则使用isAuthenticated来查看是否应将与该连接关联的该方案的身份验证信息添加到ACL中。某些方案不应包含在auth中。例如,如果指定了auth,则不将客户端的IP地址视为应添加到ACL的ID。

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

有两个内置的身份验证插件:ip和摘要。其他插件可以使用系统属性进行添加。在启动时,ZooKeeper服务器将查找以“ zookeeper.authProvider”开头的系统属性。并将这些属性的值解释为身份验证插件的类名。可以使用-Dzookeeeper.authProvider.X = com.f.MyAuth或在服务器配置文件中添加以下内容来设置这些属性:

在3.6.0中新特性

备用抽象可用于可插入身份验证。

它提供了其他参数。

public abstract class ServerAuthenticationProvider implements AuthenticationProvider {
    public abstract KeeperException.Code handleAuthentication(ServerObjs serverObjs, byte authData[]);
    public abstract boolean matches(ServerObjs serverObjs, MatchValues matchValues);
}

不是实现AuthenticationProvider,而是扩展ServerAuthenticationProvider。

然后,您的handleAuthentication()和match()方法将接收其他参数(通过ServerObjs和MatchValues)。

ZooKeeperServer ZooKeeperServer实例

ServerCnxn当前连接

path正在操作的ZNode路径(如果未使用,则为null)

perm操作值或0

setAcls当对setAcl()方法进行操作时,正在设置的ACL列表

一致性保证

ZooKeeper是一项高性能,可扩展的服务。读取和写入操作都被设计为快速,尽管读取比写入快。

原因是在读取的情况下,ZooKeeper可以提供较旧的数据,而这又归因于ZooKeeper的一致性保证:

顺序一致性:来自客户端的更新将按照发送的顺序应用。

原子性:更新成功或失败-没有部分结果。

单个系统映像:无论客户端连接到哪个服务器,客户端都将看到该服务的相同视图。即,即使客户端故障转移到具有相同会话的其他服务器,客户端也永远不会看到系统的较旧视图。

可靠性:一旦应用了更新,它将一直持续到客户端覆盖更新为止。

此保证有两个推论:

如果客户获得成功的返回码,则将应用此更新。在某些故障(通信错误,超时等)上,客户端将不知道更新是否已应用。我们会采取措施以最大程度地减少失败,但保证仅包含成功的返回码。 (这在Paxos中称为单调性条件。) 从服务器故障中恢复时,客户端通过读取请求或成功更新看到的任何更新都不会回滚。

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

使用这些一致性可以确保仅在ZooKeeper客户端上就可以轻松构建更高级别的功能,例如领导者选举,障碍,队列和读/写可撤销锁(无需对ZooKeeper进行任何添加)。

  • 笔记

有时,开发人员错误地假设了另一个保证ZooKeeper实际上并不能做出保证。

这是:同时一致的跨客户端视图:ZooKeeper不能保证每个实例在每个时间都有两个不同的客户端具有相同的ZooKeeper数据视图。

由于诸如网络延迟之类的因素,一个客户端可能会在另一客户端收到更改通知之前执行更新。考虑两个客户端A和B的情况。如果客户端A将znode / a的值从0设置为1,然后告诉客户端B读取/ a,则客户端B可能读取旧值0,具体取决于哪个服务器。它连接到。如果客户端A和客户端B读取相同的值很重要,则客户端B应该在执行读取之前从ZooKeeper API方法中调用sync()方法。

因此,ZooKeeper本身并不能保证所有服务器上的更改都会同步发生,但是ZooKeeper原语可用于构造更高级别的功能,以提供有用的客户端同步。

Java绑定

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

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

创建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句柄的任何进一步访问都是未定义的行为,应避免。

客户端配置参数

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

zookeeper.sasl.client:将值设置为false可禁用SASL身份验证。默认为true。

zookeeper.sasl.clientconfig:在JAAS登录文件中指定上下文密钥。默认值为“客户端”。

zookeeper.server.principal:当启用Kerberos身份验证时,指定连接到zookeeper服务器时客户端用于身份验证的服务器主体。如果提供了此配置,则ZooKeeper客户端将不会使用以下任何参数来确定服务器主体: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参数,因此ZooKeeper客户端将尝试确定ZooKeeper服务器主体的“实例”(主机)部分。首先,它使用提供的主机名作为ZooKeeper服务器连接字符串。然后,它尝试通过获取属于该地址的完全限定域名来“规范化”该地址。您可以通过设置禁用此“规范化”:zookeeper.sasl.client.canonicalize.hostname = false

zookeeper.server.realm:服务器主体的领域部分。默认情况下,它是客户端主体领域。

zookeeper.disableAutoWatchReset:此开关控制是否启用自动Watches重置。客户端在默认会话重连期间自动重置Watches

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

所以现在您知道了ZooKeeper。快速,简单,您的应用程序可以运行,但是请稍候…出了点问题。

以下是ZooKeeper用户的一些陷阱:

(1)如果您使用的是Watches,则必须查找已连接的Watches事件。

当ZooKeeper客户端与服务器断开连接时,除非重新连接,否则您不会收到更改通知。如果您正在查看znode的存在,那么当断开连接时创建并删除znode时,您将错过该事件。

(2)您必须测试ZooKeeper服务器故障。

只要大多数服务器处于活动状态,ZooKeeper服务都可以经受住故障。

要问的问题是:您的应用程序可以处理吗?

在现实世界中,客户端与ZooKeeper的连接可能会断开。 (ZooKeeper服务器故障和网络分区是造成连接断开的常见原因。)ZooKeeper客户端库负责恢复连接并让您知道发生了什么情况,但是必须确保恢复状态和所有失败的未完成请求。在测试实验室而不是生产环境中查找是否正确–使用由几台服务器组成的ZooKeeper服务进行测试,然后使它们重新启动。

(3)客户端使用的ZooKeeper服务器列表必须与每个ZooKeeper服务器具有的ZooKeeper服务器列表匹配。

如果客户端列表是ZooKeeper服务器的真实列表的子集,则事情可以进行,尽管不是最佳的,但如果客户端列出的ZooKeeper服务器不在ZooKeeper集群中,则事情就不会成功。

(4)请注意将事务日志放在何处。

ZooKeeper最关键的性能部分是事务日志。

ZooKeeper必须先将事务同步到媒体,然后才能返回响应。专用的事务日志设备是保持良好性能的关键。将日志放在繁忙的设备上会对性能产生不利影响。

如果只有一个存储设备,则将跟踪文件放在NFS上,并增加snapshotCount;它不能消除问题,但可以缓解问题。

(5)正确设置Java最大堆大小。

避免交换非常重要。不必要地进入磁盘几乎肯定会降低您的性能。

请记住,在ZooKeeper中,所有命令都是有序的,因此,如果一个请求命中磁盘,则所有其他排队的请求都命中磁盘。

为避免交换,请尝试将堆大小设置为拥有的物理内存量,减去操作系统和缓存所需的数量。确定配置的最佳堆大小的最佳方法是运行负载测试。

如果由于某种原因您不能这样做,请保守估计,并选择一个远低于会导致机器交换的限制的数字。

例如,在4G机器上,从3G堆开始是一个保守的估计。

TODO

java 实战例子…

参考资料

https://zookeeper.apache.org/doc/r3.6.2/zookeeperStarted.html