Zookeeper-快速入门

简介

ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。
Zookeeper 公开了一组简单的原语,分布式应用程序可以构建这些原语,以实现更高级别的服务,以实现同步,配置维护以及组和命名。
Zookeeper 被设计为易于编程,并使用在熟悉的文件系统目录树结构之后设计的数据模型。
它在Java 中运行,并且具有Java 和C 的绑定。
众所周知,协调服务很难做到。他们特别容易出现比赛条件和死锁等错误。
ZooKeeper 背后的动机是减轻分布式应用程序从头开始实施协调服务的责任

设计目标

  1. 简单
    ZooKeeper 允许分布式进程通过共享的层级命名空间相互协调,该命名空间的组织方式与标准文件系统类似。
    名称空间由数据寄存器组成 - 在ZooKeeper 用语中称为znodes - 这些与文件和目录类似。
    与专为存储而设计的典型文件系统不同,ZooKeeper 数据保存在内存中,这意味着ZooKeeper 可以实现高吞吐量和低延迟数量。
    ZooKeeper 实现非常重视高性能,高可用性,严格有序的访问。
    ZooKeeper 的性能方面意味着它可以在大型分布式系统中使用。
    可靠性方面使其不会成为单点故障。严格的排序意味着可以在客户端实现复杂的同步原语。
  2. 复制
    与它协调的分布式进程一样,ZooKeeper 本身也可以在称为集合的一组主机上进行复制。
    组成ZooKeeper 服务的服务器必须彼此了解。它们维护内存中的状态图像,以及持久性存储中的事务日志和快照。只要大多数服务器可用,ZooKeeper 服务就可用。
    客户端连接到单个ZooKeeper 服务器。客户端维护TCP 连接,通过该连接发送请求,获取响应,获取监视事件以及发送心跳。如果与服务器的TCP连接中断,则客户端将连接到其他服务器。
    a
  3. 排序
    ZooKeeper 使用反映所有ZooKeeper 事务顺序的数字标记每个更新。后续操作可以使用该顺序来实现更高级别的抽象,例如同步原语。
  4. 快速
    在“读取主导”工作负载中特别快。ZooKeeper 应用程序在数千台计算机上运行,​​并且在读取比写入更常见的情况下表现最佳,比率大约为10:1。

数据模型和分层命名空间

ZooKeeper 提供的名称空间非常类似于标准文件系统。名称是由斜杠(/)分隔的路径元素序列。ZooKeeper 名称空间中的每个节点都由路径标识。
ZooKeeper 的分层命名空间:
a

节点和短暂节点

ZooKeeper 命名空间中的每个节点都可以包含与之关联的数据以及子项。这就像拥有一个允许文件也是目录的文件系统。
ZooKeeper 旨在存储协调数据:状态信息、配置、位置信息等,因此存储在每个节点的数据通常很小,在字节到千字节范围内。因此我们使用znode 来说明Zookeeper 数据节点。
Znodes 维护一个stat 结构,其中包括数据更改、ACL 更改和时间戳的版本号,以允许缓存验证和协调更新。
每次znode 的数据更改时,版本号都会增加。(例如,每当客户端检索数据时,它也接收数据的版本。)
存储在命名空间中每个znode 的数据以原子方式读取和写入。
读取获取与znode关联的所有数据字节,写入替换所有数据。
每个节点都有一个访问控制列表(ACL),限制谁可以做什么。
ZooKeeper 也有短暂节点的概念。只要创建znode 的会话处于活动状态,就会存在这些znode 。会话结束时,znode 将被删除。

状态更新和查看

ZooKeeper 支持WATCH 的概念。客户端可以在znode 上设置监视。当znode 更改时,将触发并删除WATCH 。
触发监视时,客户端会收到一个数据包,指出znode 已更改。
如果客户端与其中一个ZooKeeper 服务器之间的连接中断,则客户端将收到本地通知。

担保

ZooKeeper 非常快速而且非常简单。由于其目标是构建更复杂的服务(如同步)的基础,因此它提供了一系列保证。
这些是:

  • 顺序一致性: 客户端的更新将按发送顺序应用。
  • 原子性: 更新成功或失败。没有部分结果。
  • 单系统映像: 无论服务器连接到哪个服务器,客户端都将看到相同的服务视图。
  • 可靠性: 一旦应用了更新,它将从那时起持续到客户端覆盖更新。
  • 及时性: 系统的客户视图保证在特定时间范围内是最新的。

简单API

ZooKeeper的设计目标之一是提供一个非常简单的编程接口。
它仅支持以下操作:

  • create: 在树中的某个位置创建一个节点
  • delete: 删除节点
  • exists: 测试某个位置是否存在节点
  • get data: 从节点读取数据
  • set data: 将数据写入节点
  • get children: 检索节点的子节点列表
  • sync: 等待传播数据

