Zookeeper简介

  |   0 评论   |   1,802 浏览

概述

Zookeeper是一个分布式的、开源的分布式应用协调服务。它暴露了一组简单的基础原件,分布式应用可以在这些原件之上实现更高级别的服务,如同步、配置维护、分组、和命名。它被设计成容易编程实现的,并且使用一个常见的文件系统的树型结构的数据模型。它运行在Java中,并且绑定了Java和C。

众所周知,协调服务很难做对。它们特别容易发生像文件竞争条件问题和死锁的错误。Zookeeper的动机就是缓解分布式应用从头开始实现分布式协调服务的责任。

设计目标

  • 简单。

Zookeeper允许程序通过一个共享的类似于标准文件系统的有组织的分层命名空间分布式处理协调。命名空间包括:数据寄存器 - 在Zookeeper中叫znodes, 它和文件、目录一样。和一般文件系统的不同之处是,它的设计就是为了存储,Zookeeper的数据保持在内存中,这就意味着它可以实现高吞吐量和低延迟的数据。

Zookeeper的实现提供了一个优质的高性能、高可用,严格的访问顺序。Zookeeper的性能方面意味着它可以用于大型的分布式系统。可靠性方面防止它成为一个单点故障。严格的顺序意味着可以在客户端实现复杂的同步原件。

  • 可复制的。

像分布式处理一样,Zookeeper自己在处理协调的时候要复制多个主机。
image

Zookeeper服务的组成部分必须彼此都知道彼此,它们维持了一个内存状态影像,连同事务日志和快照在一个持久化的存储中。只要大多数的服务器是可用的,Zookeeper服务就是可用的。

客户端连接到一个单独的服务。客户端保持了一个TCP连接,通过这个TCP连接发送请求、获取响应、获取watch事件、和发送心跳。如果这个连接断了,会自动连接到其他不同的服务器。
- 有序
-
Zookeeper用数字标记每一个更新,用它来反射出所有的事务顺序。随后的操作可以使用这个顺序去实现更高级的抽象,例如同步原件。

  • 快速。

它在“Read-dominant”工作负载中特别快。Zookeeper 应用运行在数以千计的机器上,并且它通常在读比写多的时候运行的最好,读写大概比例在10:1。

数据模型和分层的命名空间

Zookeeper提供的命名空间非常像一个标准的文件系统。一个名字是一系列的以’/’隔开的一个路径元素。Zookeeper命名空间中所有的节点都是通过路径识别。

image

节点和临时节点

不像标准的文件系统,Zookeeper命名空间中的每个节点可以有数据也可以有子目录。它就像一个既可以是文件也可以是目录的文件系统。(Zookeeper被设计成保存协调数据:状态信息,配置,位置信息,等等,所以每个节点存储的数据通常较小,通常在1个字节到数千字节的范围之内)我们使用术语znode来表明Zookeeper的数据节点。

znode维持了一个stat结构,它包括数据变化的版本号、访问控制列表变化、和时间戳,允许缓存验证和协调更新。每当znode的数据有变化,版本号就会增加。例如,每当客户端检索数据时同时它也获取数据的版本信息。

命名空间中每个znode存储的数据自动的读取和写入的,读取时获得znode所有关联的数据字节,写入时替换所有的数据。每个节点都有一个访问控制列表来制约谁可以做什么。

Zookeeper还有一个临时节点的概念。这些znode和session存活的一样长,session创建时存活,当session结束,也跟着删除。

条件的更新和watches

Zookeeper支持watches的概念。客户端可以在znode上设置一个watch。当znode发生变化时触发并移除watch。当watch被触发时,客户端会接收到一个包说明znode有变化了。并且如果客户端和其中一台server中间的连接坏掉了,客户端就会收到一个本地通知。这些可以用来[tbd]。

保证

Zookeeper是非常简单和高效的。因为它的目标就是,作为建设复杂服务的基础,比如同步。zookeeper提供了一套保证,他们包括:

  • 顺序一致性 - 来自客户端的更新会按顺序应用。
  • 原子性 - 更新成功或者失败,没有局部的结果产生。
  • 唯一系统映像 - 一个客户端不管连接到哪个服务端都会看到同样的视图。
  • 可靠性- 一旦一个更新被应用,它将从更新的时间开始一直保持到一个客户端重写更新。
  • 时效性 - 系统中的客户端视图在特定的时间点保证成为是最新的。

简单API

Zookeeper的设计目标的其中之一就是提供一个简单的程序接口。因此,它只支持这些操作:

create - 在树形结构的位置中创建节点
delete - 删除一个节点
exists - 测试节点在指定位置上是否存在
get data - 从节点上读取数据
set data - 往节点写入输入
get chilren - 检索一个节点的子节点列表
sync - 等待传输数据
关于它的更深入的讨论,和怎么使用它实现更高级别的操作,请参考[tbd]
实现

Zookeeper的组件

展示了Zookeeper服务的高级组件。除了请求处理器的异常之外,组成Zookeeper服务的服务器都会复制它们自己组件的副本。
image

