Zookeeper

一、Zookeeper概念

1.1 什么是Zookeeper

Zookeeper是一款基于观察者设计模式的分布式服务管理框架,核心功能是:

  • 管理分布式系统中需要被关注的数据(如节点状态、配置信息);
  • 接受观察者(客户端/服务节点)的注册;
  • 当数据状态发生变化时,自动通知所有已注册的观察者,触发其响应逻辑。

1.2 核心设计模式:观察者模式

  • 定义:一种行为型设计模式,定义“一对多”的依赖关系——当一个对象(被观察者,如Zookeeper中的数据节点)状态改变时,其所有依赖对象(观察者,如客户端)都会收到通知并自动更新。
  • 适用场景:对象间存在明确的一对多依赖(如Zookeeper管理多个客户端的配置同步、节点状态监控)。

二、Zookeeper基本原理

2.1 架构特点

Zookeeper集群采用主从(Leader-Follower)架构,核心特性如下:

架构特性

说明

集群组成

1个领导者(Leader)+ 多个跟随者(Follower),可按需添加观察者(Observer)

选举机制

启动时通过Paxos协议选举Leader,确保集群一致性

容错能力

集群中半数以上节点存活即可正常服务(如3台允许1台挂,5台允许2台挂)

数据一致性

每个Server保存相同的数据副本,客户端连接任意Server均获取一致数据

更新处理

Leader基于Zab协议处理数据更新,更新请求按发送顺序执行

原子性

一次更新操作(可含多个步骤)要么“多数Server修改成功”,要么“完全失败”

实时性

客户端能在“短时间窗口”内读取到最新数据

2.2 核心角色

角色

职责描述

领导者(Leader)

1. 发起并决议投票(如Leader选举、数据更新);2. 处理数据更新操作,维护系统状态

跟随者(Follower)

1. 接收客户端请求并返回结果;2. 参与Leader选举投票;3. 同步Leader的数据

观察者(Observer)

1. 接收客户端连接,转发写请求给Leader;2. 不参与投票,仅同步Leader状态;3. 扩展读取性能,减轻Follower压力

客户端(Client)

发起请求(读/写数据、注册监听),接收Zookeeper的通知和响应

关键注意点:Zookeeper Server数目推荐为奇数。例如3台与4台容错能力相同(均允许1台挂),但奇数台更节省资源,避免冗余。

2.3 数据结构

Zookeeper的数据模型类似Unix文件系统,整体为“树状结构”,核心单元是Znode(数据节点),关键特性:

  1. 存储能力:每个Znode默认可存储1MB数据(适合存储轻量信息,如配置、状态标识);
  2. 唯一标识:每个Znode通过“绝对路径”唯一标识(如/znode1/app1);
  3. 特殊属性:Znode兼具“文件”和“文件夹”特性——既能存储数据,也能拥有子节点(区别于Unix的“文件/文件夹分离”设计);
  4. 示例结构
/
├─ znode1
│  ├─ app1  (存储数据:如"service=login")
│  ├─ app2
│  └─ app3
└─ znode2  (存储数据:如"config=timeout=30s")

2.4 数据写流程

  1. 客户端发起数据更新请求(如创建Znode、修改数据);
  2. 若客户端连接的是Follower/Observer,请求会被转发至Leader;
  3. Leader通过Paxos协议发起投票,同步更新请求给所有Follower;
  4. 多数Follower(半数以上)确认在内存中修改成功,Leader判定更新成功;
  5. Leader向客户端返回“更新成功”响应,并通知所有节点同步最终状态。

三、Zookeeper应用场景

3.1 统一命名服务

  • 需求:分布式环境中,IP地址难记,需为服务/应用提供“易识别的统一名称”(类似域名)。
  • 实现逻辑
    1. 在Zookeeper中创建指定路径的Znode(如/service/注册登录服务);
    2. 存储服务对应的IP列表及权重(如192.168.22.13:访问数60192.168.22.14:访问数50);
    3. 客户端通过访问Znode路径获取服务IP,无需记忆复杂IP。
  • 示例:类似www.baidu.com映射多个IP,实现软负载均衡。

3.2 配置管理

  • 需求:分布式集群中,所有节点需使用一致的配置(如Hadoop的核心配置),修改配置后需快速同步至所有节点。
  • 实现逻辑
    1. 在Zookeeper中创建全局配置节点(如/Configuration),存储集群统一配置;
    2. 所有节点(客户端)对该配置节点注册“监听(watch)”;
    3. 当配置修改时,Zookeeper自动通知所有节点,节点读取最新配置并生效。

3.3 集群管理

Zookeeper可实现分布式环境下的节点监控、锁、队列等核心能力,具体场景如下:

(1)分布式通知/协调
  • 需求:主节点需实时掌握子节点状态(如HDFS中NameNode监控DataNode、YARN中ResourceManager监控NodeManager)。
  • 实现逻辑
    1. 子节点启动时,在Zookeeper中创建临时Znode(如/datanode/192.168.22.13);
    2. 主节点监听/datanode路径的子节点变化;
    3. 子节点下线(或故障)时,临时Znode自动删除,主节点收到通知后更新节点列表。