履行

ZooKeeper Components 是显示ZooKeeper 服务的高级组件。除请求处理器外,构成ZooKeeper服务的每个服务器都复制其自己的每个组件的副本
a
复制数据库是包含整个数据树的内存数据库。更新将记录到磁盘以获得可恢复性,并且写入在应用于内存数据库之前会序列化到磁盘。
每个ZooKeeper 服务器都为客户端服务。客户端只连接到一台服务器以提交请求。读取请求由每个服务器数据库的本地副本提供服务。更改服务状态,写请求的请求由协议协议处理。
作为协议协议的一部分,来自客户端的所有写入请求都被转发到称为领导者的单个服务器。其余的ZooKeeper 服务器(称为关注者)接收来自领导者的消息提议并同意消息传递。
消息传递层负责替换失败的领导者并将关注者与领导者同步。
ZooKeeper 使用自定义原子消息传递协议。由于消息传递层是原子的,因此ZooKeeper 可以保证本地副本永远不会发散。当领导者收到写入请求时,它会计算应用写入时系统的状态,并将其转换为捕获此新状态的事务。

使用

ZooKeeper 的编程接口非常简单。使用它,您可以实现更高阶的操作,例如同步原语,组成员身份,所有权等。

性能

ZooKeeper旨在提供高性能。研究表明它是,在读取数量超过写入的应用程序中,它特别高性能,因为写入涉及同步所有服务器的状态。
a

可靠性

为了在注入故障时显示系统随时间的行为,我们运行了由7台机器组成的ZooKeeper 服务。我们运行与以前相同的饱和度基准,但这次我们将写入百分比保持在恒定的30% ,这是我们预期工作量的保守比率。
a
这是图表中的一些重要观察结果。
首先,如果某一台服务器失败,ZooKeeper 也能够维持高吞吐量。但也许更重要的是,领导者选举算法允许系统足够快地恢复以防止吞吐量大幅下降。
在我们的观察中,ZooKeeper 选择新领导者的时间不到200毫秒。
第三,随着追随者的恢复,ZooKeeper 能够在开始处理请求后再次提高吞吐量。

入门

主要针对希望尝试它的开发人员,包含一个ZooKeeper 服务器的简单安装说明\一些验证它正在运行的命令以及一个简单的编程示例。

下载

要获取ZooKeeper发行版,请从其中一个Apache下载镜像下载最新的稳定版本。

操作

在独立模式下设置ZooKeeper 服务器非常简单。服务器包含在单个JAR 文件中,因此安装包括创建配置。
下载完一个稳定的ZooKeeper 版本后,将其解压缩并cd 到root 。
要启动ZooKeeper ,您需要一个配置文件。这是一个示例,在conf/zoo.cfg 中创建它:

1
2
3
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181

以下是每个字段的含义:

  • tickTime: ZooKeeper 使用的基本时间单位(以毫秒为单位)。它用于做心跳,最小会话超时将是tickTime 的两倍。
  • dataDir: 存储内存数据库快照的位置,除非另有说明,否则为数据库更新的事务日志。
  • clientPort: 侦听客户端连接的端口。

现在您已创建配置文件,您可以启动ZooKeeper :

1
bin/zkServer.sh start

ZooKeeper 使用log4j 记录消息,您将看到日志消息进入控制台(默认)和/或日志文件,具体取决于log4j 配置。

管理Zookeeper 存储

对于长期运行的生产系统,必须在外部管理ZooKeeper 存储(dataDir 和logs )。

连接Zookeeper

1
bin/zkCli.sh -server 127.0.0.1:2181

这使您可以执行简单的文件操作。连接后,您应该看到类似的内容:

1
2
3
4
5
6
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 以获取可从客户端执行的命令列表,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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
create path data acl
stat path [watch]
listquota path
history
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
deleteall path
setquota -n|-b val path

从这里,您可以尝试一些简单的命令来感受这个简单的命令行界面。首先,从发出list 命令开始,如ls :

1
2
[zkshell: 8] ls /
[zookeeper]

接下来,通过运行创建一个新的znode create /zk_test my_data 。这将创建一个新的znode 并将字符串my_data 与节点相关联。你应该看到:

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

输入另一个ls /命令以查看Zookeeper 集群:

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

