zookeeper的一些概念及原理

1、概览

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

本指南的前四部分对各种ZooKeeper概念进行了更高层次的讨论。这些对于理解ZooKeeper如何工作以及如何使用它们都是必需的。它不包含源代码,但它确实讲述的是与分布式计算相关的问题。

本文档中的大部分信息都是作为独立参考资料编写的。但是,在启动第一个ZooKeeper应用程序之前,您至少应该阅读ZooKeeper数据模型和ZooKeeper基本操作的章节。此外,简单编程示例章节也有助于你理解ZooKeeper客户端应用程序的基本结构。

2、ZooKeeper数据模型

ZooKeeper具有层级名称空间,非常像分布式文件系统。唯一的区别是名称空间中的每个节点都可以有与其关联的数据以及子对象。这就像有一个特殊的文件系统,允许一个文件同时也是一个目录。路径通常表示为规范的,绝对的,斜线分隔的路径,没有相对路径。任何unicode字符都可以在受到以下约束的路径中使用:

  1. null字符(\ u0000)不能是路径名的一部分。 (这会导致C绑定的客户端出现问题。)
  2. 以下字符无法使用,因为它们显示不佳,或以令人困惑的方式呈现:\ u0001  -  \ u0019和\ u007F  -  \ u009F。
  3. 以下字符不允许使用:\ ud800 -uF8FFF,\ uFFF0  -  uFFFF。
  4. "."字符可以用作另一个名字的一部分,但"."和".."不能单独用于指示路径上的节点,因为ZooKeeper不支持相对路径。例如以下内容无效:"/a/b/./c"或"/a/b/../c"。
  5. 令牌"zookeeper"是保留的

2.1 znodes

ZooKeeper目录树中的每个节点都称为znode。 Znodes维护一个stat结构,其中包含数据更改的版本号,acl(访问控制列表)更改。stat结构也有时间戳。版本号与时间戳的存在使ZooKeeper可以验证缓存并协调更新。每次znode的数据更改时,版本号都会增加。例如,每当客户端get数据时,它也会获得数据的版本信息。当客户端执行update或delete时,它必须提供它正在更改的znode的数据版本。如果它提供的版本与实际版本的数据不匹配,则更新将失败。 (这种行为可以被覆盖。欲了解更多信息,请参阅...)

注意:在分布式应用程序中,word node可以指通用主机,服务器,集群成员,客户端进程等。在ZooKeeper文档中,znodes指的是数据节点。servers是指组成ZooKeeper服务的机器;quorum peers是指构成整体的servers;客户端是指任何使用ZooKeeper服务的主机或进程。

znode是程序员需要注意的主要抽象。 Znodes有几个特性值得在此提及。

2.1.1Watches

客户可以在znode上设置watch。对该znode的更改会触发watch,然后清除watch。当watch触发时,ZooKeeper会向客户端发送通知。

2.1.2 Data Access

每个节点上的数据都是以原子方式读取和写入的。读取会获取与znode关联的所有数据字节,写入将替换所有数据。每个节点都有一个访问控制列表(ACL),限制谁可以做什么。

ZooKeeper设计的目标不是通用数据库或大型对象存储。相反,它是用于管理协调数据的。这些数据可以是配置信息,状态信息等。各种形式的协调数据的共同特征是它们相对较小:以kb为单位。 ZooKeeper客户端和服务器实现具有完整性检查功能,以确保znode具有少于1M的数据,但实际数据应该比1M少得多。在相对较大的数据下操作会导致一些操作比其他操作花费更多时间,并且会影响某些操作的延迟,因为将更多数据通过网络传输到存储介质需要额外的时间。如果需要大量数据存储,处理这些数据的通常模式是将其存储在大容量存储系统(如NFS或HDFS)中,然后将指向存储位置的指针存储在ZooKeeper中。

2.1.3 Ephemeral Nodes(临时节点)

ZooKeeper也有临时节点的概念。只要创建znode的session处于活动状态,就会存在这些znode。当会话结束时,znode被删除。由于这种行为,临时znodes不允许有子节点。

2.1.4 Sequence Nodes -- Unique Naming(序列节点--唯一名字)

