zookeeper程序员指南

1 简介
本文是为想要建立使用ZooKeeper协调服务优点的分布式应用的开发者准备的。本文包含理论信息和实践信息。
本指南的前四节对各类ZooKeeper概念进行较高层次的讨论。这些概念对于理解ZooKeeper是如何工做的,以及如何使用ZooKeeper来进行工做都是必要的。这几节没有代码,但却要求读者对分布式计算相关的问题较为熟悉。
本文的大多数信息以可独立访问的参考材料的形式存在。可是,在编写第一个ZooKeeper应用程序以前,你应该至少读过ZooKeeper数据模型和ZooKeeper基本操做。此外,简单示例程序也有助于理解ZooKeeper客户端应用程序的基本结构。

2 ZooKeeper数据模型
ZooKeeper有一个分层的名字空间,跟分布式文件系统很类似。惟一的不一样是,名字空间中的每一个节点均可以有关联的数据和子节点。这就像一个容许文件也是目录的文件系统。节点路径老是表达为规则的、斜杠分隔的绝对路径,不存在相对路径。路径可使用任何Unicode字符,可是须要遵循下列限制:
一、不能使用空字符(\?)。(这在C绑定中会致使问题)
二、由于不能正确显示,或者容易弄混淆,不能使用这些字符:\ - \和\ - \?。
三、不容许使用这些字符:\? - uF8FFF、\? - uFFFF、\uXFFFE - \uXFFFF(X是1到E之间的一个数字)、\? - \?。
四、可使用小数点,可是不能单独使用.和..来指示路径中的节点,由于ZooKeeper不使用相对路径。/a/b/./c或者/a/b/../c是无效的。
五、记号zookeeper是保留的。

2.1 ZNode
ZooKeeper树中的节点称做znode。znode会维护一个包含数据修改和ACL修改版本号的Stat结构体,这个结构体还包含时间戳字段。版本号和时间戳让ZooKeeper能够校验缓存,协调更新。每次修改znode数据的时候,版本号会增长。客户端获取数据的同时,也会取得数据的版本号。执行更新或者删除操做时,客户端必须提供版本号。若是提供的版本号与数据的实际版本不匹配,则更新操做失败。
注意:
分布式应用工程中,node这个词能够指代主机、服务器、集群成员、客户端进程等等。ZooKeeper文档用znode指代数据节点;用server指代组成ZooKeeper服务的机器;用quorum peer指代组成集群的服务器;用client指代任何使用ZooKeeper服务的主机或者进程。
znode是程序员访问的主要实体,它有一些值得讨论的特征。

2.1.1 观察
客户端能够在znode上设置观察。对znode的修改将触发观察,而后移除观察。观察被触发时,ZooKeeper向客户端发送一个通知。关于观察的更多信息请看ZooKeeper观察。

2.1.2 数据存取
存储在名字空间中每一个znode节点里的数据是原子地读取和写入的。读取操做获取节点的全部数据,写入操做替换全部数据。节点的访问控制列表(ACL)控制能够进行操做的用户。
ZooKeeper不是设计用来做为通用数据库或者大型对象存储的,而是用来存储协调数据的。协调数据的形式多是配置、状态信息、聚合等等。各类形式的协调数据的一个共同特色是:它们一般比较小,以千字节来衡量。ZooKeeper客户端和服务器实现会进行检查,以保证znode数据小于1MB,可是平均的实际数据量应该远小于1MB。对较大数据的操做将致使某些操做比其余操做耗费更多时间,进而影响某些操做的延迟,由于须要额外的时间在网络和存储媒体间移动更多数据。若是须要大数据存储,一般方式是存储到块存储系统,如NFS或者HDFS中,而后在ZooKeeper中保存到存储位置的指针。

2.1.3 临时节点
ZooKeeper有临时节点的概念。临时节点在建立它的会话活动期间存在。会话终止的时候,临时节点被删除,因此临时节点不能有子节点。

2.1.4 顺序节点:惟一命名
建立znode时,能够要求ZooKeeper在路径名后增长一个单调增长的计数器部分。这个计数器相对于znode的父节点是惟一的。计数器的格式是0d,也就是带有0填充的10个数字(这种格式是为了方便排序),好比说,<path>0000000001。队列接收节里有一个使用这种特征的例子。注意:用于存储下一个顺序号的计数器是一个由父节点维护的有符号整数(4字节),因此计数器将在超过2147483647的时候溢出(致使名字成为<path>-2147483647)。