(2)分布式锁
  • 需求:多客户端竞争同一共享资源(如数据库写入、分布式任务调度),需保证“同一时间仅一个客户端获取权限”。
  • 实现逻辑
    1. 所有客户端向Zookeeper的同一父节点(如/lock)发起“创建相同Znode”请求;
    2. Zookeeper保证“仅一个客户端创建成功”(Znode路径唯一),该客户端获取锁;
    3. 其他客户端创建失败,进入等待状态(监听父节点变化);
    4. 持有锁的客户端释放资源后,删除自身Znode,Zookeeper通知其他客户端重新竞争。
(3)分布式队列
  • 同步队列:当队列所有成员(任务)聚齐后,队列才可用(如Job由多个Task组成,所有Task完成后Job才执行)。
    • 实现:创建Job节点(如/job),每个Task完成后创建临时子节点(如/job/task1);客户端监听/job的子节点数量,达到Task总数时触发Job执行。
  • FIFO队列:按“先进先出”顺序入队/出队(如生产者-消费者模型)。
    • 实现:通过“有序临时Znode”(-s -e参数),生产者创建有序节点,消费者按序号从小到大获取节点数据。

四、Zookeeper命令行操作

4.1 进入命令行工具

执行Zookeeper安装目录下的bin/zkCli.sh脚本,默认连接本地Zookeeper(端口2181):

./zkCli.sh  # 本地连接
./zkCli.sh -server 192.168.22.13:2181  # 远程连接指定节点

4.2 核心命令详解

(1)help:查看帮助
  • 功能:列出所有支持的命令及用法。
  • 示例:
[zk: localhost:2181(CONNECTED) 0] help

输出包含lscreategetdelete等命令的基本格式。

(2)ls path [watch]:查看节点子目录
  • 功能:列出指定路径下的所有子Znode,watch参数可选(开启对该路径子节点变化的监听)。
  • 示例:
    1. 普通查询(查看根目录子节点):
[zk: localhost:2181(CONNECTED) 1] ls /
[cluster, brokers, zookeeper, admin, consumers, config, hbase]
    1. 带监听查询(监听根目录子节点变化):
# 客户端1开启监听
[zk: localhost:2181(CONNECTED) 2] ls / watch
# 客户端2创建新节点
[zk: localhost:2181(CONNECTED) 0] create /test1 abcd
# 客户端1收到通知
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/
(3)create [-s] [-e] path data acl:创建Znode
  • 核心参数:
    • -s:创建有序Znode(路径后自动追加全局递增序号,如/test10000000278);
    • -e:创建临时Znode(客户端断开连接后自动删除,默认无此参数为永久Znode);
    • path:Znode绝对路径;
    • data:Znode存储的数据;
    • acl:访问控制(默认OPEN_ACL_UNSAFE,允许所有客户端访问)。
  • 节点类型(4种):

节点类型

特点

PERSISTENT

无序永久节点(默认)

PERSISTENT_SEQUENTIAL

有序永久节点(-s)

EPHEMERAL

无序临时节点(-e)

EPHEMERAL_SEQUENTIAL

有序临时节点(-s -e)

  • 示例:
# 创建无序永久节点
[zk: localhost:2181(CONNECTED) 3] create /test2 "hello zk"
# 创建有序临时节点
[zk: localhost:2181(CONNECTED) 4] create -s -e /test3 "temp seq"
(4)get path [watch]:获取Znode数据
  • 功能:读取指定Znode的存储数据,watch参数可选(监听该节点数据变化)。
  • 示例:
[zk: localhost:2181(CONNECTED) 5] get /test2
hello zk  # 节点数据
cZxid = 0xa00000525  # 以下为节点元数据(同stat命令输出)
ctime = Wed Feb 19 21:00:00 CST 2020
...
(5)stat path [watch]:查看节点状态(元数据)
  • 功能:查看Znode的元数据(无数据内容),watch参数可选(监听节点状态变化)。
  • 关键元数字段说明:

字段

含义

cZxid

创建节点的事务ID(ZXID),64位:高32位=Leader周期,低32位=事务自增计数器

ctime

节点创建时间

mZxid

最后修改节点的事务ID

mtime

最后修改时间

pZxid

最后修改子节点的事务ID

cversion

子节点修改次数

dataVersion

节点数据修改次数

ephemeralOwner

临时节点的所有者SessionID(永久节点为0x0)

dataLength

节点数据长度(字节)

numChildren

子节点数量

  • 示例:
[zk: localhost:2181(CONNECTED) 6] stat /test1
cZxid=0xa00000117
ctime=Wed Feb 19 19:42:34 CST 2020
mZxid=0xa00000511
mtime=Wed Feb 19 20:48:02 CST 2020
pZxid=0xa00000524
cversion=2
dataVersion=3
aclVersion=0
ephemeralOwner=0x0
dataLength=4
numChildren=0
(6)delete/rmr:删除节点
  • delete path [version]:删除无子女的节点version可选(需匹配节点当前dataVersion,避免并发删除冲突);
  • rmr path递归删除节点(含所有子节点);
  • 示例:
