0%

Zookeeper快速上手

Zookeeper头图

ZooKeeper 是一个开源的分布式协调服务,是 Google 的 Chubby 一个开源的实现,是构建分布式的 Hadoop、HBase、Dubbox、Kafka 的重要组件。它为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,提供的功能包括:数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等。


概述

ZooKeeper 是一个开源的分布式协调服务,ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

原语: 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。

ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

另外,ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。

特点

  • 顺序一致性:从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
  • 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
  • 单一系统映像:无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
  • 可靠性:一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。

应用场景

ZooKeeper 概览中,我们介绍到使用其通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

下面选 3 个典型的应用场景来专门说说:

  1. 分布式锁 : 通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
  2. 命名服务 :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
  3. 数据发布/订阅 :通过 Watcher 机制 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。

实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。

在开源项目中的应用

  1. Kafka : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。
  2. Hbase : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
  3. Hadoop : ZooKeeper 为 Namenode 提供高可用支持。

重要概念

数据模型

ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都一个唯一的路径标识。

强调一句:ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的上限是每个结点的数据大小最大是 1M。

从下图可以更直观地看出:ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠”/“进行分割的路径表示,开发人员可以向这个节点中写人数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。

ZooKeeper 数据模型

数据节点znode

介绍了 ZooKeeper 树形数据模型之后,我们知道每个数据节点在 ZooKeeper 中被称为 znode,它是 ZooKeeper 中数据的最小单元。你要存放的数据就放在上面,是你使用 ZooKeeper 过程中经常需要接触到的一个概念。

znode的类型

通常将 znode 分为四大类:

  • 持久(PERSISTENT)节点 :一旦创建就一直存在,直到将其删除。
  • 临时(EPHEMERAL)节点 :临时节点的生命周期是与 客户端会话(session) 绑定,会话消失则节点消失 。并且临时节点只能做叶子节点 ,不能创建子节点。
  • 持久顺序(PERSISTENT_SEQUENTIAL)节点 :除了具有持久节点的特性之外, 子节点的名称还具有顺序性。每次创建顺序节点时,ZooKeeper 都会在节点名称后面自动追加 10 位数字。如 /sannaha/node0000000001/sannaha/node0000000001
  • 临时顺序(EPHEMERAL_SEQUENTIAL)节点 :除了具备临时节点的特性之外,子节点的名称还具有顺序性。

znode的数据结构

每个 znode 由两部分组成:

  • stat:状态信息。
  • data:节点存放的数据内容。
  • children:该 znode下的子节点。

通过 get 命令可以查看节点内容。get 命令的使用详见: 查看节点内容(get)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[zk: 127.0.0.1:2181(CONNECTED) 0] get /sannaha
# 该节点存放的数据内容
"sannaha"
# 该节点的状态信息
cZxid = 0x700000328
ctime = Tue Sep 01 15:59:05 CST 2020
mZxid = 0x700000328
mtime = Tue Sep 01 15:59:05 CST 2020
pZxid = 0x700000339
cversion = 1
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 9
numChildren = 1

状态信息的含义:

znode 状态信息 解释
cZxid create ZXID,该节点被创建时的事务 ID
ctime create time,创建该节点时时间戳
mZxid modified ZXID,该节点最后一次修改时的事务 ID
mtime modified time,该节点最后一次修改时的时间戳
pZxid 该节点的直接子节点最后一次变化时的事务 ID。子节点在 create/delete/rmr 后会更新 pZxid,set 不会更新 pZxid
cversion 直接子节点的版本号,pZxid 每次变化时版本号加 1
dataVersion 该节点内容的版本号,节点创建时为 0,每 set 一次版本号加 1
aclVersion 该节点的 ACL 版本号,表示该节点 ACL 信息变更次数
ephemeralOwner 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则为 0x0
dataLength 节点内容的长度
numChildren 该节点的直接子节点个数

版本

对应于每个 znode,ZooKeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:

  • dataVersion:当前 znode 节点的版本号。
  • cversion:当前 znode 子节点的版本。
  • aclVersion: 当前 znode 的 ACL 的版本。

