zookeeper
1、什么是分布式锁
- 分布式锁 是在 分布式系统 中,多个进程/服务 通过某种共享机制(如 ZooKeeper、Redis、etcd)协调访问 共享资源 的一种同步工具。
- 本质:避免在同一时刻多个进程同时访问某个共享资源
- 类比:类似于单机系统中的 synchronized 或 ReentrantLock,但作用范围扩展到跨机器、跨进程。
- 特性
- 互斥性 同一时间只有一个客户端能持有锁。
- 防死锁 即使客户端崩溃,锁也能自动释放(如通过超时或临时节点)。
- 高可用 锁服务本身必须是高可用的(如 Redis 集群、ZooKeeper 集群)。
- 可重入性(可选) 同一个客户端可多次获取同一把锁(如递归调用场景)。
- 公平性(可选) 按申请锁的顺序分配,避免饥饿问题。
常见实现方式对比
- ZooKeeper:临时顺序节点 + Watch。强一致、公平、自动释放。性能较低
- Redis:SETNX + 超时 + Lua。高性能、简单。非严格一致,锁超时问题
- etcd:Lease(租约) + Revision(版本)。高可用、强一致。复杂度较高
2、Zookeeper概述
- ZooKeeper 是一款分布式协调组件,专为分布式系统提供:统一配置管理、服务注册发现、集群选举、分布式锁、节点协调等能力
- 底层结构:树形目录结构(ZNode),类似文件系统。
- 存储限制:单个节点数据默认最大 1MB,只存配置、状态、地址等少量数据。
ZooKeeper在服务注册与发现场景中的应用
- 在微服务架构中,系统被拆分为多个独立服务(如订单服务、支付服务),每个服务可能部署多个实例。服务实例随时可能扩容、缩容或宕机。服务实例的IP和端口会随之发生变化。手动修改这些配置难以实现负载均衡和故障转移。
- 服务启动时会将自己的服务名、IP、端口注册到注册中心(zookeeper),调用方调用上面的服务时,会向注册中心查询可用的服务实例列表,通过负载均衡器的策略(如轮询、随机)选择一个实例发起调用。注册中心会定期检测服务实例的健康状态(如心跳检测),剔除不可用实例
ZooKeeper 集群角色
- Leader:处理所有写请求,负责事务提案的投票和提交(通过 ZAB 协议保证一致性)。
- Follower:处理读请求,参与 Leader 选举和写请求的投票。
- Observer:仅处理读请求,不参与投票(用于扩展读性能)。
- 端口作用
- 2181:客户端连接端口
- 2888:Leader 与 Follower 数据同步端口
- 3888:集群 Leader 选举通信端口
3、zookeeper选举
- ZK会以一主多从的形式部署。ZK为了保证数据的一致性,使用ZAB协议(Zookeeper Automic Brodcast)解决zk崩溃恢复和主从同步的问题
ZAB协议定义了以下几种节点状态
- Looking:选举状态
- Following:follower节点所处的状态
- Leading:leader节点所处的状态
- Observing:observer节点所处的状态
选举相关概念
- SID: Server ID 是 ZK 程序运行时读取 myid 加载出来的运行时编号,用于选举
- ZXID:事务ID。 ZXID是一个事务ID,用来标识一次服务器状态的变更。 在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和ZooKeeper服务器对于客户端“更新请求”的处理逻辑有关。
- Epoch: 每个Leader任期的代号。每投完一次票这个数据就会增加
首次选举
- 每个节点启动时默认投自己,每增加一个节点都会触发选举,各节点交换选票信息,选票会给到myid最大的节点,超过集群半数票数当选 Leader。
非首次选举
-
以下两种情况下,就开始Leader选举:
1、服务器初始化启动
2、集群运行期间无法和Leader保持连接的服务器会发起选举 -
当一台机器进入Leader选举流程时,当前集群可能处于以下两种状态:
1、集群中已经存在一个Leader,发起选举的机器只是不能和leader通信了。
当机器试图去选举Leader时,会被告知当前服务器的Leader信息,不能发起选举,只能尝试和leader建立连接
2、leader故障,集群中确实不存在Leader。剩余服务器会发起选举。选举规则:EPOCH大的直接当选leader,EPOCH相同,zxid大的当选leader,zxid相同,sid大的当选leader
4、zookeeper的配置文件
- 初次使用zookeeper,需要将%ZK_HOME%/conf目录下的zoo_sample.cfg文件重命名为zoo.cfg
- 在 ZooKeeper 的设计中,集群中所有机器上zoo.cfg文件的内容都应该是一致的。因此最好使用SVN或是GIT把此文件管理起来,确保每个机器都能共享到一份相同的配置
- 查看更多配置参数可以访问官网
https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_configuration
# 基础配置
tickTime=2000 # 是ZK的基本时间单位,它用于调节心跳和暂停时间(毫秒)
initLimit=10 # 初始连接时,Follower与Leader同步数据的超时时间(tickTime的倍数)
syncLimit=5 # Follower与Leader心跳超时时间(tickTime的倍数)
clientPort=2181 # 客户端连接端口
maxClientCnxns=60 # 单个客户端允许的最大客户端连接数
clientPortAddress=0.0.0.0 # 绑定IP(默认0.0.0.0表示监听所有接口)
# 数据与日志
dataDir=/var/lib/zookeeper/data # 数据目录(必须配置,存储快照和事务日志)
dataLogDir=/var/lib/zookeeper/log # 事务日志目录(可选,单独设置可提高性能)
# 集群配置(示例:3节点集群)
server.1=zk1.example.com:2888:3888 # 节点1:2888用于数据同步,3888用于选举
server.2=zk2.example.com:2888:3888 # 节点2
server.3=zk3.example.com:2888:3888 # 节点3
peerType=observer # 是否启用Observer模式(不参与投票)
# 高级调优
autopurge.snapRetainCount=3 # 保留的快照文件数量(默认3)
autopurge.purgeInterval=24 # 清理旧快照和日志的间隔(小时)
preAllocSize=65536 # 事务日志预分配大小(字节,默认64KB)
snapCount=100000 # 触发快照的事务操作次数阈值
maxSessionTimeout=40000 # 最大会话超时时间(毫秒,默认40秒)
minSessionTimeout=4000 # 最小会话超时时间(毫秒,默认4秒)
# 安全配置(可选)
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider # 启用SASL认证
requireClientAuthScheme=sasl # 强制客户端使用SASL认证
- server.1=zk1:2888:3888
- 1:Server ID,用来标识该机器在集群中的机器序号,与myid文件内容一致,范围1~255
- zk1:服务器地址或域名
- 2888:Leader和Follower通信端口
- 3888:选举端口
zookeeper自带脚本
- 在 ZooKeeper 的%ZK_HOME%/bin 目录下有几个有用的脚本,每个命令都有.sh和.cmd两种格式
- zkCleanup:清理 ZooKeeper 历史数据,包括事务日志文件和快照数据文件
- zkCli:ZooKeeper 的一个简易客户端
- zkEnv:设置 ZooKeeper 的环境变量
- zkServer:ZooKeeper 服务器的启动、停止和重启脚本
5、ZooKeeper 数据结构
Znode
- zookeeper没有传统文件系统中目录和文件等相关概念,而是使用了其特有的“数据节点”概念,我们称之为 ZNode。
- ZNode是 ZooKeeper 中数据的最小单元,每个 ZNode 上都可以保存数据,同时还可以挂载子节点, 因此构成了一个层次化的命名空间,称之为树。
- ZooKeeper 的数据存储采用 树形命名空间(类似文件系统)。路径:以 / 分隔(如 /services/order)。
事务ID
- 在 ZooKeeper 中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新、客户端会话创建与失效等操作。
- 对于每一个事务请求, ZooKeeper都会为其分配一个全局唯一的事务 ID,用 ZXID 来表示,是一个全局单调递增的 64 位数字。 每一个 ZXID 对应一次更新操作,从这些ZXID 中可以间接地识别出更新操作请求的全局顺序
zk的数据持久化
- zk 的数据是运行在内存中,zk 提供了两种持久化机制:
事务日志
- zk 把执行的命令以日志形式保存在 dataLogDir 指定的路径中的文件中(如果没有指定 dataLogDir,则按 dataDir 指定的路径)。
数据快照
- zk 会在一定的时间间隔内做一次内存数据的快照,把该时刻的内存数据保存在快照文件中。
- zk 通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样的恢复速度更快。
znode节点类型
- 在 ZooKeeper 中,节点类型可以分为持久节点( PERSISTENT)、临时节点( EPHEMERAL)和顺序节点( SEQUENTIAL)三大类,具体在节点创建过程中, 通过组合使用,可以生成以下四种组合型节点类型
- zk3.5开始,新增container节点、TTL节点
- 每个 ZNode 可存储少量数据(默认上限 1MB),通常用于存状态或配置(如服务地址)
持久节点
该数据节点创建后会一直存在,即使客户端断开连接仍存在,直到有删除操作来主动清除这个节点。
持久顺序节点
ZooKeeper 会自动为给定节点名加上一个数字后缀,作为一个新的、 完整的节点名。另外需要注意的是,这个数字后缀的上限是整型的最大值
临时节点
临时节点的生命周期和客户端的会话绑定在一起,如果客户端会话失效,那么这个节点就会被自动清理掉。注意,这里提到的是客户端会话失效,而非 TCP 连接断开。ZooKeeper 规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。
临时顺序节点
临时顺序节点的基本特性和临时节点也是一致的, 同样是在临时节点的基础上,添加了顺序的特性
Container节点
当容器节点下没有子节时,容器节点会被zk定期删除(60S)
TTL节点
指定节点到期时间,到期后被zk删除
示例数据结构