# 删除无子女节点
[zk: localhost:2181(CONNECTED) 7] delete /test1
# 递归删除含子节点的节点
[zk: localhost:2181(CONNECTED) 8] rmr /test3

五、Zookeeper Java API应用

Zookeeper提供Java API(核心包:org.apache.zookeeper),支持节点增删改查、监听、分布式锁等操作,核心功能及示例如下:

5.1 基础功能:节点增删改查(Demo)

核心步骤
  1. 创建ZooKeeper实例(建立与集群的连接);
  2. 调用API实现节点操作;
  3. 关闭连接,释放资源。
示例代码逻辑(关键片段)
import org.apache.zookeeper.*;
import java.io.IOException;
public class ZkBasicOps {
// Zookeeper连接地址
private static final String CONNECT_STR = "192.168.22.13:2181,192.168.22.14:2181";
// 会话超时时间(毫秒)
private static final int SESSION_TIMEOUT = 5000;
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
// 1. 创建Zookeeper实例(带默认监听器)
ZooKeeper zk = new ZooKeeper(CONNECT_STR, SESSION_TIMEOUT, event -> {
System.out.println("WatchedEvent: " + event);
});
// 2. 创建永久节点(路径:/zoo2,数据:myData2,权限:OPEN_ACL_UNSAFE)
String path = zk.create("/zoo2", "myData2".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("创建节点:" + path);
// 3. 获取节点数据
byte[] data = zk.getData("/zoo2", false, null);
System.out.println("节点数据:" + new String(data)); // 输出:myData2
// 4. 修改节点数据(版本号:-1表示忽略版本检查)
zk.setData("/zoo2", "hahahahaha".getBytes(), -1);
data = zk.getData("/zoo2", false, null);
System.out.println("修改后数据:" + new String(data)); // 输出:hahahahaha
// 5. 删除节点
zk.delete("/zoo2", -1);
// 6. 验证删除(exists返回null表示节点不存在)
Stat stat = zk.exists("/zoo2", false);
System.out.println("节点状态:" + stat); // 输出:null
// 7. 关闭连接
zk.close();
}
}

5.2 典型场景:服务器上下线动态感知

需求

客户端需实时获取服务器集群的在线状态(服务器上线时客户端自动感知,下线时自动移除)。

实现逻辑
  1. 服务器端:启动时在Zookeeper的/sgroup路径下创建临时有序Znode(如/sgroup/sub0000000004),存储服务器标识(如IP/服务名);下线时临时Znode自动删除。
  2. 客户端:监听/sgroup路径的子节点变化(NodeChildrenChanged事件),当子节点新增/删除时,重新获取所有子节点列表,更新服务器在线列表。
演示结果
  • 服务器1启动:客户端监听到子节点新增,输出server list updated: [test1]
  • 服务器2启动:客户端输出server list updated: [test1, test2]
  • 服务器1下线:客户端输出server list updated: [test2]
  • 服务器2下线:客户端输出server list updated: []

5.3 典型场景:分布式共享锁

需求

多客户端竞争同一共享资源(如数据库写入),保证“同一时间仅一个客户端持有锁”。

实现逻辑
  1. 所有客户端向Zookeeper的/locks路径创建临时有序Znode(如/locks/sub0000000000);
  2. 客户端获取/locks下的所有子节点,按序号排序;
  3. 若自身Znode序号最小,获取锁,访问共享资源;
  4. 访问完成后,释放锁(删除自身Znode);
  5. 其他客户端监听/locks的子节点变化事件,当序号最小的节点被删除时,重新执行步骤2-3,竞争锁。
演示结果

客户端按Znode序号依次获取锁,日志输出如下:

gain lock: /locks/sub0000000000  # 序号0客户端获取锁
finished: /locks/sub0000000000   # 释放锁
gain lock: /locks/sub0000000001  # 序号1客户端获取锁
finished: /locks/sub0000000001   # 释放锁
gain lock: /locks/sub0000000002  # 序号2客户端获取锁
finished: /locks/sub0000000002   # 释放锁
...

六、核心总结

  1. 定位:Zookeeper是分布式系统的“协调者”,而非“存储系统”(适合轻量数据,默认1MB/节点);
  2. 核心能力:配置同步、节点监控、分布式锁、分布式队列,依赖“观察者模式+主从架构+Paxos/Zab协议”保证一致性;
  3. 关键细节
    • 集群节点数选奇数,保证容错效率;
    • 临时Znode依赖客户端Session,适合状态监控;
    • ZXID是事务唯一标识,64位结构保证全局有序;
  1. 应用场景:分布式系统的配置管理、服务发现、状态监控、资源竞争控制。
© 版权声明

相关文章