权限控制

ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。ZooKeeper 提供了以下 5 种对于 znode 操作的权限:

  • CREATE : 能创建子节点。
  • READ :能获取节点数据和列出其子节点。
  • WRITE : 能设置/更新节点数据。
  • DELETE : 能删除子节点。
  • ADMIN : 能设置节点 ACL 的权限。

其中 CREATEDELETE 这两种权限都是针对 子节点 的权限控制。

对于身份认证,提供了以下几种方式:

  • world:默认方式,所有用户都可无条件访问。
  • auth:不使用任何 id,代表任何已认证的用户。
  • digestusername:password 认证方式 。
  • ip:对指定 ip 进行限制。

事件监听器

Watcher(事件监听器)是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许客户端在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。

触发事件的种类包括:节点创建、节点删除、节点改变、子节点改变等。

watcher机制

概括来说 Watcher 包含以下三个过程:

  1. 客户端向服务端注册 Watcher。
  2. 服务端事件发生触发 Watcher。
  3. 客户端回调 Watcher 得到触发事件情况。

Watch 机制特点如下:

  • 一次性触发:事件发生触发监听,一个 WatcherEvent 就会被发送到设置监听的客户端,这种效果是一次性的,后续再次发生同样的事件,不会再次触发。
  • 事件封装:ZooKeeper 使用 WatchedEvent 对象来封装服务端事件并传递。WatchedEvent 包含了每一个事件的三个基本属性:
  • 通知状态(keeperState)
  • 事件类型(EventType)
  • 节点路径(path)
  • Event 异步发送:Watcher 的通知事件从服务端发送到客户端是异步的。
  • 先注册再触发:Zookeeper 中的 Watch 机制,必须客户端先去服务端注册监听,这样事件发送才会触发监听,通知给客户端。

会话

Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。

Session 有一个属性叫做:sessionTimeoutsessionTimeout 代表会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 sessionID。由于 sessionID是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。

集群

为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。

zookeeper集群

上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。

集群角色

Master/Slave 模式(主从模式)是最典型集群模式,通常 Master 服务器提供写服务,其他的 Slave 服务器通过异步复制的方式获取 Master 服务器最新的数据并提供读服务。但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。

Zookeeper集群角色

ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,FollowerObserver 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。

角色 描述
Leader 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。
Follower 为客户端提供读服务,如果是写服务则转发给 Leader。在选举过程中参与投票。
Observer 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略的投票。在不影响写性能的情况下提升集群的读性能。

当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。

服务器状态

  • LOOKING:寻找状态。服务器认为当前集群中没有 Leader,会发起 Leader 选举。
  • LEADING:领导者状态。表明当前服务器角色为 Leader。
  • FOLLOWING:跟随者状态。表明当前服务器角色为 Follower。
  • OBSERVING:观察者状态,表明当前服务器角色为 Observer。

ZAB协议与选举过程

Paxos 算法是解决分布式一致性问题最有效的算法之一,但是 ZooKeeper 并没有完全采用 Paxos 算法,而是使用 ZAB 协议来保证数据一致性。ZAB 协议不像 Paxos 算法那样具有通用性,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法。

如果想了解 Paxos 算法,可以阅读 Paxos 算法

ZAB协议介绍

ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

除了规定了 Leader/Follower/Observer 三种角色外,ZAB 协议还定义了两种模式,分别是崩溃恢复消息广播

崩溃恢复模式

当整个服务框架刚刚启动,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。

说到崩溃恢复就需要介绍下 ZAB 中的 Leader 选举算法,因为 Leader 只有一个,当 Leader 出现问题的时候势必需要重新选举 Leader。

选举算法

选举算法的目标是选出具有最新状态的节点成为 Leader 来同步数据给其他节点。简单来讲,这一过程就是选出目前所有节点中拥有最大 Zxid 的节点作为 Leader,如果多个节点的 Zxid 相同,选其中 SID 最大的作为 Leader。

注:Zxid 为事务 ID,越大说明数据越新;SID 为服务器 ID,通过 myid 文件指定(CDH6.1.1 中默认路径为 /var/lib/zookeeper/myid )。为了简化说明,省去逻辑时钟的对比。

