--- layout: post title: ZooKeeper理论知识 category: 技术 tags: ZooKeeper keywords: description: 初识ZooKeeper --- {:toc} ### 1. 简介 zookeeper是Google的Chubby的一个开源实现,是hadoop的分布式协调服务 ZooKeeper的是分布式应用的分布式,开放源码的协调服务。它公开了一套简单的分布式应用程序可以建立在为同步,配置维护,组服务和命名实施更高级别的服务原语集。它被设计为易于编程,使用熟悉的文件系统目录树结构风格的数据模型。它运行在Java中,并支持Java和C。 协调服务是出了名很难得到正确。它们特别容易出错,如竞态条件和死锁。背后的ZooKeeper的动机是缓解分布式应用程序从头开始实施协调服务的责任。 ### 2. Zk的设计目标 - a.简单化: 通过共享体系的命名空间进行协调,与文件系统相似,有一些数据寄存器组成,被称为Znode,与文件和目录类似。与典型的文件系统不同的是,Zk的数据是放在内存中的,因此zk可以达到高吞吐量、低延迟。 Zk能用在大型、分布式的系统。 严格的序列访问控制意味者复杂的控制源可以用在客户端上。 - b.健壮性: zk互相知道其他服务器存在。维护一个处于内存中的状态镜像,以及一个位于存储器中的交换日志和快照。只要大部分服务器可用,zk服务就可用。 客户端连接到一个单一的ZooKeeper服务器。客户机维护通过它发送请求时,获取响应,获取观看事件,并发送心脏跳动一个TCP连接。如果TCP连接到服务器断开,客户端将连接到不同的服务器。 - c.有序性: zk为每次更新赋予一个版本号,全局有序。 - d.速度优势: 去读主要负载时尤其快,当读操作比写操作多时,性能会更好。 - e.Zk还有原子性、单系统镜像、可靠性和实效性特点。 ### 3.数据模型和分级名字空间 ZooKeeper提供的namespace很像标准的文件系统,名称是用斜杠(/)分隔路径元素的序列。在ZooKeeper的名字空间中的每个节点由路径标识。 ![分级名字空间](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zookeeper12.jpg) ### 4.节点和临时节点 不像标准FS,ZooKeeper命名空间中的每一个Node节点都可以拥有其及其子节点的数据,就像那允许文件也是目录的FS。ZooKeeper的目标是要存储协调数据、状态信息、配置和本地信息等。所以存储在每个节点上的数据很少,在KB范围内。用术语ZNode可以更清楚的表达我们讨论的是ZooKeeper的数据节点 ZooKeeper保持了包含数据改变、ACL改变、时间戳的版本信息的stat结构。允许缓存验证和协调的更新。每次的znode数据的改变,版本号也会增长。 存储在一个空间的每个znode的数据被读出,并以原子写入。读取获得与znode相关联的所有数据字节和一个写替换所有的数据。每个节点都有限制谁可以做什么的访问控制列表(ACL)。 动物园管理员还具有临时节点的概念。这些znodes存在,只要创建该znode会话处于活动状态。当会议结束znode被删除。当你想实现短暂的节点是有用的[待定]。 #### Zookeeper状态结构 zookeeper中的每个znode节点的状态结构体都包含以下 属性: - czxid znode被创建时造成的zxid的改变 - mzxid znode最后修改时的zxid的改变 - ctime znode创建的时间(毫秒/单位) - mtime znode最后修改的时间(毫秒/单位) - version znode上数据改变的版本号 - cversion 该znode的子节点的数据改变的版本号 - aversion 该节点的ACL改变的版本号 - ephemeralOwner 如果节点是临时节点,其为该节点拥有者的session的id。如果节点为非临时节点,该值为零。 - dataLength 该znode节点的数据属性长度 - numChildren 该znode的子节点的数量 示例: ![创建并查看znode](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zookeeper-1.png) ### 5.有条件的更新和监控 ZooKeeper支持监控的概念,客户端可以设置一个znode上的监控,当znode改变时,触发器会被触发或删除。当触发器被触发时,客户端会收到znode改变的通知。 并且,如果客户端和一个ZooKeeper服务节点断开,客户端将接收本地通知。 ### 数据访问 zookeeper不是设计用来通用的DB或者大对象存储的,他是管理协调数据的。管理的数据可以包括:配置,状态信息,集合点等。属性很小,也就KB级别。 客户端和服务端的实现会明确检查znode节点的数据是否超过1M大小。 在相对比较大的节点操作会造成一些操作花费更长的时间并且会影响一些操作的潜在因素因为会花费额外的时间在网络上移动更多的数据到存储介质。 如果需要存储大量的数据,通常模式是使用如NFS或者HDFS等容量存储上处理这样的数据。并且在ZK中存储这些数据的存储位置。 ### zk的session 状态图: ![session状态分析](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/state_dia.jpg) 如果能建立连接,连接成功,状态为CONNECTED。 断开连接,状态为CLOSED时几种情况: - session失效 - 权限验证失败 - 应用异常关闭 创建客户端session时,会从提供的zk服务器列表中随机获取一个,如果不能成功连接,会继续寻找下一个直到连接建立成功或所有服务都不可访问。 服务器列表是以逗号分隔的`host:port`字符串。 在3.2以后,如果指定的服务端节点带有路径,如`host:port/app/a`,则以该路径为root起点。如客户端指定`/foo/bar`,则服务端解析为`/app/a/foo/bar`。 该特性在ZK的多租户环境下非常有用。 在客户端获得到zk服务的一个手柄(handle)时,zk会创建zk的session,一个62位数字分派给客户端,如果客户端连接到不同的zk服务,它会发送该session id作为连接握手的一部分。 基于安全考虑,服务端会为session id创建任何zk服务都能验证的密码。该密码在客户端建立会话时随session id一起发送给客户端。 在客户端与心得服务重新建立session时会随session id发送该密码。 session过期是由zk集群自动管理,而不是客户端。集群会根据在分发给zk客户端session时附带的过期时间值决定session是否过期。 session过期后,集群会删除该session拥有的所有临时节点。并立即通知所有监控该节点的已经连接上的客户端。 但session过期的客户端不会被通知,而且会被断开连接直到其重新与zk集群建立连接。 ### 6.担保 ZooKeeper的速度非常快,非常简单。因为它的目标,不过,是成为更复杂的服务,如同步的结构的基础上,提供了一组保证。这些是: - 顺序一致性 - 从客户端的更新将在它们发送的顺序应用。 - 原子 - 更新成功或失败。没有部分结果。 - 单一系统映像 - 一个客户将看到相同的服务的认识,而它连接到服务器。 - 可靠性 - 一旦更新已被应用,它会从那个时候向前持续到客户端覆盖更新。 - 时效性 - 该系统的客户端视图保证是向上最新结合的一定时间内。 ### 7.简单的API 一个动物园管理员的设计目标是提供一个非常简单的编程接口。其结果是,它仅支持这些操作: - 创建 在树中的位置创建一个节点 - 删除 删除一个节点 - 存在 判断节点是否存在 - 获取数据 从一个节点读取数据 - 设置数据 写入数据到节点 - 获取子节点 检索节点的子节点的列表 - 同步 等待要传播的数据 ### 8.实现 ![实现](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zk-theory-zkcomponents.jpg) ZooKeeper的组件显示的ZooKeeper服务的高层组件。与请求处理器外,各构成ZooKeeper服务的服务器进行复制其自身的各组成部分的副本。 ### 9.用途 ZooKeeper的编程接口有意被设计的很简单,但你可以实现更高阶的操作,比如同步原语、组成员关系、所有权等等。 ### 10.性能 ZooKeeper被设计用于高性能。 ZooKeeper的吞吐量读写比变化 ![吞吐量读写比变化](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zk-theory-zkperfRW-3.2.jpg) ### 11.可靠性 可靠性错误的存在 ![吞吐量读写比变化](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zk-theory-zkperfreliability.jpg) ### 12. Zk可以用来保证数据在zk集群之间的数据的事务性的一致 (一般数据在2M以下) ![Zk可以用来保证数据在zk集群之间的数据的事务性的一致](//raw.githubusercontent.com/George5814/blog-pic/master/image/zk/zookeeper1.png) ### 13. zookeeper进行leader选举 **核心思想:** - 1.首先创建EPHEMERAL目录节点,如”/election”; - 2.每个zookeeper服务器在此目录下创建一个SEQUENCE|EPHEMERAL类型节点”如/election/n_”; - 3.zookeeper将自动为每个zookeeper服务器分配一个比前面所分配的序号要大的序号,拥有最小编号的zookeeper服务器将成为leader。 为了能在leader发生意外时,整个系统能选出leader,需要所有的follower都监视leader所对应节点,当leader故障时,leader对应的临时节点将会被删除,会触发所有监视的follower的watch,从而进行选举leader操作。 缺点:这样的解决方案会导致`从众效应`。 **实现:** 每个follower为follower集群中对应着比自己节点序号小的节点中x序号最大的节点设置一个watch,只有当follower所设置的watch被触发时,他才进行leader操作,一般将其设置为集群的下一个leader。这样很快,因为每一leader选举几乎只涉及单个leaderfollower的操作 ### 14.zookeeper锁服务 - a.zookeeper中完全分布的锁是全局存在的。 - b.zookeeper的锁机制(实现加锁) i. zk调用create(),创建路径格式为”_locknode/lock_”的节点,此节点类型为sequence(连续)和ephemeral(临时),创建节点为临时节点,所有节点连续编号“lock-i”格式 ii. 在创建锁节点上调用getChildren()方法,以获取锁目录下的最小编号节点,并且不设置watch。 iii. 步骤2获取的节点是步骤1中客户端创建的节点,此客户端会获得该种类型的锁,然后退出操作。 iv. 客户端在锁目录上调用exists()方法,并设置watch来监视锁目录下序号相对自己次小的连续临时节点的状态。 v. 如果监视节点状态发生变化,则跳转到步骤2,继续后续操作直到退出锁竞争。 vi. Zookeeper解锁简单,只需在步骤1中创建的临时节点删除即可。 **PS:** - 1.一个客户端解锁后,将只可能有一个客户端获得锁,因此每个临时的连续节点对应一个客户端,并且节点间没有重叠; - 2.在zookeeper锁机制中没有轮询和超时。 ### 15. BooKeeper - 副本功能。 - 提供可靠的日志记录。 BooKeeper为每份日志提供了分布式存储,并且采用了大多数概念,就是说只要集群中大多数机器可用,那么该日志一直有效。 BooKeeper包含四个角色:账本(服务器)、账户(Ledger,账本中存储的一系列记录)、客户端(BooKeeper Client,允许APP在系统上进行操作,包括创建账户,写账户)、元数据存储服务(metadata storage service,存储关于账户和版本的信息) ### ACL权限 权限: - CREATE:创建子节点 - READ:获取节点或列出子节点 - WRITE:为节点设置数据 - DELETE:删除子节点 - ADMIN:设置权限 ### 一致性保证 ZK是高可用,可扩展的服务,读写操作都很快,尽管读比写更快。原因是读的时候,ZK可以提供老的数据,这反过来是由于ZK的一致性保证。 - 连续一致性: 来自于客户端的更新会应用于他们发送的顺序。 - 原子性: 更新要么成功,要么失败,没有中间状态。 - 简单的系统镜像 不管他是连接的哪一个服务,客户端看到的都是相同的服务视图。 - 可靠性: 一旦更新被提交,它将从那时持续知道客户端覆盖更新。 该保证有两个推论: - 如果客户端获取到成功的返回码,更新将会被提交。在一些失败情况下(通讯错误,超时等)客户端会不知道更新是否被提交。我们采取措施减少失败,但是保证只出现成功的返回码(在Paxos中叫单调性条件)。 - 对于客户端发现的任何更新,尽管读请求或成功更新,在服务失败恢复时将会不再回滚。 - 时效性: 系统的客户端视图保证在一定时间内(几十秒)保证最新的绑定。否则系统改变将不会被该该绑定的客户端发现,或者客户端察觉服务终端。 使用这些一致性保证,ZK客户端很容易构建高层的方法,如leader选举,栅栏,队列,只可撤销的读写所。 ### java API java中只要有两个包:`org.apache.zookeeper`和`org.apache.zookeeper.data`。ZK的其他包用于内部或作为服务实现的一部分。 `org.apache.zookeeper.data`包被编织为生成类,就像容器一样简单使用。 ZK的java客户端使用的主要类是`ZooKeeper`,其中两个构造方法是可选session id和password的。zk支持通过进程实例的session回复。java程序可能保存它的session id和密码到稳定的存储中,重启和回复session都会使用程序早先的实例。 在`ZooKeeper`对象呗创建后,会创建两个线程:IO线程和事件线程。所有的IO操作通过IO线程(使用java的NIO),所有的事件回调使用时间线程。Session维护比如重连到ZK服务,维护IO线程上的心跳。同步方法的响应也在IO线程上执行。所有的异步方法的相应和监控时间在时间线程处理。 从该设计中有几件事需要注意: - 异步调用和监控回调的所有完成将会按顺序,一次一个。调用者可以做任何处理,但是没有其他回调在这期间会被处理。 - 回调不会阻塞IO线程的处理或同步调用的处理。 - 同步调用可能不会按照正确顺序返回。比如,假设客户端做了如下操作:发送了一个异步读节点`/a`,并且监控设置为true。 然后在读的回调完成中他做了一个同步读`/a`。 注意:如果在异步读和同步读的时候`/a`有改变,客户端库在同步读回应之前受到监控时间通知`/a`已经改变,但是因为完成的回调阻塞在时间队列,在监控事件被处理之前,同步读会返回`/a`的一个新值。 最后,与关闭相关的规则很简单:一旦ZooKeeper对象关闭或受到失败时间(session过期或者权限失败),ZooKeeper对象就会变为肥大。关闭时,连个线程关闭并且任何进一步的访问不会被定义行为并且应该避免。