ZooKeeper:分布式应用程序的分布式协同服务
ZooKeeper是分布式的、开源的可应用于分布式应用程序的分布式协同服务。分布式应用程序可以基于ZooKeeper暴露的一套简单的原语构建实现高级别的同步、配置管理、集群管理和名称管理服务。ZooKeeper被设计的易于编程,并且使用类似文件系统的文件树结构的数据模型。它基于Java运行,并且提供Java和C的绑定。
协同服务很难做好是众所周知的,它们特别容易出现类似竞争条件和死锁的错误。设计ZooKeeper的初衷就是减轻分布式应用从零开始实现协同服务的责任。
设计目标
ZooKeeper是简单的.ZooKeeper允许分布式进程通过一个以类似标准文件系统形式组织的共享层次命名空间来进行协作。命名空间由被称为znodes的数据注册者组成,用ZooKeeper的话来说,这些类似文件和目录。不同于典型的被设计用于存储的文件系统,ZooKeeper数据是保存在内存中的,也就意味着ZooKeeper可以实现高吞吐量和低延迟数。
ZooKeeper实现非常重视高性能、高可用和严格有序的访问,由于它的高性能,因而它可以用于大型分布式系统中,由于它的高可用,避免了单点服务停止而导致整个服务停止,更由于它的严格有序,使得客户端可以实现复杂的同步原语。
ZooKeeper是可被复制的.就像它协调的分布式进程一样,ZooKeeper自身通常在被称为集群的一组主机上进行复制。
组成ZooKeeper服务的服务器彼此间必须是直接或间接了解的(或者说可以相互通信)。它们在内存中维护映像的状态,在持久化设备中维护事务日志和快照。只要大多数服务器可以用,ZooKeeper服务就是可用的。
客户端可连接到某一个ZooKeeper服务器。客户端通过发送请求、接收响应、获取监控事件和发送心跳来维护一个TCP连接。如果TCP连接中断了,客户端将连接到另外的ZooKeeper服务器上。
ZooKeeper是有序的.ZooKeeper给每个更新操作都贴上一个数字标签来标记所有ZooKeeper服务的事务顺序,后续的操作可以使用这个数字标签来实现更高级别的抽象,例如同步原语。
ZooKeeper是高速的.它在以读为主的业务场景下特别快。ZooKeeper应用运行在数千台机器上,在“读多写少”的情况下表现很好,比率约为10:1.
数据模型和层级命名空间
ZooKeeper提供的命名空间非常类似标准文件系统。一个名称就是一个通过斜杆分隔的路径元素序列。ZooKeeper命名空间中的每个节点都是通过路径区别的。
ZooKeeper的层级命名空间
节点和临时节点
不同于标准文件系统,ZooKeeper命名空间中的每个节点都可以有与其关联的数据和子节点,这就像一个文件系统允许一个文件同时也是一个目录。(ZooKeeper被设计来存储协同数据:状态信息、配置信息、位置信息等等,因此每个节点存储的数据通常都是很小的,一般不超过1M。)我们用znode属于来表名我们在讨论ZooKeeper的数据节点。
Znodes维护一个包含数据更改、ACL更改、时间戳的版本号的状态结构,以此来允许缓存验证和协同更新。每当znode节点的数据发生改变,节点的版本号都会增加。举例来说就是:每当客户端获取数据时,也会获取到数据的版本。
存储在命名空间中的每个节点的数据读写都是原子性的(要么都读取成功,要不都读取失败)。读取将获取节点相关的所有数据,而写入将替换所有的数据。每个节点都有一个ACL(访问控制列表)用于限制谁可以做什么。
ZooKeeper也有临时节点的概念。只要创建这些节点的会话是活动的,这些节点将一直存在。当会话结束了,这些临时节点也将被删除。当你想实现[tbd]的时候,临时节点会非常有用。
条件更新和监控
ZooKeeper支持监控的理念。客户端可以在一个znode节点上设置监控,当这个节点发生改变时监控将被触发并移除。当监控被触发时,客户端将收到一个数据包告知该节点发生了改变。如果客户端和ZooKeeper服务器中的一个连接断开了,客户端会收到一个本地通知。这可用于某些[tbd]用途。
保证
ZooKeeper非常快而又非常简单。不过,因为它的目标是成为构建更复杂的应用的基础,例如同步,因而它提供类一套保证,分别是:
- 顺序一致性:客户端的更新将按顺序发送。
- 原子性: 更新要么都成功,要么都失败,不会有部分成功或失败。
- 独立系统镜像:无论客户端连接到哪个服务器上,看到的服务视图都是相同的。
- 可靠性:一旦应用被更新,它将一直存在,直到客户端覆盖这个更新。
- 时效性:系统的客户端视图保证在一定的时间范围内是最新的。
了解更多关于这方面的信息和学习如何使用,可以看[tbd]。
简单的API
ZooKeeper的一个设计目标就是提供非常简单的编程接口。因而,它仅支持如下操作:
- create(创建):在层次结构树的某个位置创建一个节点;
- delete(删除):删除一个节点;
- exists(判断是否存在):测试某个位置是否存在节点;
- get data(读取数据):从节点上读取数据;
- set data(设置数据):向某个节点写入数据;
- get children(获取子节点):获取某个节点的子节点列表;
- sync(同步):等待要传输的数据。
获取相关更深层次的探讨、了解它们怎样可以实现更高级的操作,请参考[tbd]。
实现
ZooKeeper组件展示了ZooKeeper服务的高级组件。除了请求应用程序,组成ZooKeeper服务的每个服务器都复制自己每个组件的副本。
副本数据库是一个包含整个数据树的内存数据库。更新被记录到磁盘上以便恢复,写操作在应用到内存数据库之前被序列化到硬盘上。
每个ZooKeeper服务器都为客户端提供服务。客户端仅连接到一个服务器上来提交请求。读取请求由每个服务器数据库的服务副本提供服务。更改服务状态的请求(写请求)由一致性协议处理。
作为一致性协议的一部分,所有来自客户端的的写请求都被转发到一个被称为主服务器的服务器上。其余的服务器被称为从服务器,主要接受主服务器的消息建议,并就消息传递达成一致性。消息层负责在主服务器失败时替换掉主服务器,并同步主服务器和从服务器。
ZooKeeper使用一个自定义的原子性消息传递协议。由于消息层是原子性的,ZooKeeper可以保证本地副本不会发生分歧。当主服务器收到一个写请求时,它将计算执行这个写入操作时系统的新状态和这个操作转化为一个事务的新状态。
用途
ZooKeeper的编程接口非常简单。但是,你依然可以实现高级的操作,例如同步原语、组的成员关系、所有权等。一些分布式应用可以使用它。
性能
ZooKeeper是设计成高性能的。但真是这样吗?雅虎的ZooKeeper开发团队的研究结果表明确实如此(查看下图读写率变化时的吞吐量)。在读多于写的应用中它表现很好,因为读涉及所有服务器状态的同步。(协同服务是典型的读多于写)
ZooKeeper读写率变化时的吞吐量图使用的ZooKeeper是发行版3.2,运行在双2Ghz至强处理器、两个SATA 15k RPM驱动器上。一个驱动器被专门用于ZooKeeper日志服务。快照被写到了操作系统驱动器上。1k的读和1k的写。服务器数代表了集群的大小,即组成服务的服务器的数量。大约有30台其他服务器用于模拟客户端。ZooKeeper集群被配置成了主服务器不接受客户端连接。
基准测试也表名它是可靠的。当错误存在时服务的可靠性图显示了部署是如何对各种错误做出响应的。图中标记数字的事件如下:
- 单台从服务器的失败和恢复;
- 不同从服务器的失败和恢复;
- 主服务器的失败;
- 两台从服务器的失败和恢复;
- 其他主服务器的失败。
可靠性
为了显示当错误发生时系统的表现,我们运行了一个由7台服务器构成的ZooKeeper服务。我们运行了和之前一样的相同饱和度的基准测试,但这次我们使写操作一致保持在30%,是我们预期工作量的保守比例。
从图中可以得到几个重要的观察结果。首先如果从服务器失败后快速恢复,即使从服务器失效了,ZooKeeper服务依然可以维持高吞吐量。但重要的是,主服务器选举算法要允许系统快速恢复来避免吞吐量的突然下降。在我们的观察中,ZooKeeper用了不到200毫秒选举出了新的主服务器。第三,当从服务器回复完后,一旦开始处理请求,ZooKeeper能够快速提升到之前的吞吐量。
ZooKeeper项目
ZooKeeper已经成功应用到了许多工业级应用中。它已经用于了雅虎的协同服务和失效恢复服务。消息中间件是一个高扩展性的用于管理数以千计的数据复制和数据传输话题的的发布订阅系统。它还被用于雅虎的爬虫程序,并且管理着失效恢复。雅虎的很多广告系统也使用ZooKeeper来实现可靠的服务。
我们鼓励所有的用户和开发者加入社区、贡献自己的经验。更多信息请查看Apache的ZooKeeper项目。