定义
zookeeper
是一种分布式组件
,是一种分布式协调服务
,用于管理大型主机
。在分布式环境中协调
和管理服务
是一个复杂的过程。zookeeper
通过其简单的架构
和API
解决了这个问题。zookeeper
允许开发人员
专注于核心应用程序逻辑
,而不必担心
应用程序的分布式问题
。zookeeper
是apache
的一个子项目
三种角色
- leader:负责进行投票选举的发起和决议,更新系统状态,能写也能读
- learner:包括跟随者follower和观察者observer,后面主要以follow为主
follower用于接收客户端请求并向客户端返回结果,将写请求转给leader,能读,也能参数投票选举leader
observer可以接收客户端连接,将写请求转给leader,只负责读,不参与leader的选举
数据结构
- 节点是`树状结构`(`znode`)
- `zk`中的数据是保存在`节点上`的(也就是`目录`,也叫`znode`),多个`znode`之间构成一棵树状的目录结构,也很像文件系统的目录。各个节点都是有层级的子节点
- 这样的层级结构,让每节点拥有唯一的路径,对不同信息作出清晰的隔离。
- 不同于树的节点是:`znode`的引用方式是路径引用,类似文件绝对路径,这样的层级结构,让每一个`znode`节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离
zk = 树形文件系统 + 节点通知机制
- zk维护一个类似文件系统的数据结构:
每个节点/目录(znode)都可以被监听
,也可以发通知
,每个节点/目录(znode)中都可以存储数据
。
节点类型
- `持久化目录节点`:客户端与zookeeper断开连接后,该节点依旧存在
- `持久化顺序编号目录节点`:客户端与zookeeper断开连接后,该节点依旧存在,只是zk给该节点名称进行了顺序编号
- `临时目录节点`:客户端与zookeeper断开连接后,该节点被删除
- `临时顺序编号目录节点`:客户端与zookeeper断开连接后,该节点被删除,只是zk给该节点名称进行了顺序编号
持久化机制
zk的数据是运行在
内存
中,zk
提供了两种持久化机制
- 事务日志:zk把`执行的命令`以`日志形式`保存在`dataLogDir`指定路径的文件中(如果没有指定`dataLogDir`,则按`dataDir`指定的路径)
- 数据快照:zk会在一定的`时间间隔内`做一个`内存数据的快照`,把该时刻的内存数据保存在`快照文件`中
#zk通过两种形式的持久化,恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样的恢复速度更快
常用操作
cd /opt/zk/apache-zookeeper-3.8.4-bin/bin/
启动 # ./zkServer.sh start
停止 # ./zkServer.sh stop
重启 # ./zkServer.sh restart
查看状态 # ./zkServer.sh status
[root@VM-0-177-centos bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /opt/zk/apache-zookeeper-3.8.4-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: follower
#登录连接zk命令格式
cd /opt/zk/apache-zookeeper-3.8.4-bin/bin/
./zkCli.sh # 客户端连接zk,默认什么都不指定,连接2181端口,如果时集群随便连一台就行
./zkCli.sh -server 10.0.0.177:2181 # 客户端连接zk,指定ip和端口,如果是集群随便连接一台就行
[root@VM-0-139-centos bin] ./zkCli.sh -server 10.0.0.177:2181
[zk: 10.0.0.177:2181(CONNECTED) 1] ls / # 查看根节点下有哪些节点
[zookeeper]
[zk: 10.0.0.177:2181(CONNECTED) 2] get / # 获取根节点下的数据
[zk: 10.0.0.177:2181(CONNECTED) 3] create /testZnode "test data" #创建节点并赋值。在/节点下创建一个子节点testZnode,该子节点的数据是:test data(默认创建的节点都是持久性的)
Created /testZnode
[zk: 10.0.0.177:2181(CONNECTED) 4] ls /
[testZnode, zookeeper]
[zk: 10.0.0.177:2181(CONNECTED) 5] get /testZnode # 获取子节点testZnode的数据
test data
[zk: 10.0.0.177:2181(CONNECTED) 6] set /testZnode "test2 data2" # 设置子节点testZnode的数据
[zk: 10.0.0.177:2181(CONNECTED) 7] get /testZnode
test2 data2
[zk: 10.0.0.177:2181(CONNECTED) 17] create -s /testxuhaoNode "xuhao data" # 在/节点下创建一个带序号的持久化节点并设置数据,-s带序号
Created /testxuhaoNode0000000001
[zk: 10.0.0.177:2181(CONNECTED) 18] ls /
[testZnode, testxuhaoNode0000000001, zookeeper]
[zk: 10.0.0.177:2181(CONNECTED) 19] get /testxuhaoNode0000000001
xuhao data
[zk: 10.0.0.177:2181(CONNECTED) 20] create -e /tmpNode "tmp data" # -e创建一个临时节点并设置数据
Created /tmpNode
[zk: 10.0.0.177:2181(CONNECTED) 21] ls /
[testZnode, testxuhaoNode0000000001, tmpNode, zookeeper]
[zk: 10.0.0.177:2181(CONNECTED) 22] get /tmpNode
tmp data
[zk: 10.0.0.177:2181(CONNECTED) 23] create -e -s /tmpxuhaoNode "tmpxuhao dataxuhao" # -s带序号
Created /tmpxuhaoNode0000000003
[zk: 10.0.0.177:2181(CONNECTED) 24] ls /
[testZnode, testxuhaoNode0000000001, tmpNode, tmpxuhaoNode0000000003, zookeeper]
[zk: 10.0.0.177:2181(CONNECTED) 25] get /tmpxuhaoNode0000000003
tmpxuhao dataxuhao
[zk: 10.0.0.177:2181(CONNECTED) 8] create /testZnode/test1 # 创建子节点
Created /testZnode/test1
[zk: 10.0.0.177:2181(CONNECTED) 13] set /testZnode/test1 "dasta"
[zk: 10.0.0.177:2181(CONNECTED) 14] get /testZnode/test1
dasta
[zk: 10.0.0.177:2181(CONNECTED) 26] stat /testZnode # 查看节点的状态
cZxid = 0x100000002
ctime = Mon Jun 30 20:50:31 CST 2025
mZxid = 0x100000003
mtime = Mon Jun 30 20:54:17 CST 2025
pZxid = 0x100000004
cversion = 1
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 11
numChildren = 1
[zk: 10.0.0.177:2181(CONNECTED) 31] deleteall /testZnode # 递归删除所有节点#;也可以用delete,但是不能递归删除,只能一级一级删除
应用
1、分布式协调组件
协调:
在某种环境下,某些内容不均衡,出现了矛盾,需要有一个协调者。在分布式系统中,需要有zookeeper作为分布式协调组件,协调分布式系统中的状态。
- 分布式协调主要使用的是:zookeeper中的`watch监听注册`和`异步通知的机制实现`
不同的服务器端(相当于zk的客户端)都对zookeeper上同一个数据节点进行watch注册,客户端监听数据节点的变化(包括:数据节点本身及其子节点),如果数据节点发生变化,那么所有订阅的客户端都能够接受到相应的watch通知,并做出相应的处理。
#示例
用户访问nginx,nginx做了负载均衡,后面两个节点服务是一模一样的,比如目前后面两个节点的数据表示都是true,当需要修改一下表示为false,这次由nginx发出的请求恰好到了第2个节点,那么在第2个节点修改了标识为false,此时第1个节点的标识还是true,就导致了两个节点是数据不一致了。但是如果加上了zookeeper。分布式协调中心zokeeper就可以帮我们去完成这样一个协调的工作,在水中创建一个临时节点,这两个服务都连接zk,都对zookeeper上同一个数据节点进行watch监听注册,一旦第2个节点是false,zookeeper就能监听的到,然后通知第1个节点去修改这个标识,改成和第2个节点一样。这就保证了数据的一致性,zookeeper就起到了协调的作用
2、作为分布式锁
zk在实现分布式锁上,可以做到强一致性,也就是顺序一致性
在分布式环境下,保护跨主机、跨进程、跨网络的共享资源,实现互斥访问(只有拿到锁的客户端才能操作zk中的数据),保证一致性。
#示例
- 获取锁时,在zookeeper中的locker节点下创建`顺序临时节点`(`序号最小的获得锁`)
- `释放锁时`,自动`删除临时节点`。
- 过程:
`每个服务`都连接上`zookeeper`,`每个服务`(相当于`zk客户端`)请求来到`zk`之后,都会在`zk`的`locker节点`下临时创建一个`顺序节点`,`序号最小`的那个`顺序节点`就会获取到`锁`,然后做一些`业务的处理`,比如`修改数据等等`,做完业务处理后,会`释放锁`,释放锁时,就会把相应`临时节点删除`。比如`node1临时节点删除`了,`node2`就变成了`序号最小的顺序节点`,`node2就会获取到锁`,再做相应的业务处理,以此类推,其实保证了`顺序一致性`。
3、无状态化的实现
类似于redis作为共享会话,解决http无状态的场景
- 统一配置管理,统一存放管理多个服务共用的配置文件
#示例
- 比如有`3个节点`的`登录系统`,也是一模一样的系统有3个节点,`因为是登录系统,需要用户登录,那么用户登录的信息应该放在哪一台上呢?`
- `登录信息`肯定不能放在`某一台上`,如果`下次登录到另一台`就显示用户`没有登录`了,所以我们找一个`协调组件`,把`公用的登录信息`统一放在`zookeeper中`,`zk`作为一个存放`公共数据的中心`,这样对`任何节点来讲`,它不需要关心`登录的状态`,因为`登录的状态信息`在`zookeeper中维护`了,就实现了`这几个登录系统的无状态化`
4、zookeeper可以作为注册中心
所有服务启动的时候
,都注册
到zookeeper
中
部署
- 单机安装模式
- 集群安装模式(分布式多节点的集群模式)
单机
zk官网:https://zookeeper.apache.org/releases.html
# 下载安装包
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz
# 创建安装目录
mkdir /opt/zk
#安装java环境
yum install java-1.8.0-openjdk -y
java -version
# 解压到安装目录下
tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz -C /opt/zk/
# 创建zk的数据目录
mkdir /opt/zk/data
# zk的配置文件(文件名必须为zoo.cfg)
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
dataDir=/opt/zk/data # 配置zk的数据目录
clientPort=2181 # 配置zk服务端的端口
[root@VM-0-139-centos bin]# pwd
/opt/zk/apache-zookeeper-3.8.4-bin/bin
[root@VM-0-139-centos bin]# ./zkServer.sh start # 启动zk
ZooKeeper JMX enabled by default
Using config: /opt/zk/apache-zookeeper-3.8.4-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
[root@VM-0-139-centos bin]# ./zkServer.sh status # 查看zk的状态
ZooKeeper JMX enabled by default
Using config: /opt/zk/apache-zookeeper-3.8.4-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: standalone # 单机模式
[root@VM-0-139-centos bin]# ./zkServer.sh stop # 停止zk
[root@VM-0-139-centos bin]# ./zkServer.sh restart # 重启zk
# 检测zk的端口
[root@VM-0-139-centos bin]# netstat -tunalp | grep 2181
tcp6 0 0 :::2181 :::* LISTEN 635179/java
配置文件zoo.cfg
[root@VM-0-139-centos conf]# pwd
/opt/zk/apache-zookeeper-3.8.4-bin/conf
[root@VM-0-139-centos conf]# cat zoo.cfg
tickTime=2000 #2000毫秒,follower和leader之间心跳检测的间隔时间(默认2000毫秒)
initLimit=10 #允许follower初始化连接到leader的最大时长,它表示tickTime的时间倍数,即 initLimit * tickTime。也可以说是tickTime的数量
syncLimit=5 #允许follower与leader数据同步的最大时长,它表示tickTime的时间倍数,即 syncLimit * tickTime。也可以说tickTime的数量
dataDir=/opt/zk/data # zk的数据目录
dataLogDir=../log # zk的日志目录
clientPort=2118 # zk服务端的端口
maxClientCnxns=2000 # zk的最大并发连接数
autopurge.snapRetainCount=50 # zk保存的数据快照,超过的会被清除
autopurge.purgeInterval=1 # 1h,自动触发快照清除的时间间隔(单位:小时),默认为0,表示不自动清除
集群
#机器规划
10.0.0.177 zk1 端口:2181
10.0.0.146 zk2 端口:2181
10.0.0.139 zk3 端口:2181
# 3台机器都需要安装jdk环境
yum install java-1.8.0-openjdk -y
java -version
# 177上操作
mkdir /opt/zk/
tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz -C /opt/zk/
mkdir /opt/zk/data
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
.....
dataDir=/opt/zk/data
clientPort=2181
server.1=10.0.0.177:2888:3888
server.2=10.0.0.146:2888:3888
server.3=10.0.0.139:2888:3888
# server.1或.2或.3
# server时固定格式
# .1 .2 .3是zk集群中各zk的编号(数字自定义)
# 要想分清哪个对应哪个编号,需要在各zk节点下data数据存储目录中添加一个myid的文件,将对应的数字编号1或2或3写入myid文件即可
# 2888:表示follower和leader的内部通信端口,默认2888
# 3888:表示选举的端口,默认3888
echo 1 > /opt/zk/data/myid
cd bin/
./zkServer.sh start # 启动zk
./zkServer.sh status # 查看zk状态和角色
-----------------------------------------------------------
# 146上操作
mkdir /opt/zk/
tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz -C /opt/zk/
mkdir /opt/zk/data
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
.....
dataDir=/opt/zk/data
clientPort=2181
server.1=10.0.0.177:2888:3888
server.2=10.0.0.146:2888:3888
server.3=10.0.0.139:2888:3888
# server.1或.2或.3
# server时固定格式
# .1 .2 .3是zk集群中各zk的编号(数字自定义)
# 要想分清哪个对应哪个编号,需要在各zk节点下data数据存储目录中添加一个myid的文件,将对应的数字编号1或2或3写入myid文件即可
# 2888:表示follower和leader的内部通信端口,默认2888
# 3888:表示选举的端口,默认3888
echo 2 > /opt/zk/data/myid
cd bin/
./zkServer.sh start # 启动zk
./zkServer.sh status # 查看zk状态和角色
----------------------------------------------------------------
# 139上操作
mkdir /opt/zk/
tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz -C /opt/zk/
mkdir /opt/zk/data
cp zoo_sample.cfg zoo.cfg
vim zoo.cfg
.....
dataDir=/opt/zk/data
clientPort=2181
server.1=10.0.0.177:2888:3888
server.2=10.0.0.146:2888:3888
server.3=10.0.0.139:2888:3888
# server.1或.2或.3
# server时固定格式
# .1 .2 .3是zk集群中各zk的编号(数字自定义)
# 要想分清哪个对应哪个编号,需要在各zk节点下data数据存储目录中添加一个myid的文件,将对应的数字编号1或2或3写入myid文件即可
# 2888:表示follower和leader的内部通信端口,默认2888
# 3888:表示选举的端口,默认3888
echo 3 > /opt/zk/data/myid
cd bin/
./zkServer.sh start # 启动zk
./zkServer.sh status # 查看zk状态和角色
特性
基本特性
1. 写操作严格有序
- 所有写操作按请求顺序执行,同一时间并发修改同一个Znode时,只有一个请求能成功
- zk收到多个客户端写请求时,会将它们放到一个队列里面,因为队列时先进先出,有一定顺序的,所以zk也是严格有序的
2. watch监听机制
- zk支持推拉结合的发布订阅模式,可以在`读取某个节点数据的同时`对`该节点设置监视器`,以监视从读取那一刻起该节点后续发生的数据变更。
- 客户端可以时刻监听zk的某一个节点,可监听`节点的状态和数据的变更`,如果`zk中节点的状态或数据发生了变更`,zk会通过`原子广播`通知到`监听方`(监听zk节点的客户端),客户端就可以根据监听到节点的变化,从而做对应的操作(如数据更新等)
3. zk的通知机制
- 典型的应用就是:用zk做`配置管理`,多个节点可以用zk存放`公共的配置文件`。客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变,被删除,子目录节点增加删除)时,zk会通知客户端。
- 也就是:当客户端注册了某一个目录节点,对某个节点进行监控,只要有人改了这个节点,所有监听该节点的客户端都能收到通知
4. 临时节点
- `Znode的生命周期`默认是从`创建那一刻起一直存在直到被删除`
- `默认创建`的节点是`永久节点`,如果不手动删除会一直存在。
同时`zk`也支持创建`临时节点`,临时节点生命周期与session会话一致,`会话中断,节点也随之被删除`
集群特性
- 一个leader,多个follower组成的集群
- 全局数据一致性:每个zk保存一份相同的数据副本,client无论连接到哪个zk,数据都是`一致的`
- 分布式读写,更新请求转发,由leader实施:
读的时候各个节点都能读,但是写的时候只能leader写数据,即使follower接受到写请求,也会交给leader来写
- 数据更新原子性:一次数据更新要么成功,要么失败,不能一半成功,一半失败
- 数据实时性:在一定时间范围内,client能读取到最新数据
watch监听机制与会话
watch监听机制
#它允许用户在指定节点上针对感兴趣的事件注册监听,当事件发生变化时,监听器会被触发,将事件信息推送给客户端
- 可以在读取某个节点数据的同时对该节点设置监视器,以监视从读取那一刻起该节点后续发生的数据变更。
- 客户端可以时刻监听zk的某一个节点,可监听节点的状态和数据的变更,如果zk中节点的状态或数据发生了变更,zk会通过原子广播通知到监听方(监听zk节点的客户端),客户端就可以根据监听到节点的变化,从而做对应的操作(如数据更新等)
会话
- zk客户端通过`tcp长连接`连接到zk集群,`会话session`从第一次连接开始就已经建立,之后通过tcp的心跳检测机制来保持有效的会话状态。
- 通过这个连接,客户端可以发送请求并接受响应,同时也可以接受到`watch事件的通知`。
- 当由于网络故障或客户端主动断开等原因,导致连接断开,此时只要在`会话超时时间之内重新建立连接`,则之前`创建的会话依然有效`。
zab协议(原子广播协议)
这个协议解决了zk集群的崩溃恢复(选主)和主从数据同步的问题
zk使用单一的主进程来接收并处理客户端的所有事务请求
采用原子广播协议将数据状态的变更以事务的形式广播到所有的follower进程上去。
#恢复模式(选主的过程)
- 当整个zk集群再启动过程中,或者当leader出现异常时,zab协议就会进入恢复模式。
通过半选举机制产生新的leader,之后其他机器从新的leader上同步状态,当有过半机器完成状态同步后,就退出恢复模式,进入原子广播模式
#原子广播(同步数据的过程)
- 所有的事务请求必须由唯一的leader来处理,leader将事务请求转换为事务,并将该事务分发给集群中所有的follower
如果由半数的follower进行了正确的反馈,那么leader就会再次向所有的follower发出commit信息,要求将前一个事务进行提交
#zk集群崩溃恢复时leader的选举
集群各节点之间都有通信端口,leader和follower之间都有`socket连接和心跳检测`
leader会每隔一段时间向follower发ping命令,告诉follower我还在,follower会通过心跳检测机制能知道leader的状态,当检测不到leader发来的ping命令后,就认为leader挂掉了,就会重新选举leader
zookeeper的锁
读锁
读锁:大家都可以读,要想上读锁的前提:之前的锁没有写锁
先创建一个临时序号节点,节点的数据是read,表示读锁
- 获取当前zk中序号比自己小的所有节点
- 判读最小节点是否是读锁
如果最小节点是读锁的话,表示所有节点都没有写锁,都可以上读锁
如果最小节点不是读锁的话,表示是写锁,则上读锁失败
那么为该最小节点设置监听,阻塞等待,zk中的watch机制会当最小节点发生变化时(写锁释放了),通知当前节点,于是就可以上读锁了
写锁
写锁:只有得到写锁才能写,要想上写锁的前提是:之前没有任何锁
创建一个临时序号节点,节点的数据是write,表示写锁
- 判断当前zk中所有的子节点
- 判断自己是否是最小的节点(对于写锁来说,它前面是不能由任何锁的)
如果自己是最小节点,则能上写锁
如果自己不是最小节点,说明前面还有锁,则上锁失败
羊群效应
如果用上述的方式(都监听最小节点的变化),主要最小节点发送变化(释放了写锁),就会触发其他节点的监听事件(收到最小节点释放写锁的消息),都会抢着去上写锁,这样的话对zk的压力非常大,这就是羊群效应。
我们可以修改一些监听方式,调整为下面的链式监听来解决这个问题:
- 各个节点不需要监听第一个最小序号的节点,只需要监听它的上一个序号的节点变化
- 因为如果有100个并发创建的临时节点都是有顺序的,你只需要关系你前面的那位有没有释放锁就可以了,如果你的上一位都没上锁成功,你肯定不能成功,因为都是按着顺序来的
- 还有一种场景:
…(img-jJ0BYxOp-1756807717990)]
写锁
写锁:只有得到写锁才能写,要想上写锁的前提是:之前没有任何锁
创建一个临时序号节点,节点的数据是write,表示写锁
- 判断当前zk中所有的子节点
- 判断自己是否是最小的节点(对于写锁来说,它前面是不能由任何锁的)
如果自己是最小节点,则能上写锁
如果自己不是最小节点,说明前面还有锁,则上锁失败
[外链图片转存中…(img-LiQFgBqV-1756807717990)]
羊群效应
如果用上述的方式(都监听最小节点的变化),主要最小节点发送变化(释放了写锁),就会触发其他节点的监听事件(收到最小节点释放写锁的消息),都会抢着去上写锁,这样的话对zk的压力非常大,这就是羊群效应。
我们可以修改一些监听方式,调整为下面的链式监听来解决这个问题:
[外链图片转存中…(img-zcdChzPk-1756807717991)]
- 各个节点不需要监听第一个最小序号的节点,只需要监听它的上一个序号的节点变化
- 因为如果有100个并发创建的临时节点都是有顺序的,你只需要关系你前面的那位有没有释放锁就可以了,如果你的上一位都没上锁成功,你肯定不能成功,因为都是按着顺序来的
- 还有一种场景:
当你监听的上一个节点由于其他原因断开(并不是他已经上锁成功后释放断开),这时候触发了你的监听,可能你还会上锁失败。因为可以上上个节点还没释放锁,只是你上一个节点由于自身原因断开后触发的监听,这时候需要再监听上上个节点的变化,以此类推。