选举细则:

  1. 集群中没有 Leader 节点时才开始选举。

  2. 每一轮选举,所有有资格参与投票的服务器都会交换一次选票。选票内容包含 SID 和 Zxid,记作 (SID, Zxid)。初始情况,每一台服务器都会推举自己作为 Leader 服务器,选票内容为自己的 SID 和 Zxid。

  3. 在交换选票后,每台服务器会收到其他服务器投出的选票信息,比较这些选票,将自己的选票内容改写为 Zxid 更大的那张,如果 Zxid 相同,优先选择 SID 更大的那张,然后开始下一轮选举。

  4. 某个节点得票数量大于集群节点总数的一半时才能成功当选 Leader。

服务启动时期的选举

以 3 台服务器 node01/node02/node03 组成的集群为例,集群首次运行,3 台服务器依次启动,Zxid 为 0,SID 分别为 1/2/3。

  • node01 最先启动,集群中未出现 Leader,进入 LOOKING 状态,发起 Leader 选举。投票给自己,选票为 (0, 1)
  • node02 启动,同样处于 LOOKING 状态。投票给自己,选票为 (0, 2)
  • 开启一轮投票,交换选票信息。
  • node01 持有的选票信息为 (0, 1)(0, 2),比较后将选票改写为 (0, 2),即投票给 node02。
  • node02 在比较后保持选票为 (0, 2) 不变。
  • 开启一轮投票,交换选票信息。
  • node02 收获选票数量超过集群节点总数的一半,当选 Leader,进入 LEADING 状态。node01 则成为 Follower,进入 FOLLOWING 状态。
  • node03 最后启动,此时集群中存在 Leader,node03 直接成为 Follower。

服务运行时期的选举

还是以上面 3 台服务器组成的集群为例,运行过程中 node02 挂了,集群需要重新选出 Leader。假设此时 node01 的 Zxid 为 10,node03 的 Zxid 为 9。

  • Leader 挂了,集群停止服务,进入 LOOKING 状态。
  • node01 投票给自己,选票为 (10, 1)
  • node03 投票给自己,选票为 (9, 3)
  • 开启一轮投票,交换选票信息。
  • node01 持有的选票信息为 (10, 1)(9, 3),比较后保持选票为 (10, 1) 不变。
  • node03 在比较后将选票改写为 (10, 1),即投票给 node01。
  • 开启一轮投票,交换选票信息。
  • node01 收获选票数量超过集群节点总数的一半,当选 Leader,node03 则成为 Follower。在 node03 与 node01 完成状态同步后,集群退出崩溃恢复模式。

为什么建议使用奇数个节点?

根据选举的过半机制,可以得到以下推论:

  • 集群节点数量为 3 个,需要在 2 个节点启动后才能选出 Leader,该集群最多允许挂掉1个节点。
  • 集群节点数量为 4 个,需要在 3 个节点启动后才能选出 Leader,该集群最多允许挂掉1个节点。
  • 集群节点数量为 5 个,需要在 3 个节点启动后才能选出 Leader,该集群最多允许挂掉2个节点。
  • 集群节点数量为 6 个,需要在 4 个节点启动后才能选出 Leader,该集群最多允许挂掉2个节点。

多角度对比奇数个和偶数个节点的优劣:

  • 初始化选举角度来看,奇数个节点(2N+1)对比偶数个节点(2N+2),能更快选举出 Leader。
  • 容灾角度来看,奇数个节点(2N+1)和偶数个节点(2N+2)容灾能力相同。
  • 成本角度来看,奇数个节点(2N+1)比偶数个节点(2N+2)成本低。
  • 性能角度来看,奇数个节点(2N+1)对比偶数个节点(2N+2),写服务性能相同而读服务性能稍弱。
  • 脑裂问题对集群的影响来看,如果发生脑裂形成两个小集群,奇数个节点(2N+1)总有一个小集群能满足过半机制的要求,能够继续选举出 Leader 对外提供服务;偶数个节点(2N+2)存在形成两个均等小集群的可能,无法继续提供服务。

综合考虑,Zookeeper 集群建议使用奇数个节点。

消息广播模式

ZooKeeper 中所有的事务请求都由 Leader 来处理,事务是指能够改变 Zookeeper 服务器状态的操作,比如数据节点的创建与删除、数据节点内容的更新和客户端会话创建与失效等操作。而消息广播模式就是 ZAB 协议用来处理事务请求的。

当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自寻找 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

写Leader

写Leader

