ZooKeeper 专栏
ZooKeeper 是分布式协调组件。它不是数据库,也不是缓存,更不是业务请求网关。它擅长解决分布式系统里的“协调问题”:服务注册发现、配置通知、分布式锁、选主、集群成员管理、状态变更监听等。
很多人只知道 Dubbo 可以用 ZooKeeper 做注册中心,但不理解为什么 ZooKeeper 能做注册中心、为什么能做分布式锁、为什么强调顺序一致性、临时节点和 Watcher。这个专栏要把这些原理讲清楚。
学习目标
| 能力 | 具体要求 |
|---|---|
| 会用 | 能启动 ZK,使用 Curator 创建节点、监听节点、实现锁和选主 |
| 懂模型 | 理解 znode、临时节点、顺序节点、Watcher、Session |
| 懂一致性 | 理解 ZAB、Leader、Follower、过半写入、顺序一致性 |
| 会选型 | 知道 ZK、Redis、etcd、Nacos、数据库锁各适合什么 |
| 会排查 | 能排查连接抖动、Session 过期、锁不释放、脑裂误解、节点过多 |
| 会面试 | 能讲清 ZK 为什么适合协调,不适合高频业务读写 |
本专栏学习路径
| 阶段 | 文档 | 学习重点 |
|---|---|---|
| 入门 | ZooKeeper 总览 | 明确 ZK 解决什么问题 |
| 模型 | znode、Stat、版本CAS、ACL与multi | 节点类型、三类version、顺序号边界、ACL、容量与结果未知 |
| 一致性 | ZAB选主、广播与恢复全过程 | Proposal/ACK/Commit、epoch、zxid、Quorum、DIFF/TRUNC/SNAP与读一致性 |
| 通知 | Watcher与Session全过程 | 连接状态、重连过期、一次性/持久Watch、GC Pause和安全恢复 |
| 场景 | 分布式锁、选主与Fencing | 前驱Watch、Session失效、Fencing Token、Leader任务幂等 |
| 运维 | 部署、日志、Snapshot、监控与故障恢复 | Quorum拓扑、磁盘、容量、安全、滚动升级和生产Runbook |
| 面试 | ZooKeeper 面试题 | 标准回答、追问点和原理跳转 |
ZooKeeper 解决什么问题
分布式系统里多个进程要协作,就会出现这些问题:
| 问题 | 举例 | ZK 能力 |
|---|---|---|
| 谁是主节点 | 多个调度器只能一个执行主任务 | 选主 |
| 服务地址在哪里 | Consumer 要发现 Provider | 注册发现 |
| 配置变了谁通知应用 | 动态开关、路由规则 | Watcher |
| 多实例抢同一资源 | 只有一个实例能重建缓存 | 分布式锁 |
| 集群成员变化 | 节点上线下线要感知 | 临时节点和 Watcher |
一句话:
ZooKeeper 适合做低频、关键、强协调的元数据管理,不适合做高频业务数据读写。
ZooKeeper 不是什么
| 误解 | 正确认知 |
|---|---|
| ZK 是数据库 | ZK 存少量协调元数据,不存大量业务数据 |
| ZK 是缓存 | ZK 不是为高吞吐缓存设计的 |
| ZK 是消息队列 | Watcher 是通知机制,不是可靠消息队列 |
| ZK 会转发 Dubbo 请求 | ZK 只保存服务地址,不转发业务流量 |
| ZK 锁一定比 Redis 锁好 | 一致性更强但吞吐更低,适合低频关键协调 |
核心模型图
mermaid
flowchart TD
A["ZooKeeper 集群"] --> B["Leader"]
A --> C["Follower"]
A --> D["Observer"]
B --> E["处理写请求"]
C --> F["参与投票和读请求"]
D --> G["只服务读请求不投票"]
E --> H["znode 节点树"]
F --> H
G --> HZooKeeper 对外看起来像一棵文件系统树,但它的节点叫 znode。每个 znode 可以保存少量数据,也可以有子节点。
text
/services
/stock-service
/providers
/provider-0000000001
/provider-0000000002
/locks
/rebuild-cache
/lock-0000000001
/lock-0000000002ZooKeeper 为什么适合注册中心
注册中心需要这些能力:
| 需求 | ZK 对应能力 |
|---|---|
| Provider 上线写地址 | 创建节点 |
| Provider 异常宕机自动下线 | 临时节点随 Session 过期删除 |
| Consumer 感知地址变化 | Watcher 通知 |
| 多 Provider 地址有顺序 | 顺序节点或节点列表 |
| 注册数据一致 | ZAB 保证写入顺序和多数派一致 |
流程:
mermaid
flowchart TD
A["Provider 启动"] --> B["创建临时节点"]
B --> C["Consumer 监听 providers 目录"]
C --> D["Provider 宕机"]
D --> E["Session 过期"]
E --> F["临时节点删除"]
F --> G["Consumer 收到变更通知"]ZooKeeper 为什么能做分布式锁
ZK 常用“临时顺序节点”实现锁:
mermaid
flowchart TD
A["多个客户端创建临时顺序节点"] --> B["获得节点序号"]
B --> C{"自己是不是最小序号"}
C -- "是" --> D["获得锁"]
C -- "否" --> E["监听前一个节点"]
E --> F["前一个节点删除"]
F --> C为什么监听前一个节点,而不是所有客户端都监听最小节点?
如果所有客户端都监听最小节点,锁释放时会唤醒所有等待者,造成羊群效应。监听前一个节点,只唤醒下一个候选者,效率更高。
最小 Demo:Curator 创建节点
Maven 依赖示例:
xml
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.6.0</version>
</dependency>创建持久节点:
java
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.nio.charset.StandardCharsets;
public class ZookeeperCreateNodeDemo {
public static void main(String[] args) throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient(
"127.0.0.1:2181",
new ExponentialBackoffRetry(1000, 3)
);
client.start();
String path = "/demo/config";
if (client.checkExists().forPath(path) == null) {
client.create()
.creatingParentsIfNeeded()
.forPath(path, "enabled=true".getBytes(StandardCharsets.UTF_8));
}
byte[] data = client.getData().forPath(path);
System.out.println(new String(data, StandardCharsets.UTF_8));
client.close();
}
}商业常用场景
| 场景 | 怎么用 ZK | 注意点 |
|---|---|---|
| Dubbo 注册中心 | Provider 写临时节点,Consumer 监听目录 | 不承载业务流量 |
| 分布式锁 | 临时顺序节点 | 适合低频关键锁,不适合高频热点锁 |
| 选主 | 多实例竞争 Leader | Leader 挂了要能恢复状态 |
| 配置通知 | Watcher 监听配置节点 | 配置量不能太大,通知后要重新读取 |
| 调度器主备 | 一个 Leader 负责调度 | 任务本身仍要幂等 |
| 集群成员管理 | 节点以临时节点注册 | Session 抖动会引起成员变化 |
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 存大量业务数据 | ZK 内存和同步压力大 | 只存少量元数据 |
| 高频写 ZK | Leader 压力大,延迟升高 | 高频数据放 Redis、DB、MQ |
| Watcher 当 MQ 用 | 通知丢语义、只触发一次 | Watch 后重新注册并读取最新数据 |
| Session 超时设置太短 | 网络抖动导致临时节点误删 | 根据网络和 GC 情况设置 |
| 锁内执行太久 | 其他客户端长时间等待 | 锁内只做短事务,业务要幂等 |
| 认为 ZK 避免所有脑裂 | 客户端网络分区仍需业务兜底 | 状态机、Fencing Token、幂等 |
与其他组件对比
| 组件 | 更适合 | 不适合 |
|---|---|---|
| ZooKeeper | 选主、协调、注册、低频分布式锁 | 高频业务读写、大对象存储 |
| Redis | 高性能缓存、计数、热点锁 | 强一致协调和选主 |
| etcd | 云原生元数据、K8s 后端、强一致 KV | 传统 Java Dubbo 老项目兼容成本 |
| Nacos | Spring Cloud Alibaba 注册配置 | 通用分布式锁和选主不是主定位 |
| MySQL | 业务数据和事务 | 高频协调通知 |
面试标准回答
ZooKeeper 是分布式协调组件,提供类似文件树的 znode 数据模型,支持持久节点、临时节点、顺序节点、Watcher 通知和 Session 会话。它通过 ZAB 协议保证写请求的顺序一致性和多数派提交,因此适合做注册中心、配置通知、分布式锁、选主和集群成员管理。它不适合存大量业务数据,也不适合高频读写。
本章小结
ZooKeeper 的核心不是“存数据”,而是“协调”。临时节点解决进程存活感知,顺序节点解决排队,Watcher 解决变化通知,ZAB 解决多节点写入一致性。理解这四个点,才能真正理解 ZooKeeper 为什么能支撑注册中心、锁和选主。
