ZooKeeper:面向分布式应用的分布式协调服务
ZooKeeper 是一种用于分布式应用程序的分布式开源协调服务。它公开了一组简单的基元,分布式应用程序可以基于这些基元进行构建,以实现更高级别的同步、配置维护以及组和命名服务。它被设计为易于编程,并使用以熟悉的文件系统目录树结构为样式的数据模型。它在 Java 中运行,并且具有 Java 和 C 的绑定。
众所周知,协调服务很难做到正确。它们特别容易出现错误,例如争用条件和死锁。ZooKeeper 背后的动机是减轻分布式应用程序从头开始实现协调服务的责任。
设计目标
ZooKeeper 很简单。ZooKeeper 允许分布式进程通过共享的分层命名空间相互协调,该命名空间的组织方式类似于标准文件系统。命名空间由数据寄存器组成 - 在 ZooKeeper 的说法中称为 znodes - 这些寄存器类似于文件和目录。与专为存储而设计的典型文件系统不同,ZooKeeper 数据保存在内存中,这意味着 ZooKeeper 可以实现高吞吐量和低延迟数字。
ZooKeeper 实现非常重视高性能、高可用性、严格有序的访问。ZooKeeper 的性能方面意味着它可以在大型分布式系统中使用。可靠性方面使其不会成为单点故障。严格的排序意味着可以在客户端实现复杂的同步原语。
复制 ZooKeeper。就像它协调的分布式进程一样,ZooKeeper 本身旨在通过一组称为集成的主机进行复制。
组成 ZooKeeper 服务的服务器必须彼此了解。它们在持久性存储中维护状态的内存中图像,以及事务日志和快照。只要大多数服务器都可用,ZooKeeper 服务就会可用。
客户端连接到单个 ZooKeeper 服务器。客户端维护一个 TCP 连接,通过该连接发送请求、获取响应、获取监视事件并发送心跳。如果与服务器的 TCP 连接中断,客户端将连接到其他服务器。
已订购 ZooKeeper。ZooKeeper 为每个更新添加一个数字,该数字反映了所有 ZooKeeper 事务的顺序。后续操作可以使用该顺序来实现更高级别的抽象,例如同步基元。
ZooKeeper 速度很快。在“读取主导”工作负载中,它的速度尤其快。ZooKeeper 应用程序在数千台机器上运行,在读取比写入更常见的情况下,它的表现最佳,比率约为 10:1。
数据模型和分层命名空间
ZooKeeper 提供的命名空间很像标准文件系统的命名空间。名称是一系列由斜杠 (/) 分隔的路径元素。ZooKeeper 命名空间中的每个节点都由路径标识。
ZooKeeper 的层次结构Namespace
节点和临时节点
与标准文件系统不同,ZooKeeper 命名空间中的每个节点都可以具有与其关联的数据以及子节点。这就像有一个文件系统,允许文件也是一个目录。(ZooKeeper 设计用于存储协调数据:状态信息、配置、位置信息等,因此每个节点存储的数据通常很小,在字节到千字节的范围内。我们使用术语 znode 来明确表示我们谈论的是 ZooKeeper 数据节点。
Znodes 维护一个统计信息结构,其中包括数据更改、ACL 更改和时间戳的版本号,以允许缓存验证和协调更新。每次 znode 的数据发生变化时,版本号都会增加。例如,每当客户端检索数据时,它也会接收数据的版本。
存储在命名空间中每个 znode 的数据都是以原子方式读取和写入的。读取操作获取与 znode 关联的所有数据字节,写入操作将替换所有数据。每个节点都有一个访问控制列表 (ACL),用于限制谁可以执行哪些操作。
ZooKeeper 也有临时节点的概念。只要创建 znode 的会话处于活动状态,这些 znodes 就存在。当会话结束时,znode 将被删除。
有条件的更新和监视
ZooKeeper 支持手表的概念。客户端可以在 znode 上设置监视。当 znode 发生变化时,将触发并删除监视。当触发监视时,客户端会收到一个数据包,指出 znode 已更改。如果客户端与其中一个 ZooKeeper 服务器之间的连接断开,客户端将收到本地通知。
3.6.0 中的新功能:客户端还可以在 znode 上设置永久的递归监视,这些监视在触发时不会被删除,并且会以递归方式触发已注册的 znode 以及任何子 znode 上的更改。
保证
ZooKeeper 非常快速且非常简单。但是,由于其目标是成为构建更复杂服务(例如同步)的基础,因此它提供了一套保证。这些是:
顺序一致性 - 来自客户端的更新将按照发送的顺序应用。 原子性 - 更新要么成功,要么失败。没有部分结果。 单一系统映像 - 无论客户端连接到哪个服务器,它都将看到服务的相同视图。也就是说,即使客户端故障转移到具有相同会话的不同服务器,客户端也永远不会看到系统的旧视图。 可靠性 - 应用更新后,它将从那时起一直存在,直到客户端覆盖更新。 时效性 - 保证客户对系统的视图在一定时间范围内是最新的。
简单的 API
ZooKeeper 的设计目标之一是提供一个非常简单的编程接口。因此,它仅支持以下操作:
创建 :在树中的某个位置创建节点 delete :删除节点 exists :测试某个位置是否存在节点 获取数据 :从节点读取数据 设置数据:向节点写入数据 Get Children :检索节点的子节点列表 sync : 等待数据传播
实现
ZooKeeper 组件 显示 ZooKeeper 服务的高级组件。除请求处理器外,组成 ZooKeeper 服务的每个服务器都复制其自己的每个组件副本。
复制的数据库是包含整个数据树的内存中数据库。更新将记录到磁盘以实现可恢复性,写入操作在应用于内存中数据库之前会序列化到磁盘。
每个 ZooKeeper 服务器都为客户端提供服务。客户端只连接到一个服务器以提交请求。读取请求从每个服务器数据库的本地副本提供服务。更改服务状态的请求(写入请求)由协议协议处理。
作为协议协议的一部分,来自客户端的所有写入请求都被转发到单个服务器,称为领导者。其余的 ZooKeeper 服务器(称为追随者)接收来自领导者的消息提案,并就消息传递达成一致。消息传递层负责在失败时替换领导者,并将追随者与领导者同步。
ZooKeeper 使用自定义原子消息传递协议。由于消息层是原子的,因此 ZooKeeper 可以保证本地副本永远不会发散。当领导者收到写入请求时,它会计算出在应用写入时系统的状态,并将其转换为捕获此新状态的事务。
使用
ZooKeeper 的编程接口刻意简单。但是,使用它,您可以实现更高阶的操作,例如同步原语、组成员身份、所有权等。
性能
ZooKeeper 旨在实现高性能。但事实果真如此吗?雅虎研究院的ZooKeeper开发团队的结果表明,确实如此。(请参阅 ZooKeeper 吞吐量,因为读写比率会有所不同。在读取次数超过写入次数的应用程序中,它的性能尤其高,因为写入涉及同步所有服务器的状态。(读取次数多于写入次数通常是协调服务的情况。
ZooKeeper 吞吐量随读写比率变化而变化是 ZooKeeper 版本 3.2 的吞吐量图,该版本在具有双 2Ghz Xeon 和两个 SATA 15K RPM 驱动器的服务器上运行。一个驱动器被用作专用的 ZooKeeper 日志设备。快照已写入操作系统驱动器。写入请求为 1K 次写入,读取为 1K 次读取。“服务器”表示 ZooKeeper 集合体的大小,即组成服务的服务器数量。大约有 30 台其他服务器用于模拟客户端。ZooKeeper 集合体的配置使得领导者不允许来自客户端的连接。
注意
在版本 3.2 中,与之前的 3.1 版本相比,r/w 性能提高了 ~2 倍。
基准测试还表明它也很可靠。Reliability in the Presence of Errors 显示了部署如何响应各种故障。图中标记的事件如下:
从属的故障和恢复 不同从属程序的失败和恢复 领导者的失败 两个追随者的失败和恢复 另一位领导人的失败
可靠性
为了显示系统在注入故障时随时间推移的行为,我们运行了一个由 7 台机器组成的 ZooKeeper 服务。我们运行了与以前相同的饱和度基准测试,但这次我们将写入百分比保持在恒定的 30%,这是我们预期工作负载的保守比率。
从这张图中可以看出一些重要的观察结果。首先,如果追随者失败并迅速恢复,那么即使失败,ZooKeeper 也能够维持高吞吐量。但也许更重要的是,领导者选举算法允许系统以足够快的速度恢复,以防止吞吐量大幅下降。在我们的观察中,ZooKeeper 只需不到 200 毫秒即可选出新的领导者。第三,随着关注者的恢复,一旦他们开始处理请求,ZooKeeper 就能够再次提高吞吐量。
开始
入门:使用 ZooKeeper 协调分布式应用程序
先决条件
java环境
下载
https://mirrors.aliyun.com/apache/zookeeper/zookeeper-3.9.2
单机
在独立模式下设置 ZooKeeper 服务器非常简单。服务器包含在单个 JAR 文件中,因此安装包括创建配置。
一旦你下载了一个稳定的ZooKeeper版本,就把它解压缩并cd到根目录
要启动 ZooKeeper,您需要一个配置文件。下面是一个示例,在 conf/zoo.cfg 中创建它:
tickTime=2000 dataDir=/var/lib/zookeeper clientPort=2181
这个文件可以叫任何东西,但为了便于讨论,请将其命名为 conf/zoo.cfg。更改 dataDir 的值以指定现有(开始时为空)目录。以下是每个字段的含义:
tickTime :ZooKeeper 使用的基本时间单位(以毫秒为单位)。它用于执行心跳,最小会话超时将是 tickTime 的两倍。 dataDir:存储内存中数据库快照的位置,除非另有说明,否则存储数据库更新的事务日志。 clientPort:侦听客户端连接的端口
现在您已经创建了配置文件,可以启动 ZooKeeper:
bin/zkServer.sh start
ZooKeeper 使用 logback 记录消息 – 更多详细信息可在 Programmer’s Guide 的 Logging 部分找到。您将看到发送到控制台的日志消息(默认)和/或日志文件,具体取决于 logback 配置。
此处概述的步骤以独立模式运行 ZooKeeper。没有复制,因此如果 ZooKeeper 进程失败,服务将关闭。这对于大多数开发情况来说都很好,但是要在复制模式下运行 ZooKeeper,请参阅运行复制的 ZooKeeper。
管理 ZooKeeper 存储
对于长时间运行的生产系统,必须在外部管理ZooKeeper存储(dataDir和logs)。有关详细信息,请参阅维护部分。
连接到 ZooKeeper
$ bin/zkCli.sh -server 127.0.0.1:2181
这使您可以执行简单的、类似文件的操作。
连接后,您应该会看到类似以下内容:
Connecting to localhost:2181 ... Welcome to ZooKeeper! JLine support is enabled [zkshell: 0]
在 shell 中,键入以获取可从客户端执行的命令列表,如下所示:help
[zkshell: 0] help ZooKeeper -server host:port cmd args addauth scheme auth close config [-c] [-w] [-s] connect host:port create [-s] [-e] [-c] [-t ttl] path [data] [acl] delete [-v version] path deleteall path delquota [-n|-b] path get [-s] [-w] path getAcl [-s] path getAllChildrenNumber path getEphemerals path history listquota path ls [-s] [-w] [-R] path printwatches on|off quit reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*] redo cmdno removewatches path [-c|-d|-a] [-l] set [-s] [-v version] path data setAcl [-s] [-v version] [-R] path acl setquota -n|-b val path stat [-w] path sync path
从这里,您可以尝试一些简单的命令来感受这个简单的命令行界面。首先,从发出 list 命令开始,如 中所示,产生:ls
[zkshell: 8] ls / [zookeeper]
接下来,通过运行 创建一个新的 znode。这将创建一个新的 znode 并将字符串“my_data”与节点关联。您应该看到:create /zk_test my_data
[zkshell: 9] create /zk_test my_data Created /zk_test
发出另一个命令以查看目录的外观:ls /
发出另一个命令以查看目录的外观:ls /
请注意,zk_test 目录现已创建。
接下来,通过运行以下命令验证数据是否与 znode 关联,如下所示:get
[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
我们可以通过发出命令来更改与zk_test关联的数据,如下所示:set
[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
[zkshell: 16] delete /zk_test [zkshell: 17] ls / [zookeeper] [zkshell: 18]
ZooKeeper集群
在独立模式下运行 ZooKeeper 便于评估、一些开发和测试。但在生产环境中,您应该在复制模式下运行 ZooKeeper。同一应用程序中复制的服务器组称为仲裁,在复制模式下,仲裁中的所有服务器都具有同一配置文件的副本。
注意
对于复制模式,至少需要三台服务器,并且强烈建议您拥有奇数台服务器。如果您只有两台服务器,那么您所处的情况是,如果其中一台服务器发生故障,则没有足够的计算机来构成多数仲裁。两台服务器本质上不如一台服务器稳定,因为有两个单点故障。
复制模式所需的 conf/zoo.cfg 文件与独立模式中使用的文件类似,但有一些区别。下面是一个示例:
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 限制服务器与领导者的过时时间。
对于这两个超时,您可以使用 tickTime 指定时间单位。在此示例中,initLimit 的超时为 5 个刻度,每个刻度为 2000 毫秒,即 10 秒。
表单服务器的条目。X 列出组成 ZooKeeper 服务的服务器。当服务器启动时,它通过在数据目录中查找文件 myid 来知道它是哪个服务器。该文件包含 ASCII 格式的服务器编号。
最后,记下每个服务器名称后面的两个端口号:“2888”和“3888”。对等体使用前一个端口连接到其他对等体。这种连接是必要的,以便对等方可以进行通信,例如,就更新的顺序达成一致。更具体地说,ZooKeeper 服务器使用此端口将关注者连接到领导者。当出现新的领导者时,追随者使用此端口打开与领导者的 TCP 连接。由于默认的领导者选举也使用 TCP,因此我们目前需要另一个端口进行领导者选举。这是服务器条目中的第二个端口。
注意
如果要在一台机器上测试多个服务器,请将服务器名称指定为 localhost,并为每个服务器提供唯一的仲裁和领导选举端口(即上述示例中的 2888:3888、2889:3889、2890:3890)。X。当然,单独的_dataDir_s和不同的_clientPort_s也是必要的(在上面复制的示例中,在单个本地主机上运行,您仍然会有三个配置文件)。
请注意,在一台机器上设置多个服务器不会产生任何冗余。如果发生某些事情导致机器死机,所有的动物园管理员服务器都将离线。完全冗余要求每个服务器都有自己的计算机。它必须是完全独立的物理服务器。同一物理主机上的多个虚拟机仍然容易受到该主机完全故障的影响。
如果您的 ZooKeeper 计算机中有多个网络接口,您还可以指示 ZooKeeper 绑定所有接口,并在网络故障时自动切换到健康的接口。有关详细信息,请参阅配置参数。
其他优化
还有其他几个配置参数可以大大提高性能:
为了获得低延迟的更新,拥有专用的事务日志目录非常重要。默认情况下,事务日志与数据快照和 myid 文件放在同一目录中。dataLogDir 参数指示要用于事务日志的不同目录。