ZNode 状态信息
cZxid = 0x50000000c
ctime = Wed Apr 15 08:56:33 UTC 2026
mZxid = 0x500000017
mtime = Wed Apr 15 09:49:08 UTC 2026
pZxid = 0x50000001e
cversion = 2
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 2
numChildren = 2
事务 ID 类(ZXID)
- cZxid = 0x50000000c
- Create ZXID:创建该节点时的全局事务 ID。
- 一旦节点创建,此值永久不变。
- 0x50000000c 是 64 位十六进制数:高 32 位表示 Leader 任期(Epoch),低 32 位是事务序号。
- mZxid = 0x500000017
- Modified ZXID:节点 ** 最后一次被修改(数据更新)** 时的事务 ID。
- 每次 set 命令修改数据,该值都会更新。
- pZxid = 0x50000001e
- Children ZXID
- 直接子节点列表最后一次变更的事务 ID。
- 触发条件:新增 / 删除直接子节点(create /parent/child、delete /parent/child)。
- 不触发:修改子节点的数据内容、孙子节点变更。
时间戳类
- ctime:节点创建时间。
- mtime:节点最后一次数据修改时间。
版本号(乐观锁 CAS)
- cversion:直接子节点变更次数。每创建 / 删除一个直接子节点,cversion + 1
- dataVersion:节点自身数据被修改的次数。
- aclVersion:节点权限(ACL)被修改的次数。
节点属性
- ephemeralOwner:创建该临时节点的会话的session ID。0表示持久节点,非0为临时节点
- dataLength:节点存储数据的字节长度。
- numChildren:当前节点拥有的直接子节点数量。
6、watcher机制
- 一个典型的发布/订阅模型系统定义了一种一对多的订阅关系,能够让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。在 ZooKeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能
- ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
watcher机制工作流程简单概述
- 客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的WatchManager 中。当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 中取出对应Watcher 对象来执行回调逻辑
- ZooKeeper 的 Watcher 机制,总的来说可以概括为以下三个过程:客户端注册 Watcher、服务端处理 Watcher 和客户端回调 Watcher
Watcher 接口
- 在 ZooKeeper 中,接口类 Watcher 用于表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包含 KeeperState 和 EventType 两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)。
Watcher 通知状态(KeeperState)与事件类型(EventType)一览

