ZooKeeper:为分布式应用提供的分布式协调服务
ZooKeeper提供一系列原语用于分布式应用构建更高层次的服务,如同步、配置维护、分组以及命名空间。
设计目标:
ZooKeeper足够简单且可复制。
组成ZooKeeper服务当中的服务器知道彼此之间的存在。服务器在持久存储中维护状态内存映像以及事务日志和快照。只要大多数的服务是可用的,ZooKeeper服务也是可用的。
客户端连接一台ZooKeeper服务器,维护一个TCP连接,通过它连接请求、获取响应及通知事件,并发送心跳包。如果服务器端的TCP连接断开,客户端将连接到另一个服务器。
ZooKeeper是有序的。ZooKeeper时间戳每次更新都会带一个编号用于反映ZooKeeper事务的顺序。后续操作可依据这个顺序用于实现更高层次的抽象,如同步原语。
ZooKeeper是快速的。在用于读为主的应用场景特别快速。ZooKeeper应用运行于上千台机器,在读远多于写的场景上表现最佳,通常读写比例为10:1。
数据模型及分层命名空间
依托于ZooKeeper的命名空间有点类似标准的文件系统。名称是由“/”分隔组成的字符串。在ZooKeeper的命名空间中每个节点是用路径标识出来的。
节点以及临时节点
不像标准的文件系统,在ZooKeeper命令空间中每个节点都可以有数据关联到(类似子节点)。这就好像一个文件系统,允许文件也是目录。(ZooKeeper被设计用于存储分布式数据:状态信息、配置信息、位置信息等等,通常存储在每个节点的数据是比较小的,控制在KB字节范围。)在谈论ZooKeeper数据节点的时候我们通常用术语znode来说明。
znode维护一个状态数据结构,包含数据变更版本号、ACL(Access Control List)变更以及时间戳,允许缓存验证和协调更新。每当znode的数据变化时,版本号会往上增加。每当客户机检索数据时,它也接收数据版本。
在同一个命名空间中,每个znode节点当中的数据读取和写入是原子操作。读取操作能获取到该znode节点的所有数据字节,同时写入的时候会替换掉该znode节点的所有数据。每个节点都有一个ACL用于限制谁可以做什么。
ZooKeeper还有临时节点的概念。只要创建节点的会话还处于活跃状态,那么节点还是存在的,当会话关闭时则znode节点删除。
条件更新和订阅
ZooKeeper支持订阅。客户端可以观察节点。当节点变化时,将会触发或移除观察。当客户端接收到报文描述这个节点变化时,监听将会被触发。如果客户端和ZooKeeper的某个服务断开连接时,客户端将会接收到一个本地通知。
保证
ZooKeeper是很快速和简单的。虽然它的目标是对很多复杂服务如同步机制是很基础的,但它也提供了一系列的保证,如下:
1)顺序一致性-更新将会按照客户端发送的顺序来执行
2)原子性-更新要吗全部成功或失败,没有部分结果。
3)单一系统映像-不管客户端连接的是哪个服务,它最终看到的是同样的服务视图
4)可靠性-一旦更新被执行,它会一直持久存在直到被写入
5)时间性-系统客户端的视图在一定时间内会保证最新
简单API
Zookeeper设计的一个目标是提供简单的编程接口,所以它只支持以下操作:创建create、删除delete、存在exists、获取数据get data、设置数据set data、获取子节点get children以及同步sync。
实现
下述显示ZooKeeper服务的高层次组件。除了请求处理异常外,每个组成ZooKeeper服务的服务层都有它所属每个组件的拷贝。
从数据库在整个数据树上是一个内存数据库。更新将会持久化到磁盘用于恢复,在他们应用于内存数据库时,他们会先持久化到磁盘中。
每个ZooKeeper服务器服务于客户端。客户端会确切地连接到一个服务器用于提交请求。读取请求从每个服务器的本地副本获取服务。如果是更新服务状态的请求或者是写请求的话,则会被一个一致性协议所处理。
作为一致性协议的一部分所有的从客户端发起的写请求将会转发到单个服务器,我们叫它leader。剩下的ZooKeeper服务器,我们叫follower,用于接收从leader发起的消息提议同时商定消息传递。消息传递层关注当leader失败时的替换以及followers和leaders之间的同步。
ZooKeeper使用了一个定制的原子消息传递层。因为消息传递层是原子的,所有ZooKeeper可以确保本地副本不会偏离。当leader接收到一个写请求时,它会计算当写操作被应用的话系统状态是什么,同时将此转换成捕获新状态的事务。
使用
ZooKeeper的编程接口是很简单的。通过它,你可以实现高级别的操作,如同步原语、集群管理等等。
性能
ZooKeeper被设计于高性能的。当读操作远远多于写操作的时候,它表现得相当高性能。因为写涉及到服务器之间的同步。(读操作远远多于写操作是分布式服务的一个典型例子。)
上图(ZooKeeper吞吐量按读写比变化)是在ZooKeeper3.2发布版的吞吐量展示图,基于双核2GHZ Xeon和2个SATA 15K RPM驱动器。一个驱动器用做专门的ZooKeeper log记录。快照将会写入到OS。写请求和读请求都是1K。"Servers"显示的是组成ZooKeeper服务的服务器机器数量。将近30台机器被用于模拟客户端。ZooKeeper被配置成leader不允许从客户端发起连接。
压力测试同样显示它的稳定性。可靠性当中的错误的存在表明一个部署是如何应对不同的错误情况。在下图当中显示的事件可以归类为一下几种:
1)单个follower的故障和恢复
2)不同follower的故障和恢复
3)leader的故障
4)2个followers的故障和恢复
5)另一个leader的故障
可靠性
当我们运行由7台机器组成的ZooKeeper服务,我们展示系统随着时间的推移注入错误的行为。我们运行跟以前一样的饱和压力测试,但是在这时间我们保持写操作占比在30%左右,这是我们期望的工作负载的保守比例。
在这张图里面有一些很重要的指标。首先,如果followers故障同时恢复快速的,ZooKeeper能维持在一个高吞吐量状态。但是更重要的是,这个leader选举算法对系统来说允许足够快速恢复,防止吞吐量大幅下降。在我们的观察中,ZooKeeper只花费了低于200ms用于选举出一个新leader。最后当followers恢复的时候,一旦他们启动处理请求的时候ZooKeeper能快速提升吞吐量。
ZooKeeper项目
ZooKeeper已经成功应用于多个工业应用。它被用于在Yahoo! Message Broker(这是一个高扩展性的发布订阅系统用于关于上千个主题的发布和数据传递)的故障快速恢复以及协调服务上。它被用于Yahoo! crawler当中的抓取服务,做为一个故障恢复的管理服务。Yahoo!的一系列广告系统也使用ZooKeeper作为可靠性服务。