当创建一个znode时,你也可以请求ZooKeeper将一个单调递增的计数器附加到路径的末尾。该计数器对父节点znode是唯一的。计数器的格式为%010d  - 即10个数字和0(零)填充(计数器以这种方式进行格式化以简化排序),即“<path>0000000001”。有关此功能的示例使用,请参阅  Queue Recipe 章节。注意:用于存储下一个序列号的计数器是由父节点维护的带符号整数(4字节),计数器在增加到2147483647以上时会溢出(此时的节点名称为“<path> -2147483647”)。

2.2 Time in ZooKeeper

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

Zxid

每次对ZooKeeper状态的更改都会以zxid(ZooKeeper Transaction Id)的形式对znode标记。zxid表示了ZooKeeper所有更改的总排序。每个更改都会有一个唯一的zxid,如果zxid1小于zxid2,那么zxid1发生在zxid2之前。

Version numbers

对节点的每次更改都会导致该节点的某个版本号增加。这三个版本号是version(对znode数据的更改次数),cversion(对znode子节点的更改次数)以及aversion(对znode ACL的更改次数)。

Ticks

当使用多服务器ZooKeeper时,服务器使用ticks(心跳)定义事件的时间,例如状态上传时间,会话超时时间,服务器对等点之间的连接超时等。最小会话超时时间是心跳时间的2倍;如果客户端请求的会话超时低于最小会话超时,则服务器将通知客户端会话超时实际上是最小会话超时。

Real time

除了在znode创建和znode修改时将时间戳放入stat结构中之外,ZooKeeper根本不使用实时或时钟时间。

2.3 ZooKeeper Stat Structure

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

  • czxid

    The zxid of the change that caused this znode to be created.

  • mzxid

    The zxid of the change that last modified this znode.

  • pzxid

    The zxid of the change that last modified children of this znode.

  • ctime

    The time in milliseconds from epoch when this znode was created.

  • mtime

    The time in milliseconds from epoch when this znode was last modified.

  • version

    The number of changes to the data of this znode.

  • cversion

    The number of changes to the children of this znode.

  • aversion

    The number of changes to the ACL of this znode.

  • ephemeralOwner

    The session id of the owner of this znode if the znode is an ephemeral node. If it is not an ephemeral node, it will be zero.

  • dataLength

    The length of the data field of this znode.

  • numChildren

    The number of children of this znode.

3  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”后缀也可以附加到连接字符串。这将在解释与该根相关的所有路径时运行客户端命令(类似于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”,其中客户端将认为所有路径都是相对于此根目录的 -例如在“/ foo / bar”目录下执行获取、设置等操作,将导致在“/ app / a / foo / bar”目录中执行操作)。此功能在多租户环境中特别有用,其中特定ZooKeeper服务的每个用户可以设置不同的根。这使得重复使用更加简单,因为每个用户可以将他/她的应用程序编码为“/”,而实际位置(比如/ app / a)可以在部署时确定。

当客户端获得zookeeper服务的句柄时,zookeeper创建一个zookeeper session,表示为一个64位数字,它分配给客户端。如果客户端连接到不同的zookeeper服务器,它将发送session ID作为连接握手的一部分。作为安全措施,服务器会为session id创建一个密码,这个密码用于被其他的zookeeper服务器验证。当客户端重新建立session时,密码将会和session id一起发送到客户端。

zookeeper客户端库调用创建zookeeper session的参数之一是以毫秒为单位的session timeout。客户端发送一个期望timeout,服务器响应它可以允许客户端的session timeout。当前的实现要求session timeout至少为ticktime(心跳时间)的2倍(在服务器配置中设置),最大为ticktime的20倍。

当客户端(session)从zk服务集群分区时,它将开始搜索会话创建期间指定的服务器列表。最终,当客户端和至少一个服务器之间的连接重新建立时,会话将再次转换到“connected”状态(如果在会话超时时间内重新连接),或者它将转换到“expired”状态(如果在会话超时后重新连接)。不建议创建新的会话对象(c绑定中的新zookeeper.class或zookeeper句柄)以断开连接。zk客户端库将为您处理重新连接。特别是我们在客户端库中内置了启发式函数来处理诸如“herd effect”等事情......只有在您被通知会话过期时才强制性的创建新会话。