Leader 服务器在接收到客户端的事务请求后会生成对应的事务提案并发起一轮广播,主要分为五步:

  1. 客户端向 Leader 发起写请求。
  2. Leader 将写请求以 Proposal 的形式发给所有 Follower 并等待 ACK。
  3. Follower 收到 Leader 的 Proposal 后返回 ACK。
  4. Leader 得到过半数的 ACK(Leader 对自己默认有一个 ACK)后向所有的 Follower 和 Observer 发送 Commmit。
  5. Leader 将处理结果返回给客户端。

注意:

  • Leader 并不需要得到 Observer 的 ACK,即 Observer 无投票权。
  • Leader 不需要得到所有 Follower 的 ACK,只要收到过半的 ACK 即可,注意 Leader 本身对自己有一个 ACK。
  • Observer 虽然无投票权,但仍须同步 Leader 的数据从而在处理读请求时可以返回尽可能新的数据。

写Follower和Observer

写Follower和Observer

集群中的 Follower/Observer 接收到客户端的事务请求,会首先将这个事务请求转发给 Leader 服务器,之后的流程与直接写 Leader 无任何区别。

读操作

读操作

Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取数据并返回给客户端即可。

安装

开发环境

  1. 三台虚拟机
HostName IP
node01 192.168.153.100
node02 192.168.153.101
node03 192.168.153.102
  1. 端口分配
端口 作用
2181 客户端连接 Zookeeper 集群使用的端口号
3888 选举 Leader
2888 集群内机器通讯使用的端口号(Leader 和 Follower 之间数据同步,Leader 监听此端口)

安装

  1. 下载并解压 zookeeper-3.4.5-cdh5.14.0.tar.gz下载地址

  2. 复制 zoo_sample.cfg 文件为 zoo.cfg,并修改配置:

zoo.cfg
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
# 更改zookeeper数据的默认存放目录,避免被系统清除
- dataDir=/tmp/zookeeper
+ dataDir=/export/servers/zookeeper-3.4.5-cdh5.14.0/zkdatas
# the port at which the clients will connect
# 2181端口对cline端提供服务
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# 设置保留快照数量为3个
+ autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1
# 设置自动清除快照、事务日志的任务间隔为1小时
+ autopurge.purgeInterval=1
# 配置zookeeper集群,2888为选举端口,3888为通信端口
+ server.1=node01:2888:3888
+ server.2=node02:2888:3888
+ server.3=node03:2888:3888
  1. 分发 zookeeper-3.4.5-cdh5.14.0 到 node02 和 node03:
1
2
scp -r zookeeper-3.4.5-cdh5.14.0/ node02:$PWD
scp -r zookeeper-3.4.5-cdh5.14.0/ node03:$PWD
  1. 添加 myid 配置,分别在三台虚拟机输入对应的命令:
zookeeper-3.4.5-cdh5.14.0/
1
2
3
4
5
6
7
8
9
10
11
# node01执行,设置myid为1
mkdir zkdatas
echo "1" > zkdatas/myid

# node02执行,设置myid为2
mkdir zkdatas
echo "2" > zkdatas/myid

# node03执行,设置myid为3
mkdir zkdatas
echo "3" > zkdatas/myid
  1. 三台虚拟机依次启动 zookeeper 服务:
zookeeper-3.4.9/bin/
1
$ ./zkServer.sh start

或是使用脚本一键启停三台虚拟机的 Zookeeper 服务:

  • 启动脚本:
zookeeper-3.4.5-cdh5.14.0/startZKServers.sh
1
2
3
4
5
6
7
#!/bin/bash
echo "启动zookeeper集群中..."
for host in node01 node02 node03
do
ssh -q $host "source /etc/profile; /export/servers/zookeeper-3.4.5-cdh5.14.0/bin/zkServer.sh start"
done
echo "启动完成"
  • 关闭脚本:
zookeeper-3.4.5-cdh5.14.0/stopZKServers.sh
1
2
3
4
5
6
7
#!/bin/bash
echo "关闭zookeeper集群中..."
for host in node01 node02 node03
do
ssh -q $host "source /etc/profile; /export/servers/zookeeper-3.4.5-cdh5.14.0/bin/zkServer.sh stop"
done
echo "关闭完成"

