type
status
date
slug
summary
tags
category
password
1、概述
ZooKeeper 是一个分布式协调服务,主要用于解决分布式系统中的一致性问题。ZooKeeper 可以保证如下分布式一致性特性:
- 顺序一致性:从同一个客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到 ZooKeeper 中去。
- 原子性:所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群所有机器都成功应用了某一个事务,要么都没有应用,一定不会出现集群中部分机器应用了该事务,而另外一部分没有应用的情况。
- 单一视图:无论客户端连接的是哪个 ZooKeeper 服务器,其看到的服务端数据模型都是一致的。
- 可靠性:一旦服务端成功地应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会被一直保留下来,除非有另一个事务又对其进行了变更。
- 实时性:通常人们看到实时性的第一反应是,一旦一个事务被成功应用,那么客户端能够立即从服务端上读取到这个事务变更后的最新数据状态。这里需要注意的是,ZooKeeper 仅仅保证在一定的时间段内,客户端最终一定能够从服务端上读取到最新的数据状态。
ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。ZooKeeper 的分布一致性应用场景:
- 分布式协调:提供分布式锁、队列、屏障等同步机制,协调多个节点的操作。避免多个服务同时操作共享资源(如数据库)导致冲突。
- 配置管理:集中存储和管理集群配置,所有节点可实时获取最新配置(如数据库连接信息)。
- 服务注册与发现:用于微服务架构中动态管理服务实例(如 Dubbo、Kafka 依赖 ZooKeeper 维护 Broker 列表)。
- 集群选主(Leader Election):在分布式系统中自动选举主节点,避免单点故障(如 HBase 的 Master 选举)。
- 命名服务(Naming Service):通过树形节点(ZNode)存储全局唯一的路径名,类似文件系统目录结构。
ZooKeeper 的实际应用场景:
- Hadoop/YARN:管理集群状态和资源配置。
- Kafka:存储 Broker 元数据和分区信息。
- Dubbo:服务注册与发现。
- HBase:协调 RegionServer 和 Master 选举。
ZooKeeper 的核心概念:
- ZNode:ZooKeeper 数据模型中的节点,类似文件系统的目录/文件,可存储少量数据(默认不超过 1MB)。
- Watcher 机制:客户端可监听 ZNode 的变化(如数据修改、子节点增减),实现事件驱动编程。
- ACL 权限控制:控制客户端对 ZNode 的访问权限(如读写、创建、删除)。
2、数据结构
2.1 ZNode结构
ZNode 是 ZooKeeper 中数据的最小单元,类似于 Unix 文件系统的文件和目录,由一系列使用斜杠
/
进行分割的路径表示,例如 /foo/path1
。开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点,形成一棵树状结构。如下图所示:
ZNode 按生命周期共分为四种类型:
- 持久节点(PERSISTENT):该数据节点被创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动清除这个节点。最常见的节点类型,使用示例:
create /path data
。
- 持久顺序节点(PERSISTENT_SEQUENTIAL):在持久节点的特性基础上增加顺序性。在创建节点时自动为给节点名加上一个 10 位单调递增的数字后缀,使用示例:
create -s /path data
→ 实际路径可能是/path0000000001
。
- 临时节点(EPHEMERAL):临时节点的生命周期和客户端的会话绑定在一起,如果客户端会话失效,那么这个节点就会被自动清理掉。另外,ZooKeeper 规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。使用示例:
create -e /path data
。
- 临时顺序节点(EPHEMERAL_SEQUENTIAL):在临时节点的特性基础上增加顺序性。使用示例:
create -e -s /path data
。
2.2 ZNode数据结构
每个 ZNode 包含以下部分:
- 数据(Data):最大 1MB 的二进制数据
- 元数据(Stat):包含了 ZNode 节点的所有状态信息
- czxid:创建该 ZNode 的事务ID
- mzxid:最后修改该 ZNode 的事务ID
- ctime:创建时间(毫秒)
- mtime:最后修改时间(毫秒)
- version:数据版本号。
- cversion:子节点版本号
- aversion:ACL 版本号
- ephemeralOwner:临时节点所有者会话ID(持久节点为0)
- dataLength:数据长度
- numChildren:子节点数量
- pzxid:最后修改子节点的事务ID
- 访问控制列表(ACL):控制谁可以访问和操作该ZNode
ZXID(事务ID)是什么?
在 ZooKeeper 中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,ZooKeeper 都会为其分配一个全局唯一的事务 ID,用 ZXID 来表示,通常是一个 64 位的数字。每一个 ZXID 对应一次更新操作,从这些 ZXID 中可以间接地识别出 ZooKeeper 处理这些更新操作请求的全局顺序。
我们可以通过
get
命令来获取 ZNode 的存储数据和元数据信息。返回结果中第一行是当前数据节点的数据内容,从第二行开始就是节点的
Stat
状态信息了。对于 ZooKeeper来说,无论数据内容是否变更,一旦有客户端调用了数据更新的接口,且更新成功,就会更新 dataVersion 值。2.3 ZNode的实际应用场景
1、分布式配置管理
- 场景:微服务或分布式集群需要动态更新配置(如数据库连接串、开关配置)。
- 实现:
- 将配置存储在持久化节点(如
/config/db_url
)。 - 服务启动时读取配置,并注册 Watcher 监听该节点。
- 当配置变更时,ZooKeeper 通知所有监听客户端,触发回调更新本地配置。
- 优势:避免轮询,保证配置一致性。
2、服务注册与发现
- 场景:动态扩缩容的微服务集群(如 Dubbo、Spring Cloud)。
- 实现:
- 服务启动时创建临时节点(如
/services/com.UserService/host1:8080
)。 - 服务下线时,会话结束导致节点自动删除。
- 客户端监听
/services/com.UserService
的子节点变化,获取可用服务列表。
- 优势:实时感知服务状态,无需手动维护服务列表。
3、分布式锁
- 场景:多节点竞争共享资源(如库存扣减)。
- 实现:
- 排他锁:多个客户端尝试创建同一个临时节点(如
/lock/order_123
),成功者获锁。 - 公平锁:使用临时顺序节点(如
/lock/order_123/node_0000001
),按序号顺序获取锁,避免“惊群效应”。 - 释放锁时删除节点,或会话终止时自动释放(防死锁)。
- 对比:优于 Redis 锁(无 Watcher 机制),但性能稍低。
4、Master 选举
- 场景:高可用集群(如 HDFS NameNode、Kafka Controller)需选举主节点。
- 实现:
- 所有候选节点创建临时顺序节点(如
/election/node_0001
)。 - 序号最小的节点成为 Master,其余节点监听前一个序号节点。
- 若 Master 宕机,ZooKeeper 自动删除其节点,触发次小节点接管。
- 优势:快速故障转移,避免脑裂。
5、命名服务与全局 ID
- 场景:生成分布式唯一 ID(如订单号、雪花算法中的 WorkerID)。
- 实现:
- 利用顺序节点的递增特性(如
/ids/order_0000001
),保证全局唯一且有序。 - 比 UUID 更易读,且无冲突风险。
6、集群状态监控
- 场景:实时监控节点存活状态(如 Hadoop 集群的 DataNode)。
- 实现:
- 节点注册为临时节点(如
/cluster/node1
)。 - 管理端监听父节点
/cluster
,子节点变化时触发告警或扩容。
注意事项:
- 性能限制:ZooKeeper 适合低频协调场景(如选举、配置),而非高频数据存储。
- 会话超时:临时节点依赖会话心跳,网络抖动可能导致节点误删,需合理设置
sessionTimeout
。
- 替代方案:部分场景可用 etcd(更高性能)、Consul(多数据中心支持)或 Redis(非强一致)。
3、Watch机制
ZooKeeper 的 Watcher 机制是指 ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知。Watcher 机制实现了分布式环境下的发布/订阅模式,是 ZooKeeper 实现分布式协调服务的基础。整个 Watcher 注册与通知过程如图所示。