会话过期由zookeeper集群本身管理,而不是由客户端管理。当zk客户端与集群建立会话时,它会提供上面详述的“timeout”。该值由集群用于确定客户端的会话何时到期。当群集在指定的会话超时期间内没有从客户端收到响应(即,无心跳)时,会使会话到期。在会话到期时,集群将删除该会话拥有的任何/所有临时节点,并立即通知任何/所有连接的客户端该更改(任何观察这些znode的客户端)。客户端将保持断开连接状态,直到与群集重新建立tcp连接,此时过期会话的观察者将收到“会话过期”通知。

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

  1. 'connected' : session is established and client is communicating with cluster (client/server communication is operating properly)

  2. .... client is partitioned from the cluster

  3. 'disconnected' : client has lost connectivity with the cluster

  4. .... time elapses, after 'timeout' period the cluster expires the session, nothing is seen by client as it is disconnected from cluster

  5. .... time elapses, the client regains network level connectivity with the cluster

  6. 'expired' : eventually the client reconnects to the cluster, it is then notified of the expiration

default watcher是zookeeper会话建立连接的另一个参数。当客户端发生任何状态变化时,通知这个观察者。例如,如果客户端失去与服务器的连接,客户端将被通知,或者如果客户端的会话过期等等,该观察者应该考虑初始状态为断开(即,在客户端库发送任何状态改变事件到观察者之前)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。

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

一旦与服务器的连接成功建立(connected),当执行同步或异步操作并符合以下操作之一时,客户端在发生连接丢失基本上有两种情况(java中的异常 - 请参阅绑定特定细节的api文档):

1、应用程序没有在alive/valid的会话上调用操作

2、当存在对该服务器的挂起操作时,例如,存在挂起的异步调用时,zookeeper客户端与服务器断开连接

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

4、ZooKeeper Watches

zookeeper中的所有读操作 -  getdata(),getchildren()和exists() - 都可以选择是否设置watch。这里是zookeeper对watch的定义:watch event是一次性触发,发送给设置watch的客户端,设置watch的数据发生变化时触发watch event。在watc的这个定义中需要考虑三个关键点:

1、一次性触发:

当数据发生变化时,一个watch event将发送给客户端。例如,如果客户端执行getdata(“/ znode1”,true),随后更改或删除/ znode1的数据,则客户端将获得/ znode1的监视事件。如果/ znode1再次发生变化,除非客户端进行了另一次设置新watch的读取,否则不会发送监视事件。

2、发送给客户端

这意味着一个事件正在发送给客户端,但在更改操作的成功返回代码到达发起更改的客户端之前,watch event可能无法到设置了watch的客户端。watch event异步发送给客户端。zookeeper提供了一个顺序保证:客户端永远不会接收到它设置的watch的变化,直到它第一次接收watch event。网络延迟或其他因素可能导致不同的客户端在不同时间收到watch event并返回更新代码。关键是,不同客户看到的一切都会有一致的顺序。

3、设置了watch的数据

这是指节点可以改变的不同方式。想象zookeeper维护两个watch列表是有帮助的:data watches and child watches。getdata()和exists()设置data watchs。getchildren()设置child watches。或者,根据返回的数据类型,可能会帮助您思考设置watch。getdata()和exists()返回有关节点数据的信息,而getchildren()返回一个子节点列表。因此,setdata()将触发设置的znode的data watch假设该设置成功)。一个成功的create()将触发一个对于正在创建的znode的data watch,以及正在创建的znode的parent node的child watch。一个成功的delete()会对正在被删除的znode同时触发一个data watch和一个child watch(因为不能有更多的子节点),也会触发正在删除的znode的parent node的child watch。

watch保存在客户端连接的那台zookeeper服务器上。这使得watch可以被轻量级的设置,维护和发送。当客户端连接到新的服务器时,watch将会被任何session event触发。在与服务器断开连接时不会收到watch。当客户重新连接时,任何先前注册的watch将被重新注册并在需要时触发。一般而言,这一切都是透明的。有一种情况下watch可能会丢失:对一个还未创建的znode设置一个是否存在的watch, 如果在断开连接时znode被创建然后删除,那么这个watch被忽略。

3.1 watch的语义

我们可以通过三个读取zookeeper状态的调用来设置watch:exists,getdata和getchildren。以下列表详细介绍了watch可以触发的事件以及启用这些event的调用:

  • Created event:

    Enabled with a call to exists.

  • Deleted event:

    Enabled with a call to exists, getData, and getChildren.

  • Changed event:

    Enabled with a call to exists and getData.

  • Child event:

    Enabled with a call to getChildren.

3.2 zookeeper对watch的保证