注:编写脚本后使用 chmod 赋予脚本执行权限。

  1. 查看当前虚拟机在 Zookeeper 集群中的角色:
zookeeper-3.4.9/bin
1
2
3
4
$ ./zkServer.sh status
JMX enabled by default
Using config: /export/servers/zookeeper-3.4.5-cdh5.14.0/bin/../conf/zoo.cfg
Mode: follower

常用命令

从这里开始,演示环境有所变化,操作系统更换为 CentOS 7,大数据平台更换为 CDH6.1.1,其中 Zookeeper 版本为 3.4.5。

连接Zookeeper服务

/opt/cloudera/parcels/CDH-6.1.1-1.cdh6.1.1.p0.875250/lib/zookeeper/bin/
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./zkCli.sh -server 127.0.0.1:2181
Connecting to 127.0.0.1:2181
...
Welcome to ZooKeeper!
2020-09-01 18:33:50,711 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1012] - Opening socket connection to server localhost/127.0.0.1:2181. Will not attempt to authenticate using SASL (unknown error)
JLine support is enabled
2020-09-01 18:33:50,761 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@856] - Socket connection established, initiating session, client: /127.0.0.1:38780, server: localhost/127.0.0.1:2181
2020-09-01 18:33:50,768 [myid:] - INFO [main-SendThread(localhost:2181):ClientCnxn$SendThread@1272] - Session establishment complete on server localhost/127.0.0.1:2181, sessionid = 0x1744887b0c600d2, negotiated timeout = 30000

WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: 127.0.0.1:2181(CONNECTED) 0]

帮助(help)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[zk: 127.0.0.1:2181(CONNECTED) 0] help
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit
getAcl path
close
connect host:port

查看节点列表(ls)

1
2
3
# 查看根节点下的所有直接子节点
[zk: 127.0.0.1:2181(CONNECTED) 1] ls /
[cluster, controller, brokers, zookeeper, admin, isr_change_notification, log_dir_event_notification, controller_epoch, consumers, latest_producer_id_block, config]

创建节点(create)

语法:

  • 创建持久节点:create <path> <data>
  • 创建持久顺序节点:create -s <path> <data>
  • 创建临时节点:create -e <path> <data>
  • 创建临时顺序节点:create -s -e <path> <data>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 在根节点下创建持久节点node1,数据内容为PERSISTENT
[zk: 127.0.0.1:2181(CONNECTED) 2] create /node1 PERSISTENT
Created /node1

# 在node1节点下创建持久节点node1.1,数据内容为123
[zk: 127.0.0.1:2181(CONNECTED) 3] create /node1/node1.1 123
Created /node1/node1.1

# 在node1节点下创建持久顺序节点node*,数据内容为PERSISTENT_SEQUENTIAL
[zk: 127.0.0.1:2181(CONNECTED) 4] create -s /node1/node PERSISTENT_SEQUENTIAL
Created /node1/node0000000001
[zk: 127.0.0.1:2181(CONNECTED) 5] create -s /node1/node PERSISTENT_SEQUENTIAL
Created /node1/node0000000002

# 在node1节点下创建临时节点enode1.2,数据内容为EPHEMERAL
[zk: 127.0.0.1:2181(CONNECTED) 6] create -e /node1/enode1.2 EPHEMERAL
Created /node1/enode1.2

# 在node1节点下创建临时顺序节点enode*,数据内容为EPHEMERAL_SEQUENTIAL
[zk: 127.0.0.1:2181(CONNECTED) 7] create -s -e /node1/enode EPHEMERAL_SEQUENTIAL
Created /node1/enode0000000004
[zk: 127.0.0.1:2181(CONNECTED) 8] create -s -e /node1/enode EPHEMERAL_SEQUENTIAL
Created /node1/enode0000000005

注意:

  • 每个持久节点下都有一个单独的计数器,用于给顺序节点编号。
  • 计数器由 Leader 维护,从 0000000000 开始。
  • 在同一个节点下,每创建一个节点(不论是持久节点、临时节点、持久顺序节点还是临时顺序节点),计数器都加 1。

查看节点内容(get)