ZooKeeper 的 Watcher 机制由三部分组成:
- 客户端线程
- 客户端 WatchManager
- ZooKeeper 服务器
整体工作流程如下所示:
- 客户端注册 Watcher 到服务端
- 服务端将 Watcher 存储到 WatchManager 中
- 当对应 ZNode 发生变化时,服务端触发 WatchManager 中的 Watcher
- 服务端向客户端发送 Watcher 事件通知
- 客户端处理 Watcher 事件回调
Watcher 机制的特点:
- 一次性:无论是服务端还是客户端,一旦一个 Watcher 被触发,ZooKeeper 都会将其从相应的存储中移除,再次使用需要重新注册。
- 客户端串行执行:同一客户端的 Watcher 回调是串行执行的,保证了同一客户端下 Watcher 执行的顺序性。但不同客户端的 Watcher 执行顺序不保证。
- 轻量:Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如针对 NodeDataChanged 事件,ZooKeeper 的 Watcher 只会通知客户端指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据。
- 子节点触发父节点:子节点的增删改都能够触发父节点的 Watcher。
3.1 使用Watch机制
Watcher 可以通过以下 API 注册:
getData(path, watch)
:监听节点数据变化
exists(path, watch)
:监听节点创建/删除/数据变化
getChildren(path, watch)
:监听子节点列表变化
使用示例:
3.2 Watch机制事件类型
Zookeeper 定义了以下几种 Watcher 事件类型:
- NodeCreated:节点被创建
- NodeDeleted:节点被删除
- NodeDataChanged:节点数据变更
- NodeChildrenChanged:子节点列表变更
- None:连接状态变化
从实现原理来看,接口类 Watcher 用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含
KeeperState
和 EventType
两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法: process(WatchedEvent event)
。KeeperState
和 EventType
的类型如下图所示:
4、ACL权限控制
ZooKeeper 作为一个分布式协调框架,其内部存储的都是一些关乎分布式系统运行时状态的元数据,尤其是一些涉及分布式锁、Master 选举和分布式协调等应用场景的数据,会直接影响基于 ZooKeeper 进行构建的分布式系统的运行状态。因此,如何有效地保障 ZooKeeper 中数据的安全,从而避免因误操作而带来的数据随意变更导致的分布式系统异常就显得格外重要了。
Zookeeper 的 ACL (Access Control List) 机制提供了对节点访问权限的控制,确保只有经过授权的客户端才能执行特定操作。
4.1 ACL格式分析
每个 Zookeeper 节点都有一个 ACL 列表,ACL 条目的格式为
scheme:id:permission
,由以下三部分组成:- Scheme:权限模式,指定认证方式
- ID:授权对象,指定应用该权限的实体
- Permission:权限,指定授予的权限
当创建新节点时,如果没有指定 ACL,Zookeeper 会使用默认 ACL。默认 ACL 通常是:world:anyone:cdrwa
(所有人拥有所有权限)。
4.1.1 Schema
权限模式用来确定权限验证过程中使用的检验策略。Zookeeper 支持多种权限认证方案:
- IP:使用 IP 地址来进行认证。例如配置了
ip:192.168.0.110
,即表示权限控制都是针对这个 IP 地址的。IP 模式也支持按照网段的方式进行配置,例如ip:192.168.0.1/24
表示针对 192.168.0.* 这个 IP 段进行权限控制。
- Digest:使用用户名:密码的 MD5 哈希进行认证。最常用的权限控制模式,使用
username:password
形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。当我们通过username:password
形式配置了权限标识后,我们可以先调用 ZooKeeper 的接口对其先后进行两次编码处理,分别是 SHA-1 算法加密和 BASE64 编码,具体实现是DigestAuthenticationProvider.generateDigest(String idPassword)
函数。
- World:数据节点的访问权限对所有用户开放。即所有用户都可以在不进行任何权限校验的情况下操作 ZooKeeper 上的数据。另外,World 模式也可以看作是一种特殊的 Digest 模式,它只有一个权限标识,即
world:anyone
。
- Super:在 Super 模式下,超级用户可以对任意 ZooKeeper 上的数据节点进行任何操作。Super 模式也可以看作一种特殊的 Digest 模式。
4.1.2 ID
授权对象指的是权限赋予的用户或一个指定实体,例如 IP 地址或是机器等。在不同的权限模式下,授权对象是不同的,下面列出了各个权限模式和授权对象之间的对应关系。

