作者:拥抱心中的梦想
一 Zookeeper:一个为分布式应用设计的分布式协调服务
ZooKeeper是一款开源的 分布式应用 的 分布式协调服务 。它包含一个简单的 原语集 ,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。Zookeeper 设计很容易进行编程,它使用一种类似于文件系统的目录树结构的数据模型,以 java 方式运行,有 java 和 c 的绑定(binding)。 协调服务是非常难以被正确实现的。他们特别容易产生诸如竞态条件、死锁等错误。ZooKeeper背后的动机是为分布式应用程序减轻从零开始实现协调服务的难度。
二 设计目标
2.1 Zookeeper是非常简单的
ZooKeeper允许分布式进程通过与标准文件系统类似组织的共享层级命名空间来相互协调。命名空间被称为znode的数据记录组成,用ZooKeeper的说法,这些记录和标准文件系统中的文件和目录非常相似。与典型的用于存储的文件系统不同,ZooKeeper数据保存在内存中,这意味着 ZooKeeper可以实现高吞吐量和低延迟 。
Zookeeper的实现着重于 高性能 、高可用性 和 严格的顺序访问 。ZooKeeper的性能方面意味着它可以用于大型分布式系统。可靠性方面使它不会造成单点故障。严格的排序意味着可以在客户端实现复杂的同步原语。
2.2 Zookeeper 是复制的(replicated)
就像它协调的分布式进程一样,Zookeeper 自身也在被称为“ensemble”的一组主机之间进行复制。
组成 Zookeeper 服务(Service)的每个服务器(server)之间都必须相互 了解对方 。他们维护一个 内存状态图,以及一个 持久存储的事务日志和快照 。只要这些服务器(servers)中大多数是可用的,整个ZooKeeper服务就是可用的。
客户端(client)连接到任意一台ZooKeeper服务器。客户端维护一个TCP连接,通过它发送请求、获取响应、获取监视事件以及发送心跳。如果到服务器的TCP连接中断,客户端将连接到其他不同的服务器。
2.3 Zokeeper是有顺序的
ZooKeeper使用反映所有ZooKeeper事务顺序的数字来标记每个更新。后续操作可以使用该次序来实现更高级别的抽象,例如同步原语。
2.4 ZooKeeper 是快速的
在应对以“读”为主的负载时尤其地快速。ZooKeeper应用程序在数千台机器上运行,并且在读取比写入更为普遍的情况下,性能表现最佳,比例约为10:1。
三 Zookeeper数据模型和层级命名空间
ZooKeeper提供的命名空间与标准文件系统非常相似。路径是由斜杠/分隔的一系列元素。 ZooKeeper命名空间中的每个节点都由一个路径标识。
四 Zookeeper默认节点和临时节点
与标准文件系统不同的是,ZooKeeper命名空间中的每个节点都可以拥有与其相关的数据以及子节点。这就像一个文件系统中可以存在一个文件或一个目录。 ZooKeeper被设计用来存储相关的协调数据,如状态信息、配置、位置信息等等,所以 每个节点上存储的数据通常都很小,在字节(byte)到千字节(kb)范围内。我们使用术语znode来清楚地说明我们正在讨论ZooKeeper数据节点。
Znode 维护了一个状态(stat)结构 ,其中包含了表示数据改变、访问控制列表(ACL)改变的版本号、时间戳,可用于缓存校验、协调更新。每当一个znode的数据发生变化,版本号就会增加。例如,每当客户端检索数据时,客户端也会接收到相应数据的版本信息。
存储在命名空间中每个节点上的数据是以原子方式读取和写入的。读取一个znode将获得其全部的数据,而写入则替换其全部的数据。
ZooKeeper也有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。
五 Zookeeper条件更新和监视(watches)
ZooKeeper 支持“监视”(watches)的概念。客户端可以在znode上设置一个监视(watch)。当 znode改变时,监视(watch)将被触发并移除。当监视(watch)被触发时,当“监视”被触发时,客户端会收到一个描述了 znode 的变更的数据包。如果客户端和Zookeeper服务器之间的连接断开时,客户端将会收到一个本地通知。
六 Zookeeper保证(Guarantees)
Zookeeper非常地快速也非常简单。不过,由于它的目标是作为构建诸如“同步”这类更复杂服务的基础,它提供了一些的一组保证:
-
顺序一致性 - 来自客户端的更改请求将会按照它们的发送的顺序被应用。
-
原子性 - 更改要么成功,要么失败,不会存在部分成功、部分失败的结果。
-
单一系统映像 - 客户端会看到 Zookeeper 服务的相同的视图,而无论它们连到具体哪一个服务器上
-
可靠性 - 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
-
及时性 - 客户端看到的系统视图在一定的时间范围内总是最新的。
七 简单的API
ZooKeeper的一个设计目标是提供一个非常简单的编程接口。 因此,它只支持这些操作:
-
create: 在(命名空间)树的一个特定地址上创建一个节点。
-
delete: 删除一个节点。
-
exists: 判断某个路径下是否存在该节点。
-
get data: 获取节点的数据。
-
set data: 向节点写入数据。
-
get children: 检索节点的子节点列表。
-
sync: 等待数据传播完成。
八 Zookeeper实现原理
ZooKeeper Components 显示了ZooKeeper服务的高级组件。除Request Processor外,构成ZooKeeper服务的每个服务器都复制每个组件的副本。
replicated database是一个内存数据库,它包含了整颗数据树。数据写入在应用到内存数据库之前,会先序列化到磁盘。
每一个 Zookeeper 服务器都向客户端提供服务,客户端连接到一个确切的Zookeeper服务器提交请求。读请求从服务器数据库的本地拷贝中获取。改变Zookeeper服务状态的请求、写入请求通过一个 一致性协议 进行处理。
作为协议的一部分, 客户端的所有写入请求都被转发到一个单独的服务器,该服务器被称为 leader 。而其余的服务器,被称为 follower ,从leader接收消息提案(proposal)并对消息的交付取得一致。消息层维护leader失效时的更新替换以及leader和follower之间的同步。
Zookeeper 使用自定义的原子消息协议。 由于消息层是原子的,Zookeeper可以保证本地的复制品不会不一致。当 leader收到一个写入请求时,它计算系统所处的状态以及何时应用写入请求,并将此转换为一个事务,包含新的状态。
九 使用
Zookeeper 的编程接口特意地定义得很简单。然而,通过这些编程接口可以更高阶的操作,例如同步原语,成员分组,所有权,等等。
十 性能
Zookeeper 被设计为高性能。但实际是否如此呢?在雅虎研发中心的 Zookeeper 开发团队的研究结果表明的确如此。(参见下图:Zookeeper 吞吐量随读写比的变化)。在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致在所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
Zookeeper 吞吐量随读写比的变化:
图“Zookeeper 吞吐量随读写比的变化” 是 Zookeeper3.2 版本运行于 Dual 2Gh Xeon + 2 个 15K RPM 的 SATA 硬盘驱动器的服务器上的结果。一个驱动器用作 Zookeeper 专用的日志设备。快照写到操作系统驱动器。写请求是 1K 数据的写入而读请求是 1K 的数据读取。“Servers”标出了 Zookeeper Ensemble 的大小,即组成 Zookeeper 服务的服务器的数量。大约30台其它的服务器被用作模拟客户端。Zookeeper Ensemble 被配置为不允许客户端连接到 Leader 。
注:3.2版本的读/写性能相对于3.1版本以前有最多达2倍的提升。
基准测试也表明了 Zookeeper 的可靠性。图“错误发生的情况下的可靠性”展示了 Zookeeper 是如何应对各种不同的失效的。图中标注的事件如下:
-
一个 Follower 失效然后恢复。
-
另一个不同的 Follower 失效然后恢复。
-
Leader 失效。
-
两个 Follower 失效然后恢复。
-
另一个 Leader 失效。
十一 可靠性
从这张图中可以得到几点重要的结果。首先,如果 follower 失效并快速恢复,Zookeeper 能够维持高吞吐量,尽管存在失效。但也许更重要的是,leader选举算法使系统足够快地恢复,避免了吞吐量的总体下降。从观察结果来看,Zookeeper花了不到200毫秒的时间选举出了一个新的 leader。第三,只要follower恢复,Zookeeper的吞吐量能够再次上升到刚开始处理请求时的水平。
十二 关于ZooKeeper项目
Zookeeper 已经被成功地用在许多工业级的应用。在雅虎,Zookeeper被用作雅虎消息中间件的协调和失效恢复服务,该系统是一个高伸缩性的发布订阅系统,管理着成千上万的主题复制和数据分发。Zookeeper还被用在雅虎爬虫的抓取服务上,用于管理失效恢复。许多雅虎的广告系统也用 Zookeeper 实现可靠的服务。