关于watch,zookeeper具有这些保证:

  • watch相对于其他event,其他watchs和异步相应进行了排序。zookeeper客户端库确保按顺序调度所有内容。
  • 客户端在看到与该znode相对应的新数据之前,将看到它正在监视的znode的监视事件。
  • 来自zookeeper的监听事件的顺序对应于由zookeeper服务看到的更新顺序

3.3 需要记住的关于watch的事情

watch是一次性触发器;如果您收到了watch event,并且想要获知以后的更改,则必须设置一个新的watch。

因为watch是一次性触发器,并且在获取事件和发送新请求以获得新的watch之间存在延迟,所以无法可靠地看到zookeeper中的znode发生的每一个变化。需要考虑处理客户端在获取到event和重新设置新的watch之间znode被多次更改的情况。(你可能不在意,但至少需要意识到它可能发生。)

当您从服务器断开连接时(例如,服务器出现故障时),在连接重新建立之前,您将不会收到任何watch。出于这个原因,session event被发送到所有的watch handlers。利用session events进入安全模式:断开连接时不会收到事件,因此您的流程应该在该模式下保守行事。

4、zookeeper访问控制--ACLs

zookeeper使用acls来控制对其znode的访问。acl实现与unix文件访问权限非常相似:后者使用权限位来允许/禁止针对节点的各种操作以及这些位适用的范围。与标准的Unix权限不同,zookeeper节点不受用户(文件所有者),group和world(other)的三个标准作用域的限制。zookeeper没有znode所有者的概念。相反,acl指定了ids的集合以及与这些ID相关联的权限。

还要注意,acl仅适用于具体的znode。特别是它不适用于具体znode的子节点。例如,如果/ app只能通过ip:172.16.16.1读取,那么/ app / status是所有ip都可读的,任何人都可以读/ app / status;acls不是递归的。

zookeeper支持可插入的身份验证schemesIds是使用scheme:id形式指定的,其中scheme是id对应的认证方案。例如,ip:172.16.16.1是地址为172.16.16.1的主机的ID。

当一个客户端连接到zookeeper并进行身份验证时,zookeeper会将与客户端对应的所有id与客户端连接相关联。当客户端尝试访问节点时,会根据znodes的ACL检查这些ID。acls由(scheme:expression, perms)形式的数据组成。expression的格式是特定于该scheme的。例如,(ip:19.22.0.0/16, READ)为具有以19.22开头的ip地址的任何客户端提供读取权限。

4.1 ACL Permissions

zookeeper支持以下权限:

  • CREATE: you can create a child node

  • READ: you can get data from a node and list its children.

  • WRITE: you can set data for a node

  • DELETE: you can delete a child node

  • ADMIN: you can set permissions


create和delete权限是对write权限的更细粒度的访问控制权限。create和delete的情况如下:

下面两句段话,我没有理解是什么意思,原文先贴在这里,后续再理解。

You want A to be able to do a set on a ZooKeeper node, but not be able to CREATE or DELETE children.

CREATE without DELETE: clients create requests by creating ZooKeeper nodes in a parent directory. You want all clients to be able to add, but only request processor can delete. (This is kind of like the APPEND permission for files.) 

此外,由于zookeeper没有文件所有者的概念,所以才有了admin权限。在某种意义上,admin权限指定entity为所有者。zookeeper不支持LOOKUP权限(在目录上的执行权限位允许您查找,即使您不能列出目录)。每个人都隐含有查询权限。这允许你统计一个节点,但不能执行更多的操作。(问题是,如果您想在不存在的节点上调用zoo_exists(),则没有权限检查。)

4.1.1 内置acl方案

zookeeper有以下内置方案:

  • world has a single id, anyone, that represents anyone.

  • auth doesn't use any id, represents any authenticated user.

  • digest uses a username:password string to generate MD5 hash which is then used as an ACL ID identity. Authentication is done by sending the username:password in clear text. When used in the ACL the expression will be the username:base64 encoded SHA1 password digest.

  • ip uses the client host IP as an ACL ID identity. The ACL expression is of the form addr/bits where the most significant bits of addr are matched against the most significant bits of the client host IP.

5、可插入式zookeeper认证

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

要了解身份验证框架是如何工作的,首先您必须了解两个主要的身份验证操作。框架首先必须验证客户端。这通常在客户端连接到服务器后立即完成,并且包括验证从客户端发送或收集的信息并将其与connection相关联。框架处理的第二个操作是在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服务器使用由认证插件的getScheme()方法返回的scheme来确定该方案适用于哪个ID。

