1. ZooKeeper 的核心概念
ZooKeeper 本质上是一个分布式的、开源的分布式应用程序协调服务 。它提供了一个类似于文件系统的树形命名空间(/zookeeper),以及一套简单的原语(API),使得分布式应用可以实现诸如配置维护、命名服务、分布式同步和组服务等。
理解它的几个核心概念至关重要
1、数据模型 (Znode)
-
ZooKeeper 的数据存储在一个类似于文件目录树 的结构中,树上的每个节点被称为 Znode。
-
Znode 既可以存储数据(像一个文件),也可以有子节点(像一个目录)。但它与文件系统的主要区别是,每个节点都可以存储数据。
-
Znode 有四种类型:
-
持久节点 (PERSISTENT):创建后,即使客户端会话结束,节点依然存在。
-
临时节点 (EPHEMERAL) :客户端会话结束时,节点自动被删除。这是实现服务发现和集群管理的关键。
-
持久顺序节点 (PERSISTENT_SEQUENTIAL):与持久节点类似,但 ZooKeeper 会在节点名后自动追加一个单调递增的序列号。
-
临时顺序节点 (EPHEMERAL_SEQUENTIAL) :与临时节点类似,带有序列号。这是实现分布式锁等高级同步功能的关键。
-
2、会话 (Session)
-
客户端与 ZooKeeper 服务器建立一个 TCP 长连接,并通过这个连接维持一个会话。
-
会话有状态(如
CONNECTING,CONNECTED,CLOSED)。通过心跳机制来保持会话的有效性。 -
如果会话超时,服务器会认为该客户端"死亡",并清除其创建的所有临时节点。
3、监视机制 (Watch)
-
客户端可以在某个 Znode 上设置一个 Watch(监视器)。
-
当该 Znode 发生变化(数据变更、子节点增减、节点被删除等)时,ZooKeeper 服务器会向客户端发送一个一次性的事件通知。
-
这是一个推的模式,允许客户端在数据变化时及时得到通知,而不需要轮询。
4、集群与一致性 (ZAB协议)
-
ZooKeeper 自身通常以一个集群形式部署,称为一个 Ensemble。
-
集群中有一个 Leader 节点负责处理所有写请求,多个 Follower 节点处理读请求并参与选举。
-
它使用自创的 ZAB (ZooKeeper Atomic Broadcast) 协议 来保证集群的数据一致性。所有写请求都会先由 Leader 提案,然后广播给所有 Follower,超过半数节点确认后,写操作才会被提交。这保证了:
-
顺序一致性:客户端的更新请求会按顺序执行。
-
原子性:更新要么成功,要么失败,没有中间状态。
-
单一系统映像:无论客户端连接到哪个服务器,看到的数据视图都是一致的。
-
-
ZooKeeper 遵循的是 CP 原则(在 CAP 定理中,优先保证一致性 和分区容错性),在网络分区发生时,它会保证数据的一致性,但可能牺牲部分可用性。
2. ZooKeeper 的主要作用
ZooKeeper 的核心作用是为分布式系统提供底层、可靠的基础协调服务。
1、配置管理
-
可以将系统的公共配置(如数据库地址、系统参数)存储在 ZooKeeper 的一个 Znode 中。
-
所有应用程序监听这个 Znode,一旦配置发生变化,ZooKeeper 会通知所有监听的客户端,从而实现动态配置更新
2、命名服务 (Service Naming)
- 通过树形结构,可以为一个服务或资源创建一个全局唯一的路径名,方便服务定位。
3、分布式锁 (Distributed Lock)
-
利用临时顺序节点可以轻松实现排他锁、读写锁等。
-
原理 :所有客户端在同一个节点下创建临时顺序子节点。判断自己创建的节点是否是最小的序号,如果是则获得锁;否则监听比自己序号小的前一个节点。当前一个节点被删除(锁被释放)时,自己再尝试获取锁。这是实现公平锁的经典方式。
4、集群管理 / 服务发现与注册
-
利用临时节点的特性。
-
服务注册 :每个服务启动时,在特定路径(如
/services/serviceA)下创建一个临时节点 (如/services/serviceA/host1:8080)。 -
服务发现 :服务消费者监听
/services/serviceA路径的子节点变化。 -
健康监测 :如果某个服务实例宕机,它与 ZooKeeper 的会话会结束,其创建的临时节点会自动消失。ZooKeeper 会通知所有监听的消费者,从而实现动态的服务列表更新。
5、领导者选举 (Leader Election)
-
在分布式主从架构中,需要选出一个主节点(Master)。
-
所有候选节点同时尝试在指定路径下创建一个临时节点 (如
/election/leader)。 -
由于 ZooKeeper 保证节点路径唯一,只有一个候选者能创建成功。创建成功的那个节点就成为 Leader。
-
其他候选者在该节点上设置 Watch。如果 Leader 宕机,这个临时节点会被删除,其他候选者会收到通知,并开始新一轮的选举。
3. 经典使用案例与场景
案例一:Apache HBase(分布式数据库)
角色:HBase 严重依赖 ZooKeeper 来管理其集群状态。
具体作用:
-
领导者选举:选举 HBase 的 HMaster 主节点。确保同一时刻只有一个活动的 HMaster。
-
服务发现:RegionServer(数据存储节点)启动时向 ZooKeeper 注册临时节点。HMaster 和客户端通过 ZooKeeper 来发现可用的 RegionServer。
-
元数据存储 :存储 HBase 的根元数据表(
-ROOT-和.META.)的位置,这是客户端查找数据的入口。 -
分布式锁:保证对元数据等关键资源的互斥访问。
案例二:Apache Kafka(分布式消息队列)
角色:Kafka 使用 ZooKeeper 来管理其 broker、topic 和分区。
具体作用:
-
Broker 注册:Kafka broker 启动时在 ZooKeeper 中注册自己,存储其主机名、端口等信息。
-
Topic 配置管理:存储每个 topic 的分区数量、副本列表等配置信息。
-
领导者选举:为每个 partition 选举一个 leader replica。所有生产和消费请求都由该 leader 处理。
-
消费者组管理 :记录消费者组的 offset(消费进度),以及组内成员的变动。(注意:新版本的 Kafka 正在逐步减少对 ZooKeeper 的依赖,将元数据管理迁移到自研的 KRaft 协议上,以简化架构)
案例三:实现一个简单的分布式锁(代码逻辑)
假设有两个客户端 ClientA 和 ClientB 需要竞争一把锁。
1、初始化:两者都连接到 ZooKeeper 集群。
2、创建节点 :两者都尝试在 /locks/mylock 下创建临时顺序节点。
-
ClientA 创建了
/locks/mylock/lock-000000001 -
ClientB 创建了
/locks/mylock/lock-000000002
3、尝试获取锁 :两个客户端都获取 /locks/mylock 下的所有子节点,并按序号排序。
4、判断 :序号最小的节点获得锁。这里 ClientA 的 lock-000000001 是最小序号,因此 ClientA 成功获取锁。
5、等待锁 :ClientB 没有获得锁,它并不持续轮询,而是在它前一个节点(即 lock-000000001)上设置一个 Watch。
6、释放锁 :ClientA 完成任务后,主动关闭会话或删除节点。由于是临时节点,lock-000000001 会自动消失。
7、通知与获取 :ZooKeeper 通知 ClientB:lock-000000001 已被删除。ClientB 再次获取所有子节点,发现自己的节点 lock-000000002 现在是最小的了,于是 ClientB 成功获取锁。
总结与对比(vs Nacos)
| 特性 | ZooKeeper | Nacos |
|---|---|---|
| 核心定位 | 分布式协调器 (CP) | 服务发现与配置中心 (AP/CP 可切换) |
| 数据模型 | 树形文件结构 (Znode) | Key-Value / 服务实例列表 |
| 一致性 | 强一致性 (CP),基于ZAB协议 | 默认高可用 (AP),基于Raft协议(用于配置)和自研协议(用于服务) |
| 健康检查 | 客户端TCP心跳 | 客户端上报/服务端检查(TCP/HTTP/MySQL) |
| 主要场景 | 分布式锁、领导者选举、集群状态管理 | 服务发现、动态配置、流量管理 |
| 易用性 | 需要自己封装逻辑,API相对底层 | 开箱即用,与Spring Cloud等生态集成度极高 |
简单来说:ZooKeeper 更像一个精密的瑞士军刀,提供了强大的底层协调原语,但你需要自己组合使用来构建上层功能(如服务发现)。而 Nacos 更像一个功能齐全的厨房,直接为你准备好了"服务发现"和"配置管理"这两道主菜,开箱即用,更加面向微服务架构。