ZooKeeper
- ZooKeeper: 分布式应用中的协调服务
- 设计目标
- 数据模型和层次空间
- 节点和临时节点
- 竞争下的更新和监视
- 保证
- 简单API
- 实现
- 使用
- 性能
- 可靠性
- ZooKeeper项目
ZooKeeper: 分布式应用中的协调服务
ZooKeeper是一个开源的,分布式的协调服务,用于构建分布式应用。ZooKeeper暴露了一组简单的操作原语,基于这些原语,分布式应用可以构建高层次的同步、配置管理、组管理、命名等服务。它被设计为易于编程的,同时使用了和文件目录树结构类似的数据模型。ZooKeeper使用java开发运行,同时提供Java和C的客户端。
协调服务往往很难做的完全正确。它们尤其容易出现一些竞争、死锁之类的错误。ZooKeeper的动机是想让分布式应用从实现协调服务的困境中解脱出来。
设计目标
简单。ZooKeeper允许多个进程通过一个共享的层次空间来进行相互协调,这个层次空间和标准文件系统类似。它包括了一些数据注册点(在ZooKeeper中,也叫做znode),znode和文件以及目录很相似。和文件系统用于存储数据不同的是,ZooKeeper的数据都在内存中,也就是说Zooker可以实现高吞吐和低延迟。
ZooKeeper的实现很重视高性能,高可用,严格有序。高性能意味着ZooKeepre可以用于大型分布式系统中。高可用则保证了ZooKeeper不会是一个单点。严格的有序可以让客户端实现复杂的同步原语。
多副本。ZooKeeper是多副本的。和它协调的分布式进程类似,ZooKeeper本身也是多副本的。
组成ZooKeeper服务的各个server必须知道彼此的存在。它们在内存中维护状态,同时将变更日志和快照记录到持久化存储中。只要大多数server是可用的,ZooKeeper服务就是可用的。
客户端连接到单个ZooKeeper server。客户端维持一个TCP连接,通过这个连接,客户端可以发送请求、接收响应、获取监视事件、发送心跳等。如果TCP连接断开了,客户端则会连接其他的server。
有序。ZooKeeper对每一次更新都打上一个数字,根据这个数字可以看出所有的ZooKeeper变更顺序。后续的操作可以使用这个顺序来实现一些高层次的抽象,比如同步原语。
快
在读取请求占大多数的情况下,ZooKeeper响应特别快。ZooKeeper应用运行在上千台服务器上,当读写请求大概10:1的时候,ZooKeeper性能是最好的。
数据模型和层次空间
ZooKeeper提供的命名空间和标准文件系统非常相似。一个名字是一系列路径元素的组合,中间用斜杠(/)隔开。ZooKeeper命名空间中的每个znode都通过一个唯一的路径来识别。
The name space provided by ZooKeeper is much like that of a standard file system. A name is a sequence of path elements separated by a slash (/). Every node in ZooKeeper's name space is identified by a path.
节点和临时节点
和标准文件系统不同的是,ZooKeeper中的每个节点以及它的子节点都可以拥有数据。就好像一个文件系统允许文件同时也是目录。(ZooKeeper被设计为存储协调用的数据,包括:状态信息,配置,位置信息等,因此在一般情况下每个节点存储的数据都比较小,大概在k级别)。为方便期间,后面我们用znode来指代ZooKeeper数据节点。
Znode维护了一个统计结构,其中包括了数据变更的版本号,ACL变更,时间戳,通过这个结构来进行cache的验证和更新。任何时候当znode的数据发生变更,版本号会增加。当客户的从znode获取到数据时,也会同时获取到数据的版本。
存储在单个znode中的数据的读写操作都是原子的。读操作会获取到一个znode中的全部数据,写操作则会替代全部的数据。每个znode都有个一个访问控制列表(ACL)用以限制谁可以做什么。
ZooKeeper也有临时节点的概念。创建临时节点的会话保持在线时,这些临时节点就会一直存在。当会话结束时,临时节点会被删除。当你想实现[tbd]时,临时节点会非常有用。
竞争下的更新和监视
ZooKeeper支持监视的概念。客户端可以对某个znode设置一个监视。当这个znode发生变更时,监视会被触发或者删除。当监视事件触发时,客户端会收到一个数据包,说明这个znode改变了。当客户端与ZooKeeper服务端的连接断开时,客户端会收到一个本地的通知。
保证
ZooKeeper非常的简单和快速。它的目标是成为更加复杂的服务的基础,基于此,ZooKeeper提供了一些担保:
- 顺序一致性 从同一个客户端发送的更新请求,会按照请求到达的顺序来更新数据
- 原子性 更新要么失败要么成功。没有部分完成的情况。
- 单系统镜像 客户端无论和哪个server进行连接,看到的数据都是一样的。
- 可靠性 一旦更新被应用到数据上,此次更新就会持续到另一个客户端覆盖它为止。
- 时效性 确保客户端在一定的时间内可以看到最新的数据
关于以上特点的更多说明,以及如何使用它们,参考[]
简单API
ZooKeeper的一个设计目标是提供非常简单的编程接口。基于此,ZooKeeper只支持如下操作:
- create 在指定的位置创建一个node
- delete 删除一个node
- exists 查看指定位置是否存node
- get data 获取node的数据
- set data 向node写入数据
- get children 获取node的子节点列表
- sync 等待数据传播同步
关于进一步的讨论,已经如何使用它们实现高层次的操作,请参考[]
实现
ZooKeeper Components展示了ZooKeeper的高层组件。除了request precessor之外,每个组件都有副本。
replicated Database是一个内存数据库,其中包括了整个数据树。更新操作会写到磁盘,用来恢复数据。写操作在应用到内存数据库之前会先写到磁盘。
每个ZooKeeper都会服务客户,客户端连接到某个server并将请求发给这个server。对于读请求,server直接从本地数据库读取数据并返回给客户。对于可能会改变服务状态的请求,写请求,使用一个一致性协议来处理。
作为一致性协议的一部分,所有的写请求会汇聚到同一个server来处理,这个server叫做leader。 其他的server叫做followers,followers从leader接受消息协议并达成一致。消息层会处理leader失败的情况,以及follower和leader的同步。
ZooKeeper自定义了一个原子消息协议。消息层是原子的,这保证了所有的本地副本不会出现分歧。当leader收到一个写请求时,它会计算应用写请求时系统应该处于何种状态,并把这个状态转化为一个事务。
使用
ZooKeeper的编程接口被有意的做成特别简单。但你仍然可以用它来实现很多高层的操作,比如同步原语等。
性能
ZooKeeper被设计为高性能的。但它确实如此吗?Yahoo研发中心的ZooKeeper开发小组认为确实是的。(查看 ZooKeeper Throughput as the Read-Write Ratio Varies)。ZooKeeper性能表现特别好,尤其是在读请求远多于写请求的情况下,因为写请求涉及到所有server的同步。(读请求超过些请求是协调服务的一个典型表现)
上图是ZooKeeper3.2版本的流量吞吐表,服务器的配置为dual 2Ghz Xeon cpu,两块15k RPM的硬盘。一块硬盘作为ZooKeeper的日志设备。快照被写入到系统盘。
基准测试也显示了ZooKeeper的可靠性。 Reliability in the Presence of Errors显示了在各种失败的情况下,ZooKeeper是如何反应的。图中显示的事件包括:
- 一个follower的失败和恢复
- 不同follower的失败和恢复
- leader的失败和恢复
- 两个follower的失败和恢复
- 另外一个leader的失败和恢复
可靠性
为了说明系统随着时间而产生的问题,我们运行了一个由7台机器组成的Zookeeper系统。我们运行了和上面一样的基准测试,不过我们把写请求的比例调到了30%,这个是比较保守的比例。
在上图中,有一些重要的信息。首先,如果follower失败后迅速恢复,ZooKeeper能够在失败的时候仍然保持高吞吐。更重要的是,leader选举算法可以使系统快速恢复,以阻止吞吐量的下降。基于我们的观察,ZooKeeper可以在200ms内选举出新的leader。第三,一旦follower恢复了,Zookeeper就可以迅速的恢复高吞吐能力。
ZooKeeper工程
ZooKeeper已经成功的应用在很多工业程序中。Yahoo的消息中间件使用了Zookeeper作为协调以及故障恢复的服务,消息中间件是一个高度可扩展的订阅-发布系统,管理着数以千计的主题,用于数据发送接收。ZooKeeper也被应用在Yahoo的爬虫服务中,负责故障恢复。一些Yahoo广告系统也使用了ZooKeeper以实现可靠服务。