概述
ZooKeeper是一个分布式的、开源的、用于分布式应用的协调服务 。它公开了一组简单的原语,分布式应用程序可以在这些原语的基础上构建,以实现用于同步、配置维护、分组和命名的高级服务。它被设计为易于编程,并使用了一种类似于我们所熟悉的文件系统目录树结构的数据模型。它在Java中运行,并具有Java和C的绑定。
众所周知,协调服务很难做好。它们特别容易出现竞争条件和死锁等错误。ZooKeeper背后的动机是减轻分布式应用程序从头开始实现协调服务的责任。
设计目标(Design Goals)
ZooKeeper很简单 。ZooKeeper允许分布式进程通过一个共享的分层命名空间 相互协调,该命名空间的组织方式类似于标准文件系统 。命名空间由数据寄存器 组成------在ZooKeeper术语中称为Znode ------它们类似于文件和目录。与为存储而设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。
ZooKeeper的实现非常注重高性能 、高可用性 和严格有序的访问 。ZooKeeper的性能 方面意味着它可以用于大型分布式系统。可靠性 方面使它不会成为单点故障。严格的顺序意味着可以在客户端实现复杂的同步原语。
ZooKeeper可复制。就像它协调的分布式进程一样,ZooKeeper本身也打算在一组被称为ensemble的主机上进行复制。
组成ZooKeeper服务的服务器必须都知道彼此 。它们维护内存中的状态映像,以及持久化存储中的事务日志和快照。只要大多数服务器可用,ZooKeeper服务就会可用。
客户端连接到单个ZooKeeper服务器。客户端维护一个TCP连接,通过该连接发送请求、获取响应、获取观看事件和发送心跳。如果与服务器的TCP连接中断 ,客户端将连接到另一个服务器。
ZooKeeper是有序的 。ZooKeeper用一个数字标记每次更新,这个数字反映了所有ZooKeeper事务的顺序。后续操作可以使用该顺序来实现更高级别的抽象,例如同步原语。
ZooKeeper是快速的 。它在"读主导"工作负载中特别快。ZooKeeper应用程序在数千台机器上运行,当读比写更常见时,它的性能最好,比例约为10:1。
数据模型和分层命名空间(Data model and the hierarchical namespace)
ZooKeeper提供的命名空间与标准文件系统的命名空间非常相似。名称是由斜杠(/
)分隔的路径元素序列。ZooKeeper命名空间中的每个节点都由一个路径标识。
节点和临时节点(Nodes and ephemeral nodes)
与标准文件系统不同,ZooKeeper命名空间中的每个节点都可以有与其关联的数据 以及子节点。这就像拥有一个文件系统,它允许一个文件同时也是一个目录。(ZooKeeper被设计用来存储协调数据:状态信息、配置、位置信息等,所以每个节点存储的数据通常很小,在字节到千字节的范围内。)我们使用术语znode是为了清楚地表明我们谈论的是ZooKeeper数据节点。
znode维护一个统计结构,其中包括数据更改的版本号、ACL更改和时间戳,以允许缓存验证和协调更新。每次znode的数据发生变化,版本号就会增加。例如,每当客户端检索数据时,它也会接收数据的版本。
存储在命名空间中的每个znode上的数据是自动读写的。读操作获取与znode相关的所有数据字节,写操作替换所有数据。每个节点都有一个访问控制列表(ACL)来限制谁可以做什么。
ZooKeeper也有临时节点(ephemeral node)的概念。只要创建znode的会话处于活动状态,这些znode就存在。会话结束时,znode被删除。
条件更新和监视器(Conditional updates and watches)
ZooKeeper支持监视器(watch)的概念。客户端可以在znode上设置监视器(watch)。当znode发生变化时,将触发并删除监视器。当监视被触发时,客户端接收到一个表示znode已更改的数据包。如果客户端与ZooKeeper服务器之间的连接断开,客户端会收到本地通知。
3.6.0新增功能:客户端还可以在一个znode上设置永久的、递归的监视,这些监视在触发时不会被删除,并且会递归地触发对已注册的znode以及任何子znode的更改。
保证(Guarantees)
ZooKeeper非常快,非常简单。但是,由于它的目标是作为构造更复杂的服务(如同步)的基础,因此它提供了一组保证。包括:
- 顺序一致性 ------来自客户端的更新将按照它们被发送的顺序应用。
- 原子性------更新要么成功要么失败。没有部分结果。
- 单一系统映像------客户端将看到服务的相同视图,而不管它连接到哪个服务器。也就是说,即使客户端故障转移到具有相同会话的不同服务器,客户端也永远不会看到系统的旧视图。
- 可靠性------一旦应用了更新,它将从那时起持续存在,直到客户端覆盖更新。
- 及时性------保证系统的客户端视图在一定的时间范围内是最新的。
简单的API(Simple API)
ZooKeeper的设计目标之一是提供一个非常简单的编程接口。因此,它只支持以下操作:
- 创建(
create
):在树中创建一个节点 - 删除(
delete
):删除一个节点 - 存在(
exists
):测试一个节点是否存在 - 获取数据(
get data
):从节点读取数据 - 设置数据(
set data
):写入数据到节点 - 获取子节点(
get children
):检索节点的子节点列表 - 同步(
sync
):等待将传播的数据
实现(Implementation)
ZooKeeper组件展示了ZooKeeper服务的高层级组件。除了请求处理器之外,组成ZooKeeper服务的每个服务器都复制了每个组件的副本。
复制的数据库是一个内存数据库,包含整个数据树。更新被记录到磁盘以实现可恢复性,写操作在应用到内存数据库之前被序列化到磁盘。
每个ZooKeeper服务器都服务于客户端。客户端只连接到一台服务器来提交请求。读取请求由每个服务器数据库的本地副本提供服务。更改服务状态的请求、写请求由agreement协议处理。
作为agreement协议的一部分,所有来自客户端的写请求都被转发到一个称为leader的服务器 。其余的ZooKeeper服务器,称为follower,接收来自leader的消息建议并同意消息传递。消息传递层负责替换失败的leader者,并将follower与leader同步。
ZooKeeper使用自定义原子消息传递协议。由于消息传递层是原子的,ZooKeeper可以保证本地副本永远不会发散。当leader接收到写请求时,它计算要应用写请求时系统的状态,并将其转换为捕获此新状态的事务。
使用(Uses)
ZooKeeper的编程接口非常简单。但是,有了它,您可以实现更高阶的操作,例如同步原语、组成员关系、所有权等。
性能(Performance)
ZooKeeper的设计是高性能 的。但这是真的吗?ZooKeeper开发团队在雅虎研究表明确实如此。(参见ZooKeeper吞吐量随读写比变化。)在读多于写的应用程序中,它的性能特别高,因为写涉及到同步所有服务器的状态。(对于协调服务来说,读多于写是典型的情况。)
ZooKeeper吞吐量随读写比变化是ZooKeeper 3.2发布版本在双2Ghz Xeon和两个SATA 15K RPM驱动器上运行的吞吐量图。其中一个驱动器用作专用ZooKeeper日志设备。将快照写入操作系统驱动器。写请求为1K写,读请求为1K读。"servers"表示ZooKeeper集合的大小,即组成服务的服务器数量。大约有30个其他服务器被用来模拟客户机。ZooKeeper ensemble被配置为leader不允许来自客户端的连接。
注意:与之前的3.1版本相比,3.2版本的r/w性能提高了约2倍。
基准测试也表明它是可靠的。存在错误时的可靠性显示了部署如何响应各种故障。图中标记的事件如下:
- follower的失败和恢复
- 不同follower的失败和恢复
- leader的失败
- 两个follower失败和恢复
- 其它leader的失败
可靠性(Reliability)
为了显示故障注入时系统随时间的行为,我们运行了一个由7台机器组成的ZooKeeper服务。我们运行与以前相同的饱和基准测试,但这次我们将写百分比保持在恒定的30%,这是我们预期工作负载的保守比例。
这张图中有一些重要的观察结果。首先,如果follower失败并快速恢复,那么ZooKeeper能够在失败的情况下保持高吞吐量。但也许更重要的是,leader选举算法允许系统足够快地恢复,以防止吞吐量大幅下降。在我们的观察中,ZooKeeper花了不到200毫秒的时间来选出一个新的leader。第三,随着追随者的恢复,ZooKeeper能够在他们开始处理请求时再次提高吞吐量。
ZooKeeper项目
ZooKeeper已成功用于许多工业应用。作为雅虎的协调和故障恢复服务。消息代理(Message Broker),它是一个高度可伸缩的发布-订阅系统,管理用于复制和数据传递的数千个主题。它被雅虎的抓取服务使用。Crawler,它还在其中管理故障恢复。一些雅虎广告系统也使用ZooKeeper来实现可靠的服务。
鼓励所有用户和开发人员加入社区并贡献他们的专业知识。有关更多信息,请参阅Apache上的ZooKeeper项目。