4.1.3 Permission
权限就是指那些通过权限检查后可以被允许执行的操作。在 ZooKeeper 中,所有对数据的操作权限分为以下五大类:
- CREATE(C):数据节点的创建权限,允许授权对象在该数据节点下创建子节点。
- DELETE(D):子节点的删除权限,允许授权对象删除该数据节点的子节点。
- READ(R):数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等。
- WRITE(W):数据节点的更新权限,允许授权对象对该数据节点进行更新操作。
- ADMIN(A):数据节点的管理权限,允许授权对象对该数据节点进行ACL相关的设置操作。
4.2 ACL相关命令
通过两种方式进行 ACL 的设置。
- 使用
create
命令在创建数据节点的同时设置 ACL 权限。
- 使用
setAcl
命令单独对已经存在的数据节点设置 ACL 权限。
4.2.1 create
设置ACL权限
命令格式如下:
假如我们要设置创建节点
/mcb/acl1
,并为用户 root:12345
设置 cdrwa
权限,首先调用 DigestAuthenticationProvider.generateDigest(String idPassword)
函数进行加密编码处理,如下所示:输出如下所示:
执行下面命令,在创建 ZNode 的时候设置 ACL 权限。
这时候我们如果直接查看节点
/mcb/acl1
信息,会报无权限错误可以先使用下面命令为当前会话添加用户
root:12345
再查看节点
/mcb/acl1
信息,执行成功4.2.2 setAcl
命令设置ACL权限
使用
setAcl
命令可以单独对已经存在的数据节点进行 ACL 设置,命令如下所示4.2.3 常用命令总结
ACL相关命令
命令 | 描述 | 语法 |
getAcl | 获取某个节点的acl权限信息 | getAcl <path> |
setAcl | 设置某个节点的acl权限信息 | setAcl <path> scheme:id:permissions |
addauth | 添加认证用户,注册时输入明文密码,登陆时也是使用明文密码,但zk存储时会对其先后进行SHA-1和BASE64两次编码处理,密码以加密的形式存储。 | addauth <scheme> user:password |
Schema示例
schema | id | 示例 |
world | 只有一个ID:anyone | setAcl <path> world:anyone:crdwa |
auth | id为username:passwd(需要提前创建) | setAcl <path> auth:username:password:crdwa |
digest | id为username:passwd(任意自定义),例如username:BASE64(SHA-1(password)) | setAcl <path> digest:username:1g4T1B5w+se9ntA6Ckp90uPaJ30=:crdwa |
ip | 通常是一个IP地址或是IP段,例如 192.168.0.110 或 192.168.0.1/24 | setAcl <path> ip:192.168.1.1:crdwa |
super | 与digest模式一致 | setAcl <path> super:username:1g4T1B5w+se9ntA6Ckp90uPaJ30=:crdwa |
5、常见问题
5.1 脑裂问题
脑裂(Split-Brain)是指在分布式系统中,由于网络分区或通信故障,导致集群被分割成多个独立运作的子集群,每个集群都可以对外提供服务,从而导致系统状态不一致的问题。
脑裂(Split-Brain)的发生原因:ZooKeeper 集群中各个节点间的网络通信不良时,容易出现脑裂问题:
- 集群中的部分 Follower 节点监听不到 Leader 节点的心跳,就会认为 Leader 节点出了问题;
- 这些监听不到 Leader 节点心跳的 Follower 节点就会选举出新 Leader 节点;
- 新的 Leader 和旧 Leader 节点 和各自的 Fllower 节点组成多个小集群。
解决脑裂问题:ZooKeeper 采用了Quorums(法定人数) 机制解决脑裂问题。Quorums 机制是指在一个分布式系统中,某项操作需要获得大多数节点的认可才能执行成功。Quorums 计算公式:
Quorum = floor(N/2) + 1
,其中 N 是集群节点总数。在 Zookeeper 集群中 Quorums 机制适用于以下场景:
- 领导者选举:只有获得 Quorum 数量认可的节点才能成为领导者。
- 数据一致性保证:任何提交的变更要等超过半数的节点达成一致才算提交成功。
比如:
- 3 个节点的集群中,Quorums = 2:集群可以容忍 (3 - 2 = 1) 个节点失败,这时候还能选举出 Leader,集群仍然可用;
- 4 个节点的集群中,Quorums = 3:集群同样可以容忍 1 个节点失败,如果 2 个节点失败,那整个集群就不可用了。
由此可以看出奇数 n 个节点和偶数 n + 1 个节点,可以容忍失败的节点数是一样,所以 ZooKeeper 一般都是部署奇数个节点(3 个及以上),减少资源浪费。
为什么 Quorums 过半机制可以解决脑裂问题呢?假如有五台服务器组成一个集群,部署在两个机房,其中一个机房三台机器,另外一个机房两台机器。