当客户端发送认证信息以与连接相关联时,handleauthentication被调用。客户端指定信息对应的scheme。zookeeper服务器将信息传递给对应的认证插件,这个对应的认证插件的getcheme()返回值匹配客户端传递的shceme。如果handleauthentication的实现者确定信息错误,它通常会返回一个err,否则它会使用cnxn.getAuthInfo().add(new Id(getScheme(), data))将信息与连接相关联。

身份验证插件涉及设置和使用acls两部分。当为znode设置acl时,zookeeper服务器会将entry的id部分传递给isvalid(string id)方法。由插件来验证该ID是否具有正确的格式。例如,ip:172.16.0.0/16是一个有效的id,但ip:host.com不是。如果新的acl包含“auth” entry,则使用isauthenticated()方法来查看与该connection关联的scheme的认证信息是否应该添加到acl。一些schemes不应包含在auth中。例如,如果scheme指定为auth,则客户端的IP地址不应该添加到acl的id。

zookeeper在检查acl时调用matches(String id, String aclExpr)方法。需要将客户端的认证信息与相关的acl表项进行匹配。为了找到适用于客户端的条目,zookeeper服务器将查找对应scheme的每个条目,并且如果客户端传了相应scheme的认证信息,则调用matches(String id, String aclExpr)方法时会将id设置为之前通过handleauthentication()方法添加到connection的信息,并将aclexpr设置为acl条目的id。身份验证插件根据其自己的逻辑和匹配的scheme共同确定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,则只会使用一个。所有服务器也必须定义相同的插件,否则使用插件提供的身份验证方案的客户端将无法连接到某些服务器。

6、一致性保证

zookeeper是一款高性能,可扩展的服务。读取和写入操作都很快,但读取速度比写入速度快。原因是在读取的情况下,zookeeper可以提供较旧的数据,之所以能使用较旧的数据又是由于zookeeper的一致性保证:

顺序一致性

来自客户端的更新将按照它们发送的顺序执行。

原子性

更新要么成功要么失败

统一系统映像

无论连接到哪个服务器,客户端都会看到相同的服务视图。

可靠性

一旦应用了更新,它就会一直持续到客户覆盖更新为止。这个保证有两个推论:

1、如果客户端获得成功的返回码,则更新将被应用。在某些故障(通信错误,超时等)时,客户端将不知道更新是否已应用。虽然我们采取措施尽量减少故障,但这个保证仅在返回成功码时适用。(这在paxos中称为单调性条件。)

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

及时性

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

基于这些一致性保证很容易构建更高级别的功能,例如仅在zookeeper客户端(不需要zookeeper)构建一些功能例如,leader选举, barriers, 队列和读/写可撤销锁。

7、Java binding

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

zookeeper java客户端使用的主类是zookeeper类。它的两个构造函数只有一个可选的会话ID和密码。zookeeper支持跨进程实例的会话恢复。一个Java程序可能会将其会话标识和密码保存到稳定的存储中,用于重新启动并恢复该程序之前的实例中使用的会话。

当创建zookeeper对象时,还会创建两个线程:一个io线程和一个event线程。所有io都发生在io线程上(使用java nio)。所有的事件回调都发生在event线程上。会话维持(例如重新连接到zookeeper服务器并保持心跳)在io线程上完成。同步方法的响应也在io线程中处理。所有对异步方法和监视事件的响应都在event线程上处理。有几件事要注意这个设计的结果:

所有异步调用和观察者回调的完成都将按顺序进行。调用者可以执行他们希望的任何处理,但在此期间不会处理其他回调。

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

同步调用可能不会以正确的顺序返回。例如,假设客户端执行以下处理:发出节点/ a的异步读取,并将watch设置为true,然后在读取的完成回调中执行/ a的同步读取。(也许不是很好的做法,但也不是非法的,它只是一个简单的例子。)请注意,如果异步读取和同步读取之间节点/a 发生了变化,假设/a节点在同步读取的响应返回之前发生了变化,则客户端库将接收到监视事件,但由于完成回调阻塞了event队列,在监视事件被处理之前,同步读取将返回/a节点的新值。

最后,与关机相关的规则很简单:一旦zookeeper对象关闭或接收到致命事件(session_expired和auth_failed),则zookeeper对象将变为无效。结束时,两个线程关闭,zookeeper句柄的任何进一步访问都是未定义的行为,应该避免


猜你喜欢

转载自blog.csdn.net/qq_34680763/article/details/80039030