学习HBase过程中,发现它与ZooKeeper的关系比较密切,于是专门学习了一下ZooKeeper,下面是ZooKeeper官方文档的半翻译版并非一字一句的照抄,而是写入了自己的理解)。此文档是为了向那些希望使用ZooKeeper协调优势自己分布式应用的人提供编程向导,它包含了理论和实战两部分!前四节将对ZooKeeper进行理论上比较深入的探讨,这对我们理解ZooKeeper的工作原理和怎么使用ZooKeeper非常重要。此四节不包含编程的源代码,但是它会通过讲解一些分布式计算相关的问题来让你对ZooKeeper加深了解:
- ZooKeeper数据模型
- ZooKeeper会话(Sessions)
- ZooKeeper监视(Watches)
- 确保全局一致性
ZooKeeper数据模型
ZooKeeper有一个属性的命名空间,它更像一个分布式文件系统。不同之处在于此命名空间中的每个节点可以访问它自身的数据和子节点的数据。节点的路径总是以斜杠分开这样的形式(/node/childNode)表示。ZooKeeper的路径可以以任何的Unicode字符组成,除了下列这些情况:
- null字符(\u0000)不能出现在路径名中(这样做会导致与C交互是出现问题?)。
- 接下来这些不能正常显示或导致路径混乱的字符也不能出现在路径名中:\u0001 - \u0019, \u007F - \u009F
- 这些字符也不允许使用:\ud800 -uF8FFF, \uFFF0-uFFFF, \uXFFFE - \uXFFFF (X为1-E的16进制数字), \uF0000 - \uFFFFF.
- 字符“.”可以出现在路径名中,但是“.”,“..”绝对不能单独成为路径名。众所周知,Linux文件系统中这两个路径表示了什么!
- zookeeper是保留字符,也不能用。
ZNode
ZooKeeper路径中的每个节点都叫做一个ZNode。Znode持有一个状态数据结构,此结构中包含数据更新的版本号、访问权限(ACL)更新的版本号、时间戳。这些版本号和时间戳使ZooKeeper可以验证缓存有效性和协调更新。每当ZNode中的数据更新时,版本号都会递增。例如,当客户端获取数据时,它(客户端)也会接收到此数据对应的版本号。当此客户端下次执行更新(或删除)数据操作时,它必须向ZNode提供这些数据的版本号(是客户端当前持有的版本号)。如果此版本号与ZNode本身的版本号不一致,即其他客户端已经更新了此数据,那么此更新就会执行失败。
提示:在分布式应用工程中,节点可以是一个正常的主机、一个服务器、一个集群、一个客户端程序等等。在ZooKeeper文档中,ZNode表示这些数据节点在ZooKeeper服务器中的引用。quorum peers表示ZooKeeper集群中的服务器。客户端表示任何使用ZooKeeper服务的主机或进程。
ZNode是我们编程访问的主题,上面的提示非常值得引起你的注意!
Watches
客户端可以在ZNode上面添加一个Watch。之后此ZNode的改变都会激活并清除(?)此Watch,当Watch激活时,ZooKeeper会向此客户端发送一个通知,关于Watch的更多信息参见“ZooKeeper监视(Watches)”小节。
数据访问:命名空间中存储在每个ZNode中数据的读写操作都是原子性的,读操作会获取与此ZNode相关联的所有数据字节,写操作会替换所有数据。每个节点都会有一个权限限制列表(ACL),此列表会约束哪些客户端有权对此ZNode做哪些数据操作。ZooKeeper并没有被设计为常规的数据库或者大数据存储。相反的是,它用来管理调度数据,比如分布式应用中的配置文件信息、状态信息、汇集位置等等。这些数据的共同特性就是它们都是很小的数据,通常以KB为大小单位。ZooKeeper的服务器和客户端都被设计为严格检查并限制每个ZNode的数据大小至多1M,当时常规使用中应该远小于此值。关系型大数据的操作会导致一些其他操作耗费更多的时间,并且因为大数据的网络传输和写入存数介质速度过慢也会导致其他操作的延迟。如果你需要存储大数据库,通常的做法是将这些大数据存储在NFS或HDFS中,然后将这些大数据的存储位置指针存储在ZooKeeper的ZNode中。
瞬时ZNode:ZooKeeper中也有瞬时ZNode的概念,这些ZNode仅存在于创建此ZNode的会话的有效期内。当此会话结束后此ZNode也会被删除,由于瞬时ZNode的特性,它不能有子节点。
顺序节点 -- 唯一命名:当创建一个ZNode时候,你也可以请求ZooKeeper将一个单调递增计数器添加在路径之后。此计数器对于父节点是唯一的。
ZooKeeper中的时间
ZooKeeper可以以不同的方式跟踪时间:
- Zxid:每次对ZooKeeper的改变都会收到一个zxid(ZooKeeper事务ID)格式的戳。它表示ZooKeeper中所有改变的总顺序,每次改变都会有一个唯一的zxid,如果zxid1的值小于zxid2,那么zxid1会在zxid2之前发生。
- 版本号:每个ZNode的数据更新都会导致此ZNode版本号的增加。三个版本号分别为:version(ZNode中数据更改的次数)、cversion(ZNode的子节点的改变次数)、aversion(ZNode的ACL改变次数)。
- 心跳(Ticks):在使用多服务器集群的ZooKeeper时候,服务器之间会使用心跳来限定事件时间,比如状态传递、会话超时、连接超时等等。心跳时间通过最小会话失效时间(minimum session timeout)指定。如果客户端请求的会话超时时间小于最小会话失效时间,服务器会告诉客户端服务器端指定的会话失效时间作为真实会话失效时间。
- 真正时间:ZooKeeper并不使用真正的时间或者时钟时间,当然它会在ZNode创建和修改时候使用真正时间作为时间戳。
ZooKeeper的状态结构
ZooKeeper中每个ZNode的状态数据结构都是由下列字段组成
- czxid:导致此ZNode创建的zxid(ZooKeeper事务ID)
- mzxid:最后一次修改此ZNode的zxid
- ctime:从此ZNode创建到现在为止的时间毫秒数
- mtime:从此ZNode上次修改到现在为止的时间毫秒数
- version:此Znode的数据修改次数
- cversion:此ZNode的子节点修改次数
- aversion:此ZNode的ACL修改次数
- ephemeralOwner:如果此ZNode是一个瞬时节点,此值表示此ZNode对应的会话ID。如果不是瞬时节点则为0
- dataLength:此ZNode中数据的长度
- mumChildren:此ZNode的子节点数量
ZooKeeper会话(Sessions)
ZooKeeper客户端可以通过创建一个ZooKeeper的句柄,从而与ZooKeeper服务建立一个会话(session)。会话创建之后,句柄的初始状态为CONNECTING状态,此时客户端句柄会尝试与ZooKeeper服务器建立连接,此服务器会指明此句柄将状态设置为CONNECTED。在常规操作中只会有这两种状态,如果有任何不可恢复的错误发生,比如会话失效、权限验证失败、应用强制关闭句柄等等,此句柄状态会转变为CLOSED状态。下图展现了客户端句柄可能出现的状态转换:
为了创建一个客户端会话,应用程序代码必须提供一个连接字符串,此字符串包括一系列以逗号分开的“主机名:端口”对,每个“主机名:端口”均对应一个ZooKeeper服务器(例如:127.0.0.1:3000,128.0.0.1:2001)。ZooKeeper的客户端句柄会随机挑选一个ZooKeeper服务器并尝试与之创建连接。如果连接建立失败,或因为任何原因客户端与此服务器断开连接,此客户端都会自动尝试列表中的下一个服务器,直到连接建立。 |