- NodeChildrenChanged 事件会在数据节点的子节点列表发生变更的时候被触发,即新增子节点或删除子节点会触发事件,而子节点内容的变化是不会触发这个事件的
Watcher 特性总结
ZooKeeper 的 Watcher 具有以下几个特性
- 一次性
- 无论是服务端还是客户端,一旦一个 Watcher 被触发,ZooKeeper 都会将其从相应的存储中移除。再次使用需要重新注册。这样的设计有效地减轻了服务端的压力(如果注册一个 Watcher 之后一直有效,那么,针对那些更新非常频繁的节点,服务端会不断地向客户端发送事件通知,这无论对于网络还是服务端性能的影响都非常大)。
- 客户端串行执行
- 客户端 Watcher 回调的过程是一个串行同步的过程,保证了顺序
- 轻量
- WatchedEvent 是 ZooKeeper 整个 Watcher 通知机制的最小通知单元,这个数据结构中只包含三部分内容:通知状态、事件类型和节点路径。也就是说, Watcher通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。
- 例如针对 NodeDataChanged 事件, ZooKeeper 的 Watcher 只会通知客户端指定数据节点的数据内容发生了变更, 而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据。这也是 ZooKeeper 的 Watcher机制的一个非常重要的特性。
- 另外,客户端向服务端注册 Watcher 的时候,并不会把客户端真实的Watcher 对象传递到服务端,仅仅只是在客户端请求中使用 boolean 类型属性进行了标记,同时服务端也仅仅只是保存了当前连接的 ServerCnxn 对象。
7、ACL
- ZooKeeper 提供了一套完善的 ACL( Access Control List)权限控制机制来保障数据的安全
- ZooKeeper 的 ACL 权限控制和 Unix/Linux 操作系统中的 ACL 有一些区别,可以从三个方面来理解 ACL 机制:权限模式( Scheme)、授权对象(ID)和权限( Permission)
- 通常使用scheme : id : permission来标识一个有效的 ACL 信息
四种权限模式/对象
world:anyone:所有人开放(默认)ip:192.168.1.10:IP 白名单digest:user:pwd:账号密码认证super:超级管理员,拥有全部权限