每个 znode 由两部分组成:

  • stat:状态信息。
  • data:节点存放的数据内容。
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
26
27
28
29
# 查看持久节点内容
[zk: 127.0.0.1:2181(CONNECTED) 9] get /node1
PERSISTENT
cZxid = 0x700000b8a
ctime = Tue Sep 01 18:36:50 CST 2020
mZxid = 0x700000b8a
mtime = Tue Sep 01 18:36:50 CST 2020
pZxid = 0x700000ba0
cversion = 6
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 10
numChildren = 6

# 查看临时节点内容,ephemeralOwner显示着当前sessionid
[zk: 127.0.0.1:2181(CONNECTED) 10] get /node1/enode1.2
EPHEMERAL
cZxid = 0x700000b9e
ctime = Tue Sep 01 18:37:30 CST 2020
mZxid = 0x700000b9e
mtime = Tue Sep 01 18:37:30 CST 2020
pZxid = 0x700000b9e
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x1744887b0c600d2
dataLength = 9
numChildren = 0

状态信息的含义:

znode 状态信息 解释
cZxid create ZXID,该节点被创建时的事务 ID
ctime create time,创建该节点时时间戳
mZxid modified ZXID,该节点最后一次修改时的事务 ID
mtime modified time,该节点最后一次修改时的时间戳
pZxid 该节点的直接子节点最后一次变化时的事务 ID。子节点在 create/delete/rmr 后会更新 pZxid,set 不会更新 pZxid
cversion 直接子节点的版本号,pZxid 每次变化时版本号加 1
dataVersion 该节点内容的版本号,节点创建时为 0,每 set 一次版本号加 1
aclVersion 该节点的 ACL 版本号,表示该节点 ACL 信息变更次数
ephemeralOwner 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则为 0x0
dataLength 节点内容的长度
numChildren 该节点的直接子节点个数

修改节点数据(set)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 修改node1节点数据
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 newdata
cZxid = 0x700000b8a
ctime = Tue Sep 01 18:36:50 CST 2020
mZxid = 0x700000bc1
mtime = Tue Sep 01 18:40:28 CST 2020
pZxid = 0x700000ba0
cversion = 6
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 6

查看节点状态(stat)

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看node1节点状态
[zk: 127.0.0.1:2181(CONNECTED) 12] stat /node1
cZxid = 0x700000b8a
ctime = Tue Sep 01 18:36:50 CST 2020
mZxid = 0x700000bc1
mtime = Tue Sep 01 18:40:28 CST 2020
pZxid = 0x700000ba0
cversion = 6
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 6

查看节点列表和状态(ls2)

ls2 命令更像是 ls 命令和 stat 命令的结合。 ls2 命令返回的信息包括两部分:

  1. 子节点列表。
  2. 当前节点的 stat 信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 查看node1节点列表和状态
[zk: 127.0.0.1:2181(CONNECTED) 13] ls2 /node1
[node0000000002, enode0000000005, enode1.2, node1.1, enode0000000004, node0000000001]
cZxid = 0x700000b8a
ctime = Tue Sep 01 18:36:50 CST 2020
mZxid = 0x700000bc1
mtime = Tue Sep 01 18:40:28 CST 2020
pZxid = 0x700000ba0
cversion = 6
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 6

删除节点(delete)

存在子节点的节点无法直接删除,需要先删除子节点,才能删除该节点。

1
[zk: 127.0.0.1:2181(CONNECTED) 14] delete /node1/node1.1

递归删除节点(rmr)

1
[zk: 127.0.0.1:2181(CONNECTED) 15] rmr /node1

总结

  • ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
  • 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的,那么 ZooKeeper 本身仍然是可用的。
  • ZooKeeper 将数据保存在内存中,这也就保证了高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。
  • ZooKeeper 是高性能的。高性能主要体现在“读”多于“写”这种协调服务的典型场景中,因为“写”会导致所有的服务器间同步状态。
  • ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。
  • ZooKeeper 底层其实只提供了两个功能:统一管理(存储、读取)用户程序提交的数据为用户程序提供节点提供监听服务(从而管理节点的上下线)。

Zookeeper 介绍部分转载自 nailClimb 的 JavaGuide,有删改。

参考资料
ZooKeeper 相关概念总结
ZooKeeper 实战
实例详解ZooKeeper ZAB协议、分布式锁与领导选举