- 正常情况:3 个节点可以形成多数派,集群正常工作
- 网络分区:如果两个机房的网络出现问题,分成 3 节点和 2 节点两个分区:
- 第一个机房因为有三台机器,三台机器都投票给同一个机器,满足过半机制的节点数(n ≥ 3),所以这个机房可以选举出一个 Leader。
- 第二个机房只有两台机器,不满足过半机制,所以不能选举出 Leader。
- 整个集群仍然只有一个 Leader 对外提供服务,避免了脑裂问题。
- 完全隔离:
- 如果每个节点都与其他节点隔离
- 没有任何分区能达到多数派要求
- 整个集群将不可用,但不会出现不一致
5.2 羊群效应
羊群是一种很散乱的组织,平时在一起也是盲目地左冲右撞,但一旦有一只头羊动起来,其他的羊也会不假思索地一哄而上,全然不顾前面可能有狼或者不远处有更好的草。因此,羊群效应就是比喻人都有一种从众心理,指个体在群体中倾向于模仿多数人的行为或决策,即使这些行为可能缺乏理性或与自身判断相悖。
Zookeeper 的羊群效应是当某个特定事件发生时,大量客户端同时响应这个事件。例如:
- 大量客户端同时竞争锁:使用 Zookeeper 实现分布式锁时,当锁释放时,所有等待的客户端会同时尝试获取锁。
- 节点变化通知:当某个 ZNode 发生变化时,所有 watch 该节点的客户端都会收到通知。
羊群效应会产生大量客户端请求,造成很大的网络和性能开销,影响 Zookeeper 服务器的稳定性。
解决方案:
- 顺序节点:使用 Zookeeper 的顺序节点特性,每个客户端在选择 watch 节点的时候,只需要关注比自己序号小的那个节点,从而让客户端按顺序获取资源。

- 所有客户端都尝试去创建临时有序节点以获取锁
- 序号最小的临时有序节点获得锁
- 未获取到锁的客户端给自己的上一个临时有序节点添加监听
- 获得锁的客户端进行自己的操作,操作完成之后解锁,删除自己的临时有序节点
- 当下一个监听到自己的上一个临时有序节点释放了锁,尝试自己去加锁
- 之后剩下的客户端重复加锁和解锁的操作
最佳实践:
- 避免让大量客户端 watch 同一个节点
- 对于锁服务,考虑使用公平锁而非竞争锁
- Author:mcbilla
- URL:http://mcbilla.com/article/3751224c-b905-4104-aabb-de9c2d0a0258
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts