结果导向 · 分布式锁 / 注册中心 / 选举 / 配置中心 / Watch 监听
ZooKeeper 是什么(一句话):一个分布式协调服务——它自己不存业务数据、不跑业务逻辑,专门帮分布式系统做协调工作:选主、加锁、注册发现、配置同步。像分布式系统的"村委会",不干活,专门通知大家、维持秩序。
什么场景该用 ZooKeeper:
| 场景 | ZK 在里面干什么 | 不用会怎样 |
|---|---|---|
| 分布式锁 | 临时节点 + Watch 实现公平锁,会话断开自动释放 | 自己用 Redis 实现也行,但 ZK 更强一致、不会脑裂 |
| 注册中心(Dubbo/Kafka) | 服务上下线通知,客户端 Watch 节点变化 | Dubbo 老版本默认用 ZK,Kafka 元数据也存 ZK |
| Leader 选举 | 多实例竞争创建同一个临时节点,创建成功的当 Leader | 自己实现选举逻辑很复杂,容易出 bug |
| 配置中心 | 配置存在 ZNode,客户端 Watch 变化自动刷新 | 现在更推荐 Nacos/Apollo,ZK 做配置太重 |
| 分布式 ID(不推荐) | 顺序节点生成唯一序号 | 有更好的方案(雪花算法),ZK 性能不够 |
定位:ZooKeeper 是一个树形结构的内存数据库 + 事件通知系统。它的核心是 ZNode 树(类似文件系统目录),每个节点可以存少量数据,客户端可以"监听"节点变化,节点一变就收到通知。基于这两个能力,衍生出分布式锁、注册中心、选举等上层应用。
核心能力全景(5 大块):
常被拿来和 ZK 比的:Nacos(阿里注册中心+配置)、Eureka(Netflix 注册中心)、etcd(K8s 用的)、Consul(HashiCorp)。结果导向直接看下表:
| 维度 | ZooKeeper | Nacos | Eureka | etcd | Consul |
|---|---|---|---|---|---|
| 定位 | 分布式协调(通用) | 注册中心+配置中心 | 注册中心 | KV存储+协调 | 注册+配置+健康检查 |
| 一致性 | CP (ZAB,强一致) | AP/CP 可切换 | AP (最终一致) | CP (Raft) | CP (Raft) |
| 语言 | Java | Java | Java | Go | Go |
| 注册中心 | ⚠️ 能做但重 | ✅ 专精,功能全 | ✅ 简单 | ⚠️ 偏底层 | ✅ 功能全 |
| 配置中心 | ⚠️ 能做但弱 | ✅ 强(灰度/版本) | ❌ 不支持 | ⚠️ 偏底层 | ✅ 支持 |
| 分布式锁 | ✅ 经典方案 | ⚠️ 不推荐 | ❌ | ✅ 支持 | ✅ 支持 |
| 运维难度 | ⚠️ 高(Java,GC) | ✅ 低(Docker友好) | ✅ 低 | ✅ 低(单二进制) | ✅ 低 |
| 什么时候选它 | Kafka元数据/Dubbo老项目/强一致锁 | ✅ Spring Cloud新项目首选 | 维护中(2.x停更) | K8s生态 | 多数据中心 |
ZK 的数据是树形结构,和文件系统目录一样。每个节点叫 ZNode,可以存数据、可以有子节点。根节点是 /。
| 节点类型 | 特性 | 典型用途 |
|---|---|---|
| 持久节点 (PERSISTENT) | 创建后一直存在,主动删除才消失 | 配置、目录结构 |
| 持久顺序节点 | 持久 + 自动加递增序号后缀 | 分布式队列 |
| 临时节点 (EPHEMERAL) | 会话断开自动删除,不能有子节点 | 注册中心、Leader选举、分布式锁 |
| 临时顺序节点 | 临时 + 顺序序号 | 分布式公平锁(最常用) |
Watch 是 ZK 的事件订阅机制。客户端读节点时可以注册 Watch,节点变化时 ZK 推送通知。
ZK 集群一般是奇数台(3/5/7),用 ZAB 协议保证强一致。ZAB 协议做两件事:①选 Leader ②数据同步。
/locks/order-create 下创建临时顺序节点 lock-00000001;②获取所有子节点,自己是不是最小的?是最小→拿到锁;不是→Watch 前一个节点;③前一个节点删除(释放锁或崩溃),自己变最小→拿到锁;④释放锁=删除自己的节点。这就是 ZK 分布式锁的"公平锁"实现,每个等待者只 Watch 前一个,避免羊群效应。
背景:秒杀活动,3 台应用实例同时收到扣库存请求。MySQL 的 UPDATE stock SET num=num-1 在高并发下会超卖。用 ZK 分布式锁串行化扣库存操作。
背景:报表生成任务部署在 3 台机器上,但任务每天只该跑一次。用 Leader 选举——3 台竞争,只有 Leader 执行,Leader 挂了自动切换。
背景:Dubbo 服务提供者启动时注册到 ZK,消费者从 ZK 拉取地址列表。提供者挂了,ZK 自动通知消费者摘除节点。
现象:配置中心用 ZK 实现,改了配置,部分实例刷新了、部分没刷新。排查发现 Watch 通知漏了。
原因:Watch 是一次性的。流程是:①收到通知 ②重新 getData 注册 Watch ③读取新数据。但①和②之间有时间窗口,如果这期间配置又改了,第二次变更不会被通知,因为 Watch 还没重新注册。
解法:①先重新注册 Watch 再处理数据(顺序反过来);②用 ZK 3.6+ 的 addPersistentWatch()(持久 Watch,不用重新注册);③新项目直接用 Nacos 做配置中心,别用 ZK。
现象:某服务 GC 停顿 40 秒,ZK 会话超时(默认 40 秒),临时节点被删,服务被摘除。但服务其实还活着,GC 结束后重新连上 ZK 又注册回来。在这期间消费者认为它下线了,流量全打到其他实例,差点雪崩。
原因:ZK 会话超时是硬性的——不管你是真挂了还是 GC 假死,超时就删临时节点。JVM Full GC 导致 STW,客户端没法发心跳,ZK 认为你挂了。
解法:①调大 sessionTimeout(从 40s 调到 120s,容忍 GC);②优化 JVM 避免 Full GC;③用 Nacos——Nacos 是客户端主动心跳,GC 假死时心跳会恢复,不会误判下线(AP 模式更宽容)。
现象:某个服务有 1 万个消费者 Watch 了同一个节点。该节点一变,1 万个通知同时发出,ZK CPU 飙到 100%,网络瞬间打满。
原因:这是 ZK 经典的"羊群效应"——太多客户端 Watch 同一个节点,节点变化时全部通知。
解法:分布式锁用临时顺序节点 + 只 Watch 前一个(InterProcessMutex 就是这么做的),避免羊群效应。注册中心场景改用子节点列表 Watch(getChildren + watch)而不是单个节点 Watch,减少通知范围。
现象:5 台 ZK 集群,网络分区导致 2 台一组、3 台一组。3 台那组选举出 Leader 继续服务,2 台那组因不过半无法选举。分区恢复后,2 台那组的数据被丢弃(因为 3 台那组 zxid 更新)。
原因:ZK 的 ZAB 协议要求过半写成功,网络分区时少数派那组无法写,分区恢复后少数派的数据被多数派覆盖。这是 CP 系统的正常行为——保证一致性,牺牲可用性。
解法:这不是 bug 而是特性,ZK 就是要强一致。如果业务能接受最终一致,用 Nacos AP 模式或 Eureka,分区时两组都能读写,恢复后合并。但分布式锁场景必须用 CP(ZK),不能脑裂,否则两个 Leader 同时加锁就超卖了。
都是协调服务,但定位不同:ZK 是通用分布式协调(锁/选举/注册都能做),Nacos 专精注册中心+配置中心。
新项目选型:注册中心/配置中心 → Nacos(功能全、运维简单、AP/CP 可切换)。分布式锁/Leader 选举 → ZK 或 Redis(Nacos 不适合做锁)。Kafka 元数据 → ZK(Kafka 内置依赖)。ZK 在新项目里用得越来越少,主要是 Kafka 和 Dubbo 老项目在用。
核心区别:ZK 锁是 CP(强一致),Redis 锁是 AP(最终一致)。
• 选 ZK 锁:宁可慢也不能错的场景(金融扣款、库存扣减、秒杀)。ZK 不会脑裂,主从切换时锁不会丢。
• 选 Redis 锁(Redisson):性能优先、能接受极小概率脑裂的场景。Redis 单机 10 万 QPS,ZK 只有 5000-10000。
实际中 90% 场景用 Redis 锁(够用且快),只有金融级强一致才上 ZK 锁。
ZK 用"过半写成功"保证一致性。奇数台的容错能力最优:
• 3 台:挂 1 台还能用(2 > 1.5 ✓),挂 2 台废(1 < 1.5 ✗)→ 容错 1 台
• 4 台:挂 1 台还能用(3 > 2 ✓),挂 2 台废(2 = 2 ✗)→ 容错 1 台
• 5 台:挂 2 台还能用(3 > 2.5 ✓)→ 容错 2 台
3 台和 4 台容错能力一样(都只能挂 1 台),但 4 台多花一台钱。所以奇数台性价比最高。生产推荐 3 台(小规模)或 5 台(大规模),不要用偶数台。
临时节点(EPHEMERAL):生命周期绑定客户端会话,客户端断开自动删除。不能有子节点。用于:注册中心(服务下线自动摘除)、分布式锁(持锁者崩溃自动释放锁)、Leader 选举(Leader 崩溃自动重选)。
持久节点(PERSISTENT):创建后一直存在,需要主动删除。用于:配置数据、目录结构、元数据。
判断标准:"这个节点代表的实体还在不在?实体没了节点该不该自动消失?" 该消失→临时节点,不该消失→持久节点。
为什么一次性:避免通知风暴。如果是持久的,一个高频变化的节点会给所有订阅者持续发通知,ZK 扛不住。一次性保证每个事件精确通知一轮。
怎么解决漏事件:
① 收到通知后先重新注册 Watch 再处理数据(顺序很重要);
② ZK 3.6+ 用 addPersistentWatch()(持久 Watch,自动重新注册);
③ 用 Curator 的 NodeCache / PathChildrenCache(封装了重新注册逻辑,不会漏);
④ 配置中心场景直接用 Nacos,不用自己处理 Watch 问题。
1. 定位:分布式协调服务,不是数据库。存元数据和协调状态,不存业务数据。像分布式系统的"村委会"。
2. 核心机制:临时节点(会话绑定,断了自动删)+ Watch(一次性通知)。这两个组合出锁/选举/注册中心。
3. 现状:新项目用得越来越少。注册中心/配置中心 → Nacos。分布式锁 → ZK 或 Redis。ZK 主要存量在 Kafka 和 Dubbo 老项目。