zookeeper数据模型
zookeeper有一个层级命名空间,和一个分布式文件系统很是类似 。惟一的不一样是每一个节点能够有关联的数据,子节点也是。就像有一个文件系统,而且容许文件能够是一个目录。
一个规范的,绝对的,斜杠分割的路径来表示一个节点路径。没有相对路径。
任何符合下列规范的Unicode字符能够被使用:
- null字符串(\u0000)不能是一个路径名称。
- 下列字符不能使用,觉得不能很好的被展现:\u0001-\u001F,\u007F-\u009F.
- 下列字符是不容许的:\ud800-uF8FF,\uFFF0-uFFFF.
- "."字符能够做为另外一个名字被使用,可是“.”和“..”不能做为一个单独节点路径被使用,由于zookeeper不使用相对路径。下列是无效的:“a/b/./c”或者“a/b/../c”。
- “zookeeper”标记被保留。
ZNodes
在zookeeper树中的每个节点被称为一个znode。ZNodes包含了一个stat数据结构,这个数据结构包含了数据变动的版本号,acl变动。stat数据结构也有时间戳,版本号和时间戳一块儿来容许zookeeper校验缓存和协调更新。每当一个znode数据变动,版本号就会增长。例如:当一个客户端取得数据,它一样也接收数据的版本。而且,当一个执行一个更新或删除操做,它必须提供数据的版本号。若是客户端提供的版本号和实际的版本号不匹配,更新操做将会失败。
注意:在分布应用中,node一词能够被用来表示一台主机,一台服务器,集中中的一个,一个客户端进程等。
早ZooKeeper这边文档中,
znodes 表示一个数据节点,
Servers表示组成ZooKeeper服务中的机器,
quorum peers 表示组成集合的机器,客户端表示使用一个ZooKeeper服务的主机或进程。
Znodes是一个程序访问的主要实体,在这里有许多值得提到的特性:
Watches:
客户端能够在znodes上设置监听器,znode的改变触发这个监听器而后清空这个监听器。当一个监听器被触发,Zookeeper发送给客户端一个通知。更多信息能够查看watches章节。
数据访问:
每一个znode上存储的数据的读写都是原子的,读操做取出全部的和这个znode有关的全部数据,写操做替换全部数据。每一个节点有一个访问权限列表(ACL)限制谁能够作这些事。
zookeeper没有被设计成一个通常的数据库,或者大型对象存储。它管理协调数据,数据能够是状态,配置信息,集合点等的形式。各类各样的数据有一个共同的属性就是它们都很小:以千字节为标准。
ZooKeeper客户端和服务器有一个健康检查来确保znodes的数据少于1M,可是数据平均应该更小。操做较大的数据将致使一些操做花费更多的时 间,而且会影响一些操做的延迟,由于在网络和存储媒介中移动更多的数据将须要额外的时间。若是须要存储大数据,一般的处理是把数据存储在一个大容量存储系 统中,并把存储位置的指针存储到ZooKeeper上。
临时节点:
zookeeper也有临时节点的概念。这些znodes存活的时间和建立这个节点的会话有效期是同样的。当会话结束,节点被删除。由于这种临时节点的特性,临时节点不容许有子节点。
顺序节点——惟一名称:
当建立一个节点的时候,也能够请求zookeeper在路径后面增长一个自增的计数器。对父节点来讲,这个计数器是惟一的。计数器是%010d的格式( 十进制整数输出,宽度10位,不足前面补0;)——是一个十位数,好比:<path>0000000001。
zookeeper中的时间
Zookeeper以多种方式跟踪时间:
- Zxid: ZooKeeper状态的每次变化都接收一个zxid(ZooKeeper事务id)形式的标记。这个展现了全部的ZooKeeper的变动顺序。每次变动会有一个惟一的zxid,若是zxid1小于zxid2说明zxid1在zxid2以前发生。
- Version numbers: 节点的每次变化都会引发这个节点版本号之一的一次增长。这三个版本号是:version(一个节点的数据变化次数),cversion(一个节点的子节点变化次数),aversion(一个节点的ACL 变化次数)。
- Tricks: 当使用多个ZooKeeper服务,服务器使用ticks来肯定事件的时间,好比说状态上传、会话超时、链接超时等。这个tick时间仅仅经过最小会话超 时时间间接的暴露出来;若是一个客户端请求会话的超时时间小于最小超时时间,服务器将会告诉客户端实际的会话超时时间是最小超时时间。
- Real Time: ZooKeeper不使用实时、时钟时间。除了把时间戳放在stat结构中。
ZooKeeper Stat 结构
- czxid:该数据节点被建立时的事务id。
- mzxid:该节点最后一次被更新时的事务id。
- ctime:节点被建立时的时间。
- mtime:节点最后一次被更新时的时间。
- version:这个节点的数据变化的次数。
- cversion:这个节点的子节点 变化次数。
- aversion:这个节点的ACL变化次数。
- ephemeralOwner:若是这个节点是临时节点,表示建立者的会话id。若是不是临时节点,这个值是0。
- dataLength:这个节点的数据长度。
- numChildren:这个节点的子节点个数。
zookeeper 会话
经过使用一种语言绑定来建立服务端的句柄,一个zookeeper客户端能够和zookeeper服务建立会话。一旦建立,句柄开始在CONNECTING状态,客户端库尝试链接组成zookeeper服务中的一个服务器,而且切换到CONNECTED状态。在正常的操做期间将会是这两种状态之一。若是一个不可恢复的错误发生了,好比说会话过时或者受权失败,或者应用显示的关闭了句柄,句柄将会到CLOSED状态。下图展现了一个zookeeper可能的状态转变。
为了建立一个客户端会话,应用程序代码必须提供一个链接字符串列表以逗号分隔开,主机:端口号成对出现,每一个都至关于一个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”后缀能够被加在链接字符串后面,这会运行客户端命令致使全部的路径都和这个跟路径相关。若是使用像下面的示 例:”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”做为跟路径,而且全部的路径都与这个根路径相关,好比getting、setting等。”/foo/bar” 将致使操做在”/app/a/foo/bar”(从服务端的观点来看)。这个特性在多租户下面是很是也有用的,ZooKeeper服务的每一个用户能够有不 同的根路径。这让再使用变得很是简单,由于每一个用户均可以编写代码让他的应用好像在”/”根路径下,但实际的位置能在部署时决定。
当一个客户端从ZooKeeper服务获得一个句柄,ZooKeeper建立了一个会话,表现为一个64位的数字,并把 它分配给客户端。若是客户端链接到一个不一样的服务端,在链接握手的时候它将发送这个会话id。做为一个安全措施,服务端给会话id建立了一个密码,让服务 端可以校验。当客户端创建会话的时候,这个密码随着会话id一块儿发送给客户端。每当客户端与一个新的服务端恢复会话的时候,密码会随着会话id一块儿发送过 去。数据库
客户端调用建立会话的时候有一个参数是会话超时时间(毫秒),客户端发送一个要求的超时时间,服务端回复一个他能给客户端的超时时间。当前实现要求 超时时间至少是2倍的tickTime,最大是20倍的tickTime。ZooKeeper客户端API容许使用一个协商的超时时间。
当一个客户端从ZK服务集群成为分区,它将开始寻找在会话建立时期指定的服务端列表。最终,当客户端和至少一个服务端联通从新创建的时候,会话要么 转变成“connected”状态(若是在会话超时时间内恢复链接),要么转变成“expired”状态(若是在超时时间以外恢复链接)。在断开时建立一 个新的会话是不可取的。ZK客户端库将处理链接。尤为是客户端内部有方法来处理像“羊群效应”之类的事情。仅仅在你被通知会话过时的时候去建立一个新的会 话。express
ZooKeeper集群本身管理会话过时,而不是由客户端管理。当ZK客户端和一个集群创建会话,它提供一个“超时时间”。这个值被集群使用来决定 客户端的会话是否过时。当集群不能在指定的会话超时时间内从客户端收到信息,过时发生。在会话过时期间,集群将删除由这个会话建立的全部的临时节点,而且 当即通知链接的客户端这个改变。此时,会话过时的客户端依然和集群式断开的,它不会收到通知直到它能和集群从新创建链接。这个客户端将保持断开状态直到和 集群的TCP链接从新创建,而且在这个时候,过时会话的监听将会收到“会话过时”通知。
- “connected”:会话被创建,而且客户端能和集群交流
- ……客户端从集群被分割
- “disconnected”:客户端与集群丢失了联系
- ……时间流逝,在超时时间以后,集群已经让这个会话过时,而客户端没看到什么,由于它已经从集群断开链接了
- ……时间流逝,客户端恢复网络和集群联通
- “expired”:最后客户端与集群从新链接,而后收到过时的通知
ZooKeeper会话创建的另外一个参数是默认监听器。当客户端的一些状态改变发生,监听器会收到通知。好比若是客户端丢失与服务端的链接,客户端将会收到通知,或客户端的会话到期等。这个监听器应该考虑初始状态到断开链接。对于一个新的链接,第一个发给监听器的事件就是会话链接事件。
客户端经过发送请求保持会话存活。若是会话在一段时间内空闲将会致使会话超时,客户端将会发送PING请求保持会话存活。这个PING请求不只仅让 ZooKeeper服务端知道客户端是存活的,并且让客户端检查它的和ZooKeeper 服务端的链接也是存活的。PING的时间是足够保守的合理时间,来发现死掉的链接和一个新的服务端从新链接。
一旦成功创建一个到服务端的链接,当客户端发生connectionloss异常 时有两种基本的状况,在执行一个同步或者非同步的操做时:安全
- 应用调用一个操做,可是会话再也不存活。
- 当等待一个操做的时候ZooKeeper客户端从服务端断开链接,好比说:等待一个异步调用。
3.2.0新增——SessionMovedException。有一个内部的异常,一般不会被客户端发现,被称为 SessionMovedException。一个已经链接的会话可是从新链接到了一个不一样的服务器上接收了一个请求,这个异常就会发生。这个错误的正常 缘由是一个客户端发送了一个请求到一个服务端,可是网络数据包延迟了,因此客户端超时并链接到了一个新的服务器。当延迟的数据包到达了第一个服务器,这个 服务端发现这个会话已经被移除了而且关闭了这个客户端链接。客户端通常不会发现这个错误由于它们不在从老的链接读取数据(老的链接通常被关闭了)。这种事情发生的另外一种状况是当两个客户端使用一个保存的会话id和密码来尝试恢复相同的链接时,只有一个客户端可以恢复链接,另外一个客户端将会断开。
更新服务器列表。咱们容许一个客户端更新链接字符串经过提供一个新的逗号分隔的主机:端口号列表,每一个都是一个服务器。函数调用一个几率负载均衡算法会引发客户端断开与当前主机的链接,来使在新列表中的每一个服务器达到与预期一致的数量。万一客户端链接的当前主机不在新的列表中,这个调用会引发链接被删除。另外,这个决定基因而否服务器的数量增长或减小了多少。
好比说,若是以前的链接包含三个主机,如今的链接多了两个主机,链接到每一个主机的客户端的40%为了负载均衡将会移动到新的主机上去。这个算法会引发客户端断掉它当前与服务器的链接,这个几率是0.4,而且客户端随机选择两个新主机中的一个链接。
另外一个例子,假设咱们有5个主机,而后如今更新列表移除两个主机,链接到剩余三台主机的客户端依然保持链接,然而全部链接到已被移除主机的客户端都 须要移到剩下三台主机的一台上,而且这种选择是随机的。若是链接断开,客户端进入一个特殊的模式并使用几率算法选择一个新的服务器,而不只仅只是循环。服务器
在第一个例子中,每一个客户端决定断开链接的几率为0.4,可是一旦作了决定,它将会随机的链接到一个新的服务器,仅仅当它不能链接到任何一台新的服 务器上时,它将尝试链接旧的服务器。当找到一个服务器或者新列表中全部的服务器都链接失败的时候,客户端回到操做正常模式,选择一个任意的服务器并尝试链接它,若是链接失败,它会继续尝试不一样的随机的服务器,并一直循环下去。
ZooKeeper Watches
ZooKeeper中全部的读操做——getData(), getChildren()和 exists()—能够选择设置 一个监听器。这是ZooKeeper’s一个监听器的定义:一个监听事件是一次性触发,当一个被设置监听的数据改变时,发送给设置这个监听器的客户端。在这个监听器的定义中,有三个要点:
- 一次性触发:当数据改变的时候一个监听事件会被发送给客户端。好比说,若是一个客户端作了getData(“/znode1″, true)操做,而后 /znode1下的数据被改变或者删除了,客户端将获得/znode1的一个监听事件。若是/znode1节点再次发生改变,没有监听事件会被发送,除非客户端作了别的,设置了一个新的监听器。
- 发送到客户端:这意味着事件正在发送给客户端的途中,可是在操做成功的返回码到达发起这个变动操做的客户端以前,事件可能还没到达监听的客户端。 ZooKeeper提供了一个有序保证:在它第一次看到监听事件以前,它永远不会看到它设置的监听改变。网络延迟或别的因素,可能会引发不一样的客户端看见监听器和更新操做的返回码,在不一样的时间。关键得一点是不一样的客户端看见的每件事有一个一致的顺序。
- 被设置监听的数据:这是指一个节点能变化的不一样方式。能够认为ZooKeeper有两个监听器列表:数据监听和子节点监听。getData()和 exists()设置数据监听器。 getChildren()设置子节点监听器。二选一,根据返回数据的类型来设置监听器。getData()和exists()返回节点的数据信息,然而 getChildren()返回一个子节点列表。所以,setData()会触发数据监听器。一个成功的 create()会触发一个数据监听器。一个delete()会触发数据监听器和子节点监听器。
在ZooKeeper服务器中,当客户端链接的时候,监听器被保存在本地。这使得监听器轻量级的被设置、保存、分发。当一个客户端链接一个新的服务器,监听器会触发一些会话事件。当从服务器断开链接的时候,不会收到监听器。当一个客户端从新链接,若是须要的话,以前注册的监听器会被注册和触发。有一个监听器可能丢失的状况:若是在断开链接期间,一个节点被建立和删除,一个已存在的节点的监听器尚未建立,将丢失。
咱们能在三种调用读取ZooKeeper状态的状况下设置监听器:exists,getData和getChildren,下面的列表是一个监听器触发的事件的详细状况:
- 建立事件:exists的调用
- 删除事件:exists,getData和getChildren的调用
- 改变事件:exists,getData的调用
- 子节点事件:getChildren的调用
咱们能够调用removeWatches来移除一个注册在节点上的监听器。一样的,一个ZooKeeper客户端在没有服务器链接的状况下能移除本地的监听器,经过设置本地的标记为true。下面是事件的详细列表监听器成功的被移除后触发:
- 子节点移除事件:调用getChildren增长的监听器。
- 数据移除事件:调用exists或getData增长的监听器。
ZooKeeper对监听器的保证app
对于监听器,ZooKeeper有下列的保障:
- 监听器和另外的事件,另外的监听器和异步的回复是有序的。ZooKeeper 客户端库确保每件事都有序分发。
- 一个客户端看到这个节点的新的数据以前,会先看到他监听的节点的一个监听事件。
- 从ZooKeeper 来的监听事件的顺序对应于ZooKeeper 服务看到的更新的顺序。
关于监听器要记住的事情
- 监听器是一次触发的,若是你获得了一个监听事件而且想继续获得将来的事件通知,你必须设置一个另外的监听器。
- 由于监听器是一次触发的,就会在获得事件和发送请求设置新的监听器之间有一个延迟,你不能看到ZooKeeper的节点上每次 改变。准备好处理在获得事件和设置监听器之间节点屡次改变的状况(你或许不太关心,但至少要意识这会发生)。
- 一个监听器对象或一个函数/上下文对,为一个事件只会被触发一次。好比说,若是相同的监听器在一次exists或getData调用中被注册到了相同的文件,而且文件被删除,对于该文件删除的通知,监听器对象只会被调用一次。
- 当你从服务器断开链接,在恢复链接以前,你不会获得任何监听器。因为这个缘由,会话事件会被发送给全部的未处理的监听器。使用会话事件进入一个安全模式:在断开期间,你不会收到事件,因此你的进程在这种模式下应该当心行事。
ZooKeeper使用ACLs控制访问
ZooKeeper使用ACLs来控制访问它的节点(ZooKeeper数据树上的数据节点)。ACL的实现和UNIX文件访问权限很是类似:它使 用权限位来容许/拒绝对节点和位适用范围的各类操做。不像标准的UNIX权限,一个ZooKeeper节点没有限制在这三个标准的范围:user (文件拥有者)、group、world 。ZooKeeper没有节点拥有者的概念,取而代之的是,一个ACL指定ids和id相关的权限的集合。
还请注意一个ACL只适用于一个指定的节点,它也不适用于子节点。好比说,若是 /app节点只能被ip:172.16.16.1读取, /app/status是所有可读的,任何人都 能够读取/app/status。ACLs不是递归的。
ZooKeeper支持可插拔式的认证方案。Ids指定使用这个形式scheme:id,scheme是id对应的受权方案,好比说,ip:172.16.16.1是一个主机地址为172.16.16.1的id。
当一个客户端链接ZooKeeper并进行认证,ZooKeeper把符合这个客户端的全部ids联系起来。当客户端尝试存取一个节点的时候,这些ids用来检查一个节点的ACLs。ACLs由成对(scheme:expression, perms)的组成。expression的格式指定了权限,好比说,(ip:19.22.0.0/16, READ)给全部的以19.22开头的IP地址的客户端读的权限。
ZooKeeper支持下列权限:
- CREATE:能够建立一个子节点
- READ:能够从一个节点读取数据并展现子节点
- WRITE:能够设置一个节点的数据
- DELETE:能够删除一个子节点
- ADMIN:能够设置权限