Java面试——Zookeeper在分布式系统中的作用

为什么分布式系统需要 Zookeeper

Zookeeper 不是“必须”的,但它是解决分布式协作中一类典型问题的成熟方案。当多个服务节点需要共享状态、协调动作、避免脑裂或实现选主时,自己用 Redis 或数据库做临时方案容易出错——比如 Watch 机制缺失导致事件丢失、没有顺序一致性保障、临时节点清理不及时引发僵尸节点。ZooKeeper 提供原子性、顺序一致性、临时节点(Ephemeral Node)和 Watch 机制,让这些逻辑变得可预测。

Zookeeper 的核心数据模型与常用 API

它的数据模型像一棵多层目录树,每个节点叫 ZNode,支持持久节点(PERSISTENT)、临时节点(EPHEMERAL)、有序节点(SEQUENTIAL)等类型组合。实际开发中高频使用的操作集中在:create()exists()getData()getChildren()addWatch()(或旧版 watcher 参数)。

  • create("/leader", data, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL):常用于选主,节点自动销毁意味着服务下线
  • getChildren("/workers", true):监听子节点变化,配合临时节点实现服务发现
  • exists("/config", new Watcher() {...}):监听配置路径是否存在或被删除,适合动态配置推送

注意:Watcher 是一次性触发的,回调里需重新注册;ZooKeeper 客户端默认不自动重连,生产环境务必配置 retryPolicy

常见误用场景与坑点

很多团队把 ZooKeeper 当成 KV 存储用,结果性能崩了、连接打满、Watch 泄漏。它不是为高吞吐读写设计的——单节点写入能力通常在几千 TPS,且所有写请求都走 Leader,网络延迟敏感。

  • 不要存大文件或频繁更新的业务数据:ZNode 数据上限默认 1MB,超限会抛 org.apache.zookeeper.KeeperException$ConnectionLossException
  • 不要在 Watch 回调里做耗时操作(如 DB 查询、HTTP 调用),会导致会话超时(SESSION_EXPIRED
  • 使用 Curator 框架时,InterProcessMutex 的锁路径必须全局唯一,否则多个服务可能误认为自己持有了同一把锁
  • 集群部署至少 3 台(推荐奇数台),2 台时挂一台就不可用;myid 文件内容与 zoo.cfgserver.x 必须严格对应

替代方案什么时候更合适

ZooKeeper 在强一致性和复杂协调场景仍有优势,但若只是轻量服务发现或配置管理,可以考虑更现代的选项:

  • 服务发现:Consul(健康检查 + DNS/HTTP 接口 + KV)、Nacos(AP 模式下可用性更高,支持长轮询+UDP推送)
  • 分布式锁:Redis + Redlock(注意时钟漂移问题)、Etcd(基于 Raft,API 更简洁,lease + compareAndSwap 组合更可控)
  • 配置中心:Apollo(配置灰度、权限粒度细)、Nacos(配置监听语义更接近 ZooKeeper 的 Watch)

真正关键的不是换不换,而是清楚当前业务对一致性(CP 还是 AP)、延迟容忍、运维成本的排序。ZooKeeper 的

Watch 语义和 ZNode 状态机模型一旦理解透,迁移到 Etcd 或 Nacos 的概念迁移成本其实很低。