现在已创建zk_test 目录。
接下来,通过运行get 命令验证数据是否与znode 相关联,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
[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 关联的数据,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[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 在设置数据之后做了一次,确实改变了。
让我们delete 通过发出以下节点:

1
2
3
4
[zkshell: 16] delete /zk_test
[zkshell: 17] ls /
[zookeeper]
[zkshell: 18]

编程Zookeeper

ZooKeeper 具有Java 绑定和C 绑定。它们在功能上是等价的。
C 绑定有两种变体:单线程和多线程。这些仅在消息传递循环的完成方式上有所不同。

运行Zookeeper

在独立模式下运行ZooKeeper 便于评估,开发和测试。
但在生产中,您应该以复制模式运行ZooKeeper 。同一应用程序中的复制服务器组称为仲裁,在复制模式下,仲裁中的所有服务器都具有相同配置文件的副本。
注: 对于复制模式,至少需要三台服务器,强烈建议您使用奇数个服务器。
复制模式所需的conf/zoo.cfg 文件类似于独立模式中使用的文件,但有一些差异。这是一个例子:

1
2
3
4
5
6
7
8
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
  • 新配置项initLimit 是暂停ZooKeeper 用于限制仲裁中ZooKeeper 服务器连接到领导者的时间长度。
  • 配置项syncLimit 限制服务器与领导者的过期时间。
  • 表单server.X 的条目列出构成ZooKeeper 服务的服务器。当服务器启动时,它通过在数据目录中查找文件myid 来知道它是哪个服务器。该文件包含服务器编号,ASCII 格式。
  • 对等方使用以前的端口连接到其他对等方。这种连接是必要的,以便对等方可以进行通信。更具体地说,ZooKeeper 服务器使用此端口将关注者连接到领导者。当新的领导者出现时,跟随者使用此端口打开与领导者的TCP 连接。由于默认的领导者选举也使用TCP ,我们目前需要另一个端口进行领导者选举。这是服务器条目中的第二个端口。

优化

  • 要在更新时获得较低的延迟,请务必拥有专用的事务日志目录。默认情况下,事务日志与数据快照和myid文件放在同一目录中。dataLogDir参数指示用于事务日志的不同目录。

Java 示例

为了介绍ZooKeeper Java API ,我们在这里开发了一个非常简单的客户端。
此ZooKeeper 客户端监视ZooKeeper 节点的更改并通过启动或停止程序来响应。

要求

  • 它需要作为参数。
  • ZooKeeper 服务的地址。
  • znode 的名称。
  • 要将输出写入的文件的名称。
  • 带参数的可执行文件。
  • 它获取与znode关联的数据并启动可执行文件。
  • 如果znode更改,则客户端将重新获取内容并重新启动可执行文件。
  • 如果znode消失,客户端将终止可执行文件。

设计

传统上,ZooKeeper 应用程序分为两个单元,一个维护连接,另一个监视数据。
在此应用程序中,名为Executor 的类维护ZooKeeper 连接,而名为DataMonitor 的类监视ZooKeeper 树中的数据。
此外,Executor 包含主线程并包含执行逻辑。它负责与用户进行的少量交互,以及与作为参数传递的exectuable 程序的交互,以及根据znode 的状态,根据要求关闭或重新启动。

执行类

  • Executor 对象是示例应用程序的主要容器,它包含ZooKeeper 的对象DataMonitor 。
  • Executor 的工作是启动和停止在命令行上传入其名称的可执行文件。它这是为了响应ZooKeeper 对象触发的事件。Executor 将对自身的引用作为ZooKeeper 构造函数中的Watcher 参数传递,它还将对自身的引用传递给DataMonitor 构造函数的DataMonitorListener 参数。
  • 该Watcher 接口由Zookeeper 的Java API 定义。ZooKeeper 使用它来回传给它的容器。它只支持一种方法:process() ,并且ZooKeeper 使用它来传递主线程所在的通用事件。(例如ZooKeeper 连接或ZooKeeper会话的状态)
  • 本例中的Executor 只是将这些事件转发到DataMonitor 决定如何处理它们,它这样做只是为了说明按照惯例,Executor 或类似Executor 的对象 拥有 ZooKeeper 连接,但可以将事件委托给其他事件到其他对象,它还使用此作为触发监视事件的默认通道。
  • DataMonitor 对象使用它与其容器进行通信,该容器也是Executor 对象,DataMonitor是调用这些方法的对象,以响应ZooKeeper状态的变化。

数据逻辑类

  • DataMonitor类具有ZooKeeper逻辑的功能。它主要是异步和事件驱动的。
  • 调用ZooKeeper.exists() 检查是否存在znode ,设置监视,并将对self(this) 的引用作为完成回调对象传递。
  • 当ZooKeeper.exists() 操作在服务器上完成时,ZooKeeper API 在客户端上调用此完成回调。

源代码

详细源代码请参考我的Github地址。


使用Zookeeper 编程 - 基本教程

我们使用ZooKeeper显示Barriers 和生产者 - 消费者队列的简单实现。我们将相应的类称为Barrier 和Queue 。

介绍

Barriers

生产者-消费者队列