2.2 ZooKeeper中的时间
ZooKeeper以多种方式跟踪时间:
一、zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中全部修改总的次序。每一个修改都有惟一的zxid,若是zxid1小于zxid2,那么zxid1在zxid2以前发生。
二、版本号
对节点的每次修改将使得节点的版本号增长一。版本号有三种:version(znode数据修改的次数)、cversion(znode子节点修改的次数),以及aversion(znode的ACL修改次数)。
三、tick
多服务器ZooKeeper中,服务器使用tick来定义状态上传、会话超时、节点间链接超时等事件的时序。tick仅被最小会话超时(2倍的tick时间)间接使用:若是客户端要求小于最小会话超时的时间,服务器将告知客户端,实际使用的是最小会话超时。
四、真实时间
除了在建立和修改znode时将时间戳放入stat结构体中以外,ZooKeeper不使用真实时间,或者说时钟时间。

2.3 ZooKeeper的Stat结构体
ZooKeeper中每一个znode的Stat结构体由下述字段构成:
czxid:建立节点的事务的zxid
mzxid:对znode最近修改的zxid
ctime:以距离时间原点(epoch)的毫秒数表示的znode建立时间
mtime:以距离时间原点(epoch)的毫秒数表示的znode最近修改时间
version:znode数据的修改次数
cversion:znode子节点修改次数
aversion:znode的ACL修改次数
ephemeralOwner:若是znode是临时节点,则指示节点全部者的会话ID;若是不是临时节点,则为零。
dataLength:znode数据长度。
numChildren:znode子节点个数。