Replicated database 是一个内存数据库,它包含全部的数据树。为了可恢复性,更新记录保存到磁盘上,并且写入操作在应用到内存数据库之前被序列化到磁盘上。
每个Zookeeper 服务端服务客户端。客户端正确的连接到一个服务器提交请求。每个服务端数据库的本地副本为读请求提供服务。服务的变化状态请求、写请求,被一个协议保护。
作为协议的一部分,所有的写操作从客户端转递到一台单独服务器,称为leader。其他的Zookeeper服务器叫做follows,它接收来自leader的消息建议并达成一致的消息建议。消息管理层负责在失败的时候更换Leader并同步Follows。
Zookeeper使用了一个自定义的原子消息协议。因为消息层是原子的,Zookeeper可以保证本地副本从不出现偏差。当leader接受到一个写请求,它计算写操作被应用时系统的状态,并将捕获到的新状态转化进入事务。

使用

Zookeeper的编程接口非常简单。然而,用它你可以实现更高级的操作,如同步基本数据,组成员,所有权,等等。一些分布式应用已经使用了它

性能

Zookeeper被设计成高性能的。但真的是这样吗?Yahoo的Zookeeper开发团队的结果表明了它就是这样的。它在应用中读比写多的时候有特别高的性能,因为在写的时候包含所有服务器状态同步。(读比写多是协调服务的典型案例)
Zookeeper的吞吐量随读写比例的变化
image

上图是Zookeeper3.2版本在dual 2Ghz Xeon and two SATA 15K RPM 驱动配置的服务器上的吞吐量图像。一个驱动作为专门的Zookeeper日志装置。快照写进操作系统驱动。1k的写请求和1K的读取请求。“Servers” 表明了Zookeeper全体的大小,组成Zookeeper服务的服务器数量。接近于30台机器模仿客户端。Zookeeper全体被配置为leaders不允许客户端连接。

注意:相对于3.1的发布版本,在3.2的版本中读写性能都改善了。

测试基准也表明它是可靠的。Reliability in the Presence of Errors 展示了怎样部署应对各种失败。事件标记出以下几点:

  1. 一个follower的故障和恢复
  2. 不同的follower的故障和恢复
  3. leader的故障
  4. 两个followers的故障和恢复
  5. 另一个leader的故障

可靠性

展示我们启动了7台机器组成的Zookeeper服务注入超时故障的行为。我们之前运行了相同的饱和基准,但现在我们保持写的百分比在不变的30%,这是一个比较保守的工作负载比。

image

从这张图有几个重要的观察。第一,如果follows失败并快速的恢复,Zookeeper能够维持一个高的吞吐量尽管有故障。但也许更重要的是,leader选举算法允许系统快速恢复,足以预防吞吐量大幅下降。在我们的观测中,Zookeeper用了不到200ms的时间就选出了一个新的leader。第三,随着follower恢复,Zookeeper一旦开始处理它们的请求,它能够再次提高吞吐量。

示例

连接Zookeeper

如果Zookeeper在运行中,你可以有几个操作连接到它

JAVA: 使用

bin/zkCli.sh -server 127.0.0.1:2181
这让你执行简单,类似文件的操作。

C:在Zookeeper源文件中的src/c子目录中通过运行make cli_mt或make cli_st编译cli_mt(多线程的)或cli_st(单线程的)。查看src/c中的README获取全部的详细信息。

你可以在src/c目录中运行程序:

LD_LIBRARY_PATH=. cli_mt 127.0.0.1:2181 或
LD_LIBRARY_PATH=. cli_st 127.0.0.1:2181
这将给你一个简单的shell脚本,在Zookeeper上执行文件系统的操作。

一旦连接上了,你可以看到一些信息:

Connecting to localhost:2181
log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper).
log4j:WARN Please initialize the log4j system properly.
Welcome to ZooKeeper!
JLine support is enabled
[zkshell: 0]

从shell脚本,输入help,可以获取一个从客户端可执行的命令列表:

[zkshell: 0] help
ZooKeeper host:port cmd args
        get path [watch]
        ls path [watch]
        set path data [version]
        delquota [-n|-b] path
        quit
        printwatches on|off
        createpath data acl
        stat path [watch]
        listquota path
        history
        setAcl path acl
        getAcl path
        sync path
        redo cmdno
        addauth scheme auth
        delete path [version]
        setquota -n|-b val path

从这里,你可以尝试一些简单的命令行接口找到一些感觉。第一,通过发行的列表命令开始,像ls :

[zkshell: 8] ls /
[zookeeper]

下一步,通过运行create /zk_test my_data,创建一个新的znode。这将创建一个新的znode节点和一个相关联的字符串“my_data”:

[zkshell: 9] create /zk_test my_data
Created /zk_test

使用ls / 命令查看目录:

[zkshell: 11] ls /
[zookeeper, zk_test]

注意 zk_test目录已经被创建了。

接下来,使用 get 命令验证数据是否与znode关联上了:

[zkshell: 12] get /zk_test
my_data
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 5
mtime = Fri Jun 05 13:57:06 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0
dataLength = 7
numChildren = 0

我们可以是用set命令改变数据与zk_test的关联。像:

[zkshell: 14] set /zk_test junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0
[zkshell: 15] get /zk_test
junk
cZxid = 5
ctime = Fri Jun 05 13:57:06 PDT 2009
mZxid = 6
mtime = Fri Jun 05 14:01:52 PDT 2009
pZxid = 5
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0
dataLength = 4
numChildren = 0

(注意我们使用get在setting data之后,并且它确实改变了。)

最后,让我们delete节点:

[zkshell: 16] delete /zk_test
[zkshell: 17] ls /
[zookeeper]
[zkshell: 18]

读后有收获可以支付宝请作者喝咖啡