六种权限
- CREATE( C) :数据节点的创建权限
- DELETE( D) :子节点的删除权限
- READ( R) :数据节点的读取权限,允许授权对象访问该数据节点并读取其数据内容或子节点列表等。
- WRITE( W) :数据节点的更新权限
- ADMIN( A) :数据节点的管理权限,允许授权对象对该数据节点进行 ACL 相关的设置操作。
ACL管理
- 通过 zkCli 脚本登录 ZooKeeper 服务器后, 可以通过两种方式进行 ACL 的设置。一种是在数据节点创建的同时进行 ACL 权限的设置,另一种是使用setAcl 命令单独对已经存在的数据节点进行 ACL 设置创建数据节点的同时设置 ACL
# 加密得到wAs/IxL3XzKzH5Q9nV0dKpV9M4w=
echo -n "foo:123456" | openssl dgst -binary -sha1 | base64
create /t 1 digest:foo:wAs/IxL3XzKzH5Q9nV0dKpV9M4w=:cdrwa
addauth digest foo:123456
get /t
super模式的用法
- 如果一个持久数据节点包含了 ACL 权限控制,而其创建者客户端已经退出或已不再使用,那么这些数据节点该如何清理呢?这个时候,就需要在 ACL 的 Super 模式下,使用超级管理员权限来进行处理
zookeeper的典型应用场景及实现
数据发布/订阅
- 数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 的一个或一系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新
- 发布/订阅系统一般有两种设计模式,分别是推( Push)模式和拉( Pull)模式。在推模式中,服务端主动将数据更新发送给所有订阅的客户端; 而拉模式则是由客户端主动发起请求来获取最新数据,通常客户端都采用定时进行轮询拉取的方式。
- ZooKeeper 采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher 事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。
实现
- 以数据库切换场景为例,大致流程如下:
配置存储
- 在进行配置管理之前,首先我们需要将初始化配置存储到 ZooKeeper 上去。一般情况下,我们可以在 ZooKeeper 上选取一个数据节点用于配置的存储,例如:/app1/database_config(以下简称“配置节点”)
![image]()
# 数据库配置
dbcp.driverClassName=com.mysql.jdbc.Driver
dbcp.dbJDBCUrl=jdbc:mysql://1.1.1.1:3306/taokeeper
dbcp.characterEncoding=GBK
dbcp.username=xiaoming
dbcp.password=123456
配置获取
- 集群中每台机器在启动初始化阶段,首先会从上面提到的 ZooKeeper 配置节点上读取数据库信息,同时,客户端还需要在该配置节点上注册一个数据变更的 Watcher监听,一旦发生节点数据变更,所有订阅的客户端都能够获取到数据变更通知。
配置变更
- 在系统运行过程中, 可能会出现需要进行数据库切换的情况,这个时候就需要进行配置变更。借助 ZooKeeper,我们只需要对 ZooKeeper 上配置节点的内容进行更新,ZooKeeper 就能够帮我们将数据变更的通知发送到各个客户端,每个客户端在接收到这个变更通知后, 就可以重新进行最新数据的获取。
负载均衡
- 负载均衡( Load Balance)用来对多个计算机、网络连接、CPU、磁盘驱动器或其他资源进行分配负载,以达到优化资源使用、最大化吞吐率、最小化响应时间和避免过载的目的。通常负载均衡可以分为硬件(F5)和软件负载均衡两类
- 在分布式系统中,为了保证系统的高可用性,通常采用副本的方式来对数据和服务进行部署。而对于消费者而言, 则需要在这些对等的服务提供方中选择一个来执行相关的业务逻辑,其中比较典型的就是 DNS 服务。
实现
- 以动态DNS服务为例
![image]()
- 动态 DNS 系统的架构体系中几个比较重要的组件及其职责。
- Register 集群负责域名的动态注册。
- Dispatcher 集群负责域名解析。
- Scanner 集群负责检测以及维护服务状态(探测服务的可用性、 屏蔽异常服务节点等)。
- SDK 提供各种语言的系统接入协议,提供服务注册以及查询接口。
- Monitor 负责收集服务信息以及对 DDNS 自身状态的监控。
- Controller 是一个后台管理的 Console,负责授权管理、流量控制、静态配置服务和手动屏蔽服务等功能,另外,系统的运维人员也可以在上面管理 Register、Dispatcher 和 Scanner 等集群。
域名注册
- 域名注册主要是针对服务提供者来说的。域名注册过程可以简单地概括为:每个服务提供者在启动的过程中,都会把自己的域名信息注册到 Register 集群中去。
- 服务提供者通过 SDK 提供的 API 接口,将域名、IP 地址和端口发送给 Register集群。例如, A 机器用于提供 serviceA.xxx.com,于是它就向 Register 发送一个“域名→IP:PORT” 的映射:“serviceA.xxx.com → 192.168.0.1:8080”。
- Register 获取到域名、 IP 地址和端口配置后,根据域名将信息写入相对应的ZooKeeper 域名节点中。
域名解析
- 域名解析是针对服务消费者来说的,正好和域名注册过程相反:服务消费者在使用域名的时候,会向 Dispatcher 发出域名解析请求。
- Dispatcher 收到请求后,会从 ZooKeeper上的指定域名节点读取相应的 IP:PORT 列表, 通过一定的策略选取其中一个返回给前端应用。
域名探测
- 域名探测是指 DDNS 系统需要对域名下所有注册的 IP 地址和端口的可用性进行检测,俗称“健康度检测”。
- 每个服务提供者都会定时向 Scanner 汇报自己的状态。Scanner 会负责记录每个服务提供者最近一次的状态汇报时间,一旦超过 5 秒没有收到状态汇报,那么就认为该 IP 地址和端口已经不可用,于是开始进行域名清理过程。
- 在域名清理过程中, Scanner 会在 ZooKeeper 中找到该域名对应的域名节点,然后将该 IP地址和端口配置从节点内容中移除。
命名服务
- 命名服务(Name Service)也是分布式系统中比较常见的一类场景, 在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等——这些我们都可以统称它们为名字( Name),其中较为常见的就是一些分布式服务框架(如 RPC、 RMI)中的服务地址列表, 通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等。
- UUID 是一个很好的生成全局唯一 ID 的方式,例如“e70f1357-f260-46ff-a32d-53a086c57ade”。 缺点是长度过长,存储一个 UUID 需要花费更多的空间。含义不明,根据这个字符串,开发人员从字面上基本看不出任何其表达的含义,这将会大大影响问题排查和开发调试的效率。
实现
- 通过调用 ZooKeeper 节点创建的 API 接口可以创建一个顺序节点,并且在 API 返回值中会返回这个节点的完整名字。利用这个特性,我们就可以借助 ZooKeeper 来生成全局唯一的 ID 了
- 所有客户端都会根据自己的任务类型,在指定类型的任务下面通过调用create()接口来创建一个顺序节点,例如创建“job-” 节点。
- 节点创建完毕后, create()接口会返回一个完整的节点名,例如“job-0000000003”。
- 客户端拿到这个返回值后,拼接上 type 类型,例如“type2-job-0000000003”,这就可以作为一个全局唯一的 ID 了
![image]()
分布式协调/通知
- 对于一个在多台机器上部署运行的应用而言,通常需要一个协调者( Coordinator)来控制整个系统的运行流程。引入协调者,便于将分布式协调的职责从应用中分离出来, 从而可以大大减少系统之间的耦合性,而且能够显著提高系统的可扩展性。
- ZooKeeper 中特有的 Watcher 注册与异步通知机制,能够很好地实现分布式环境下不同机器,甚至是不同系统之间的协调与通知,从而实现对数据变更的实时处理。基于ZooKeeper 实现分布式协调与通知功能,通常的做法是不同的客户端都对 ZooKeeper 上同一个数据节点进行 Watcher 注册,监听数据节点的变化(包括数据节点本身及其子节点),如果数据节点发生变化, 那么所有订阅的客户端都能够接收到相应的 Watcher 通知,并做出相应的处理。




浙公网安备 33010602011771号