3 ZooKeeper会话
客户端使用某种语言绑定建立一个到服务的句柄时,就创建了一个ZooKeeper会话。会话建立后,句柄处于CONNECTING状态,客户端库会试图链接到组成ZooKeeper服务的某个服务器;链接成功则进入到CONNECTED状态。一般操做中句柄将处于这两个状态之一。若是发生不可恢复的错误,如会话过时、身份鉴定失败,或者应用显式关闭,则句柄进入到CLOSED状态。下图显式了ZooKeeper客户端可能的状态转换:
要建立客户端会话,应用程序代码必须提供一个包含逗号分隔的列表的字符串,其中每一个主机:端口对表明一个ZooKeeper服务器(例如,"127.0.0.1:4545"或者"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",则客户端的根将是/app/a,全部路径将是相对于这个根的:获取/设置/foo/bar数据的操做将实际在/app/a/foo/bar上执行(从服务器来看)。这个特征在多用户环境中特别有用,某个特定ZooKeeper服务的每一个用户可使用不一样的根。这让重用更加简单,用户应用在编码时以/为根,但实际的根位置(如/app/a)能够在部署时肯定。

客户端取得ZooKeeper服务句柄时,ZooKeeper建立一个会话,由一个64位数标识,这个数将返回给客户端。若是链接到其余服务器,客户端将在链接握手时发送会话ID。出于安全考虑,服务器会为会话ID建立一个密码,ZooKeeper服务器能够校验这个密码。这个密码将在建立会话时与会话ID一同发送给客户端。与新的服务器从新创建会话的时候,客户端会和会话ID一同发送这个密码。

客户端库建立会话时须要的参数之一是毫秒表示的会话超时。客户端发送请求的超时值,服务器以能够分配给客户端的超时值回应。当前实现要求超时值最小是2倍的tickTime(在服务器配置文件中设置),最大是20倍的tickTime。客户端API能够获取商定的超时值。

从服务集群分裂开来时,客户端(会话)将搜索会话建立时给出的服务器列表。最终,客户端至少和一个服务器从新创建链接,会话再次进入“已链接”状态(若是在 会话超时以前从新链接上),或者进入到“已过时”状态(若是在会话超时后才从新链接上)。不建议在断开链接时建立一个新的会话对象(即一个新的ZooKeeper.class对象,或者C绑定中的zookeeper句柄),由于客户端库会进行从新链接。特别是客户端库具备试探特征,能够处理“羊群效应”等问题。只须要在被通知会话已过时时建立新的会话(必须的)。

会话过时由ZooKeeper集群,而不是客户端来管理。客户端与集群创建会话时会提供上面讨论的超时值。集群使用这个值来肯定客户端会话什么时候过时。集群在指定的超时时间内没有获得客户端的消息时发生会话过时。会话过时时集群将删除会话的全部临时节点,当即通知全部(观察节点的)客户端。此时已过时会话的客户端仍是同集群断开链接的,不会被通知会话已通过期,直到(除非)客户端从新创建到集群的链接,这时候已过时会话的观察才会收到“会话已过时”通知。

已过时会话的观察看到的状态转换过程示例:
一、已链接:会话创建,客户端与集群通讯中(客户端/服务器通讯正常进行)
二、客户端从集群中分离
三、链接已断开:客户端失去同集群的链接
四、时间流逝,超时时间事后,集群让会话过时,客户端并不知道,由于它仍是同集群断开链接的。
五、时间流逝,客户端与集群间的网络恢复正常。
六、已过时:最终客户端从新链接到集群,此时被通知会话已通过期。

创建会话时的另外一个参数是默认观察。客户端发生状态改变时观察会被通知。好比说,客户端将在失去同服务器的链接,或者会话过时时被通知。观察应该认为初始状 态是链接已经断开(在客户端库向观察发送任何状态改变事件以前)。新建链接时发送给观察的第一个事件一般是会话链接创建事件。

会话由客户端发送的请求保持为活动状态。若是要空闲一段将致使超时的时间,客户端将发送PING请求,保持会话是活动的。PING请求不只让服务器知道客户端仍然是存活的,也让客户端能够确认,到服务器的链接依然是活动的。PING的时序足够保守,确保可以在合理的时间内检测到死掉的链接,从新链接到新的服务器。

一旦到服务器的链接成功创建,则进行同步或者异步操做时,一般有两种状况致使客户端库产生链接丢失事件(C绑定中的错误码,Java中的异常:关于绑定特定的细节,请看API文档):
一、应用程序对已经不存活/有效的会话进行操做
二、在有到服务器的未决操做(例如,有一个进行中的异步调用)时,客户端断开同服务器的链接

3.2.0版增长:SessionMovedException。有一种称做SessionMovedException的 内部异常。一般客户端看不到这个异常。在某链接上收到一个会话请求,可是这个会话已经重建到另外一个服务器上的时候会发生这种异常。致使这种错误的缘由一般 是,客户端向服务器发送请求,可是数据分组被延迟,以至客户端超时而且链接到一个新的服务器。延迟的分组到达先前的服务器的时候,服务器检测到会话已经移 走,会关闭客户端链接。客户端一般看不到这个错误,由于客户端不会从较早的链接上读取数据(一般关闭了较早的链接)。两个客户端试图使用已保存的会话ID和密码从新创建相同的链接时会看到这种错误。其中一个客户端将从新创建链接,而另外一个客户端会被断开链接(致使无限次地试图从新创建链接/会话)。

4 ZooKeeper观察

ZooKeeper中的全部读操做:getData()、getChildren()和exists(),都有一个设置观察做为边效应的选项。ZooKeeper对观察的定义是:观察事件是在被观察数据发生变化时,发送给创建观察的客户端的一次性触发器。对于这个定义,有三点值得关注:
一、一次触发
观察事件将在数据修改后发送给客户端。好比说,若是客户端执行getData("/znode1",true),而后/znode1的数据发生变化,或者被删除,则客户端将收到/znode1的观察事件。若是再次修改/znode1,则不会给客户端发送观察事件,除非客户端再执行一次读取操做,设置新的观察。

二、发送给客户端
这暗示着,在(致使观察事件被触发的)修改操做的成功返回码到达客户端以前,事件可能在去往客户端的路上,可是可能不会到达客户端。观察事件是异步地发送给观察者(客户端)的。ZooKeeper会保证次序:在收到观察事件以前,客户端不会看到已经为之设置观察的节点的改动。网络延迟或者其余因素可能会让不一样的客户端在不一样的时间收到观察事件和更新操做的返回码。这里的要点是:不一样客户端看到的事情都有一致的次序。

三、为哪些数据设置观察
节点有不一样的改动方式。能够认为ZooKeeper维护两个观察列表:数据观察和子节点观察。getData()和exists()设置数据观察。getChildren()设置子节点观察。此外,还能够认为不一样的返回数据有不一样的观察。getData()和exists()返回节点的数据,而getChildren()返回子节点列表。因此,setData()将为znode触发数据观察。成功的create()将为新建立的节点触发数据观察,为其父节点触发子节点观察。成功的delete()将会为被删除的节点触发数据观察以及子节点观察(由于节点不能再有子节点了),为其父节点触发子节点观察。

观察维护在客户端链接到的ZooKeeper服 务器中。这让观察的设置、维护和分发是轻量级的。客户端链接到新的服务器时,全部会话事件将被触发。同服务器断开链接期间不会收到观察。客户端从新链接 时,若是须要,先前已经注册的观察将被从新注册和触发。一般这都是透明的。有一种状况下观察事件将丢失:对尚未建立的节点设置存在观察,而在断开链接期 间建立节点,而后删除。

4.1 ZooKeeper关于观察的保证
一、观察与其余事件、其余观察和异步回应是顺序的。ZooKeeper客户端库保证一切都是按顺序分发的。
二、客户端将在看到znode的新数据以前收到其观察事件。
三、观察事件的次序与ZooKeeper服务看到的更新次序一致。

4.2 关于观察须要记住的
一、观察是一次触发的:若是想在收到观察事件以后收到将来修改的通知,必须再次设置观察。
二、由于观察是一次触发的,而收到观察事件和发送新的请求、再次创建观察之间是有延迟的,因此不能可靠地观察到节点的全部修改。应该要准备处理在收到观察事件和再次设置观察之间,节点被屡次修改的状况。(能够不处理,但至少要知道这种状况是可能的)
三、一个观察对象,或者函数/上下文对,只会由于某个通知而触发一次。好比说,对同一个文件使用exists和getData调用,设置相同的观察对象,而后文件被删除,则观察对象只会被调用一次,带有文件删除通知。
四、与服务器断开链接期间(好比说,服务器故障)不能收到任何观察事件,直到链接从新创建。所以,会话事件是发送给全部未决观察处理器的。可以使用会话事件进入到安全模式:断开链接期间不会收到任何事件,进程应该谨慎操做。
五、使用ACL的访问控制

ZooKeeper使用ACL控制对节点的访问。ACL的实现同Unix文件访问权限很是类似:采用权限位来定义容许/禁止的各类节点操做,以及位应用的范围。与标准Unix权限不一样的是,ZooKeeper节点不禁用户(文件全部者)、组和其余这三个标准范围来限制。ZooKeeper没有节点全部者的概念。取而代之的是,ACL指定一个ID集合,以及与这些ID相关联的权限。

还要注意的是,ACL仅仅用于某特定节点。特别是,ACL不会应用到子节点。好比说,/app只能被ip:172.16.16.1读取,/app/status能够被全部用户读取。ACL不是递归的。

ZooKeeper支持可插入式鉴权模式。使用scheme:id的形式指定ID,其中scheme是id对应的鉴权模式。好比说,ip:172.16.16.1是地址为172.16.16.1的主机的ID。

客户端链接到ZooKeeper,验证自身的时候,ZooKeeper将全部对应客户端的ID都关联到客户端链接上。客户端试图存取节点的时候,ZooKeeper会在节点的ACL中校验这些ID。ACL由(scheme:expression,perms)对组成。expression的格式是特定于scheme的。好比说,(ip:19.22.0.0/16,READ)给予任何IP地址以19.22开头的客户端以READ权限。

5.1 ACL权限
ZooKeeper支持下述权限:
一、CREATE:可建立子节点
二、READ:可获取节点数据和子节点列表
三、WRITE:可设置节点数据
四、DELETE:可删除子节点
五、ADMIN:可设置节点权限

从WRITE权限中分离出CREATE和DELETE能够取得更好的访问控制。使用CREATE和DELETE的状况:
一、但愿A能够设置节点数据,可是不能CREATE或者DELETE子节点。
二、没有DELETE的CREATE权限:客户端经过在某父目录中建立节点来建立请求。此时但愿全部客户端能够添加节点,可是只有请求处理器能够删除节点。(这与文件的APPEND权限相似)

此外,ADMIN权限存在的缘由是,ZooKeeper没有文件全部者的概念。某些状况下ADMIN权限能够指定实体的全部者。ZooKeeper不支持LOOKUP权限(目录上的、容许进行LOOKUP的执行权限位,即便不能列出目录内容)。每一个用户都隐含地拥有LOOKUP权限。这仅仅让用户能够取得节点状态。(问题是,若是想对一个不存在的节点进行zoo_exists()调用,没有权限能够检查)

5.1.1 内置的ACL模式
ZooKeeper内置下述ACL模式:
一、world具备单独的ID,表明任何用户。
二、auth不使用任何ID,表明任何已确认用户。
三、digest使用username:password字符串来生成MD5散列值,用做ID。身份验证经过发送明文的username:password字符串来进行。用在ACL表达式中时将是username:base64编码的SHA1密码摘要。
四、ip使用客户端主机IP做为ID。ACL表达式的形式是addr/bits,表示addr的最高bits位将与客户端主机IP的最高bits位进行匹配。

6 插入式身份验证
ZooKeeper运行在各类使用不一样身份验证模式的环境中,因此它有一个彻底插入式的身份验证框架。内置的身份验证模式也是使用这个框架的。

要理解身份验证框架如何工做,首先必须理解两种主要的身份验证操做。框架首先要验证客户。这一般在客户端链接到服务器后当即进行,由验证客户端发送的信息,或者验证收集的关于客户端的信息,而且将其关联到链接两个步骤构成。框架进行的第二个操做是在ACL中找出客户端对应的实体。ACL实体就是<idspec,permissions>对。idspec多是与链接关联的身份验证信息相匹配的简单字符串,或者是一个能够计算出身份验证信息的表达式。进行匹配是身份验证插件要实现的任务。下面是身份验证插件必须实现的接口:

第一个方法,getScheme返回标识插件的字符串。由于支持多种身份验证方法,因此每一个身份验证凭证,或者说idspec老是带有scheme:前缀。ZooKeeper服务器使用身份验证插件返回的模式字符串来肯定将模式应用到哪些id。

handleAuthentication在客户端发送与链接相关联的身份验证信息时被调用。客户端指定身份验证信息的模式。ZooKeeper服务器将信息传递给getScheme返回值与客户端传递的模式值相匹配的身份验证插件。handleAuthentication一般在肯定身份验证信息不正确时返回错误,或者使用cnxn.getAuthInfo().add(new Id(getScheme(),data))将身份验证信息关联到链接。

身份验证插件与设置和使用ACL相关。为节点设置ACL时,ZooKeeper服务器会将条目的id部分传递给isValid(String id)方法。插件必须验证id具备正确的形式。好比说,ip:172.16.0.0/16是一个有效的id,可是ip:host.com则不是。

若是新的ACL含有auth条目,则isAuthenticated用于肯定与链接相关联的身份验证信息是否要添加到ACL中。某些模式不该该包含在auth中。好比说,若是指定了auth,则客户端的IP地址不会被看做是id,不该该添加到ACL中。

检查ACL时,ZooKeeper调用matches(String id,String aclExpr)。函数须要将客户端的身份验证信息与相应的ACL条目进行匹配。为找出应用到客户端的条目,ZooKeeper服务器找出每一个条目的模式,若是有客户端的、这个模式的身份验证信息,则matches(String id,String aclExpr)会被调用,id设置为先前经过handleAuthentication添加到链接的身份验证信息,aclExpr设置为ACL条目的id。身份验证插件使用其逻辑进行匹配,肯定id是否包含在aclExpr中。

有两个内置的身份验证插件:id和digest。可经过系统属性添加额外的插件。ZooKeeper服务器启动时会查找以zookeeper.authProvider.开头的系统属性,将这些属性的值解释为身份验证插件的类名。可以使用-Dzookeeper.authProvider.X=com.f.MyAuth来设置这些属性,或者在系统配置文件中添加相似于下面的条目:

应该注意,要确保后缀是惟一的。若是有重复的,如-Dzookeeper.authProvider.X=com.f.MyAuth和-Dzookeeper.authProvider.X=com.f.MyAuth2,只会使用一个。此外,全部服务器必须定义有一样的插件,不然客户端使用插件提供的身份验证模式链接到某些服务器时会有问题。

7 一致性保证
ZooKeeper是高性能、可伸缩的服务。读和写操做都设计为高速操做,虽然读比写更快。缘由是在读操做中,ZooKeeper可返回较老的数据,这源自ZooKeeper的一致性保证:
一、顺序一致性:一个客户端的更新将以发送的次序被应用。
二、原子性:更新要么成功,要么失败,没有部分结果。
三、单一系统镜像:不管链接到哪一个服务器,客户端将看到一样的视图。
四、可靠性:一旦应用了某更新,结果将是持久的,直到客户端覆盖了更新。这个保证有两个推论:
(1)若是客户端获得成功的返回码,则更新已经被应用。某些失败状况下(通讯错误、超时等),客户端不知道更新是否已经应用。咱们采起措施保证最小化失败,但这个保证只对成功的返回码有效。(这称做是Paxos中的单一条件)
(2)服务器从失败恢复时,客户端经过读请求或者成功更新看到的任何更新,都不会回滚。
五、及时性:保证客户端的系统视图在某个时间范围(大约为十几秒)内是最新的。在此范围内,客户端要么可看到系统的修改,要么检测到服务终止。
使用这些一致性保证,就能够很容易地单独在ZooKeeper客户端构建如领导者选举、护栏、队列以及可恢复的读写锁等高层功能。更多细节请看Recipes and Solutions。
注意:有时候开发者会错误地假定一个ZooKeeper实际上没有提供的保证:
六、跨客户端视图的并发一致性
ZooKeeper并不保证在某时刻,两个不一样的客户端具备一致的数据视图。由于网络延迟的缘由,一个客户端可能在另外一个客户端获得修改通知以前进行更新。假定有两个客户端A和B。若是客户端A将一个节点/a的值从0修改成1,而后通知客户端B读取/a,客户端B读取到的值可能仍是0,这取决于它链接到了哪一个服务器。若是客户端A和B读取到相同的值很重要,那么客户端B应该在执行读取以前调用sync()方法。
因此,ZooKeeper自己不保证修改在多个服务器间同步地发生,可是可使用ZooKeeper原语来构建高层功能,提供有用的客户端同步。(更多信息,请看ZooKeeper Recipes)

8 绑定
ZooKeeper客户端库以两种方式提供:Java和C。下面几节描述这两种绑定。

8.1 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线程或者同步调用的处理。
三、同步调用可能不会以正确的次序返回。好比说,假设客户端进行下述处理:提交一个watch设置为ture的、对节点/a的异步读取,而后在读取操做的完成回调中执行一个对/a的同步读取。(多是很差的实现,可是是合法的,这只是一个简单的例子)

若是在异步读取和同步读取之间,对/a进行了修改,则客户端库将在同步读取返回以前接收到一个事件,代表/a已经被修改。可是由于完成回调阻塞了事件队列,同步读取将在观察事件被处理以前返回/a的新值。

最后,关于关闭的规则很直接:一旦被关闭或者接收到致命事件(SESSION_EXPIRED和AUTH_FAILED),ZooKeeper对象就变成无效的了。关闭后,两个线程被关闭,后续对zookeeper句柄的任何访问都将致使不肯定的行为,应该避免。

8.2 C绑定
C绑定有单线程和多线程库。多线程库易于使用,跟Java API很是类似。多线程库将建立用于处理链接维持和回调的IO线程与事件分发线程。经过暴露在多线程库中使用的事件循环,单线程库容许在事件驱动应用中使用ZooKeeper。

有两个共享库:zookeeper_st和zookeeper_mt。前者提供了异步API和回调,可集成到应用程序的事件循环中。这个库存在的目的仅仅是为了支持没有pthread可用,或者pthread不稳定的平台(如FreeBSD 4.x)。在其余场合,应用开发者应该连接zookeeper_mt,它同时支持同步和异步API。

8.2.1 安装
若是从Apache代码仓库检出的代码建立客户端库,执行下面的步骤。若是从apache下载的工程源代码包开始建立,则跳到步骤3。
一、在ZooKeeper顶级目录(.../trunk)执行ant compile_jute。这将在../trunk/src/c目录中建立"generated"目录。
二、修改当前目录为../trunk/src/c,执行autoreconf -if,以启动autoconf、automake和libtool。请确认安装了2.59或者更高版本的autoconf。跳到步骤4。
三、若是从工程源代码包开始建立,解压缩源代码包,cd到zookeeper-x.x.x/src/c目录。
四、执行./configure <your-options>以生成makefile。对于这一步,configure工具支持下述有用的选项:
(1)--enable-debug 启用优化和调试信息。(默认是禁用的)
(2)--without-syncapi 禁止同步API支持,不建立zookeeper_mt库。(默认是启用的)
(3)--disable-static 不建立静态库。(默认是启用的)
(4)--disable-shared 不建立共享库。(默认是启用的)
(5)注意:关于执行configure的通常信息,请看INSTALL文件。
五、执行make或者make install,建立而且安装库。
六、要生成ZooKeeper API的doxygen文档,可执行doxygen-doc。全部文档将放置到docs子目录中。默认状况下,这个命令只生成HTML。关于其余文档格式的信息,请执行./congiure --help。

8.2.2 使用C客户端
要测试客户端,可运行ZooKeeper服务器(关于如何运行,请看工程wiki页面的指示),使用做为安装过程一部分建立的某个cli应用程序来链接到服务器。下面的例子显示了使用cli_mt(多线程,与zookeeper_mt库一同建立),可是也可使用cli_st(单线程,与zookeeper_st库一同建立):这个客户端应用程序提供了一个执行简单ZooKeeper命令的Shell。成功启动而且链接到服务器以后,程序显示shell提示符。如今就能够输入ZooKeeper命令了。
在应用程序中使用ZooKeeper API时,应该记住:
一、包含ZooKeeper头文件:#include <zookeeper/zookeeper.h>
二、若是建立多线程客户端,请使用-DTHREADED编译器标志,以启用库的多线程版本,而且连接到zookeeper_mt库。若是建立单线程客户端,不要使用-DTHREADED,而且连接到zookeeper_st库。
关于Java和C的使用示例,请看程序结构和简单示例。

9 建立块:ZooKeeper操做指南
本节描述开发者可对ZooKeeper服务器执行的全部操做。这些信息比本手册前面章节的内容要更底层,可是比ZooKeeper API参考的层次要高。

9.1 处理错误
Java和C绑定均可能报告错误。Java客户端绑定经过抛出KeeperException来报告错误,对异常对象调用code()可取得特定的错误码。C客户端绑定返回ZOO_ERRORS枚举定义的错误码。在两个语言绑定中,API回调都指示结果值。关于全部可能的错误码及其含义的详细信息,请看API文档(Java绑定的javadoc,C绑定的doxygen)。

9.2 链接到ZooKeeper

9.3 读取操做

9.4 写入操做

9.5 处理观察

9.6 其余ZooKeeper操做

10 程序结构和简单示例

11 转向:常见问题和解决

如今你了解ZooKeeper了,它高效、简单,你的程序能够工做,可是等等……,出了点问题了。下面是ZooKeeper用户遇到的一些陷阱:

一、使用观察的时候,必须处理已链接的观察事件。ZooKeeper客户端同服务器断开链接期间,不会收到修改通知,直到从新链接。若是观察一个节点的出现,则断开链接期间会错过节点的建立和删除事件。
二、必须测试ZooKeeper服务失败。一旦多数服务器不活动,ZooKeeper服务会失败。问题是:你的程序能够处理这种状况吗?在真实世界中,客户端到ZooKeeper的链接可能会断开(ZooKeeper服务器失败和网络分区是链接丢失的常见缘由)。ZooKeeper客户端库会处理链接恢复,而且让你知道发生了什么,可是你必须保证能够正确恢复状态和任何已失败的未决请求。在实验室确认程序是正确的,而不是在产品中:用由多个服务器组成的ZooKeeper服务进行测试,而且进行一些重启。
三、客户端和服务器使用的服务器列表应该一致。若是客户端的列表只是真正的服务器列表的一部分,程序能够工做,虽然不是最优的;可是若是客户端包含不在集群中的服务器,则不能工做。
四、注意在哪里放置事务日志。事务日志是ZooKeeper中最关乎性能的部分。返回响应以前,ZooKeeper必须将事务同步到媒体中。专用事务日志设备是取得良好性能的关键。若是只有一个存储设备,把跟踪文件放到NFS中,而且增长snapshotCount;这不能解决问题,可是有必定的改善。
五、正确设置Java的最大堆大小。避免交换是很是重要的。大多数状况下,没必要要地放入磁盘确定会下降性能到不可接受的程度。记住,在ZooKeeper中,一切都是顺序的,若是一个请求触及磁盘,其余排队的请求也会触及磁盘。为避免交换,试试将堆大小设置为拥有的物理内存大小减去操做系统和缓存须要的大小。肯定最优堆大小的最好方法是执行负载测试。若是由于一些缘由而不能进行测试,请采起保守估计,选择一个小于将致使交换的值。好比说,在4GB的机器上,3GB是一个保守的开始值。java

 

本文参考自:node

http://www.uml.org.cn/sjjm/201309125.asp程序员

相关文章
相关标签/搜索