跳转到内容

ZooKeeper 面试题

12 道题
分类
中间件
题目数
12 道
已阅读 0 / 12 题
1 ZooKeeper 是什么?核心定位与设计目标

答案:

ZooKeeper 是 Apache 顶级开源项目,提供高可用的分布式协调服务(Distributed Coordination Service),将分布式一致性协议封装为简单的 API,让应用聚焦业务逻辑而非底层协调难题。

核心定位:CP 系统(Consistency + Partition tolerance),牺牲部分可用性换取强一致性,写操作性能上限为 Leader 节点单点吞吐(典型 1-2 万 QPS)。

设计目标

目标含义
简单数据模型共享的树形命名空间(hierarchical namespace),类似文件系统
强一致性客户端看到的视图保证 FIFO 顺序,写操作全局有序(zxid)
高可用半数以上节点存活即可对外提供服务(≥ ⌊n/2⌋+1)
顺序访问所有请求分配全局单调递增 zxid,因果顺序保证
轻量 API核心 API 仅 10 余个(create/delete/exists/getChildren/getData/setData/sync 等)
Watch 机制客户端可在节点上注册 Watcher,节点变化时收到一次性通知

典型应用场景:配置中心(KOPF)、命名服务(Dubbo)、分布式锁(Curator)、集群选举(HDFS NameNode HA)、Master 选举(Kafka Controller)、分布式队列、负载均衡。

与 etcd 区别:etcd 基于 Raft,ZooKeeper 基于 ZAB(ZooKeeper Atomic Broadcast),两者协议相近但 ZAB 协议针对 ZK 特性深度定制。

2 ZAB 协议详解:广播模式与恢复模式

答案:

ZAB(ZooKeeper Atomic Broadcast)是 ZooKeeper 专为分布式协调设计的原子广播协议,包含两个核心模式:广播模式(Broadcast)和恢复模式(Recovery)。

广播模式(正常运行态)

写请求通过 Leader 处理,使用二阶段提交思想的简化协议:

Client → Follower → Leader
  Leader 分配全局单调递增 zxid(64 位:高 32 位 epoch,低 32 位 counter)
  Leader 为 Follower 发送 PROPOSAL(含 zxid + 数据)
  Follower 写入本地日志后回复 ACK
  Leader 收到 ≥ 半数 Follower 的 ACK 后发送 COMMIT
  Follower 提交事务并响应 Client

关键特性:

  • 全局有序:所有事务有唯一 zxid,因果顺序由 zxid 递增保证
  • 多数派确认:超过半数的 Follower ACK 即提交,避免脑裂
  • 简化 2PC:不需要协调日志(无 prepare 阶段),Leader 自带顺序约束

恢复模式(崩溃恢复态)

Leader 崩溃或集群启动时进入恢复模式,需解决两个核心问题:

  1. 选举新 Leader:拥有最大 zxid 的 Follower 优先当选(数据最新)
  2. 数据对齐:丢弃未提交的事务(PROPOSAL 但未 COMMIT),重放已提交但未应用的事务

ZAB 与 Paxos 区别:Paxos 是通用一致性算法,ZAB 是为 ZK 定制的包含广播+恢复的工程化协议,主备模型(Leader/Follower)而非对等模型,更易实现主从数据同步。

# 查看当前节点状态
echo srvr | nc 127.0.0.1 2181
# 输出关键字段:Mode (leader/follower/standalone), Zxid
3 Leader 选举机制详解

答案:

Leader 选举是 ZAB 恢复模式的核心环节,确保集群在 Leader 失效后能快速选出新 Leader 并恢复服务。

选举触发条件

  • 集群启动时无 Leader
  • Leader 宕机(超过 sessionTimeout 未响应)
  • Follower 数量不足(少于半数)

FastLeaderElection 算法(默认)

每个 Server 启动后进入 LOOKING 状态,核心流程:

  1. 投票初始化:每台服务器先投自己(myid, zxid, epoch)
  2. 广播投票:向所有其他 Server 发送投票
  3. 投票比较:收到对方投票后,按规则更新自己的票
  4. 规则
    • epoch 优先:epoch 大的胜出(防止旧 Leader 复活)
    • zxid 次之:zxid 大的胜出(数据更新)
    • myid 最后:myid 大的胜出(确定性选择)
  5. 过半确认:当某台 Server 收到超过半数相同的投票时,选举结束,该 Server 当选 Leader

选举时间:典型集群 3-5 个节点,选举耗时 200ms-2s,受 initLimit(默认 10,tickTime=2s 故 20s)和 syncLimit 影响。

配置示例

# 5 节点集群 zoo.cfg
tickTime=2000
initLimit=10      # 初始化连接超时(tickTime 倍数)
syncLimit=5       # Follower 与 Leader 同步超时
dataDir=/data/zk
dataLogDir=/data/zk/log
clientPort=2181
server.1=zk1:2888:3888   # myid=1, 内部通信 2888, 选举 3888
server.2=zk2:2888:3888
server.3=zk3:2888:3888
server.4=zk4:2888:3888
server.5=zk5:2888:3888

注意事项

  • 集群节点数推荐奇数(3/5/7),减少脑裂概率同时节省资源
  • 跨机房部署需配置 group/weight 参数(基于 ZAB 的 Hierarchical Quorums)
  • 选举期间集群不可写(CP 特性),影响业务可用性
4 Znode 数据模型:树形命名空间与节点类型

答案:

ZooKeeper 数据模型采用层次化命名空间(Hierarchical Namespace),类似 Unix 文件系统,但每个节点(Znode)既可存储数据(≤ 1MB)又可拥有子节点。

Znode 路径

/
├── /services          # 服务注册根
│   ├── /order-service
│   │   ├── /instance-0001   # 临时顺序节点
│   │   ├── /instance-0002
│   │   └── /instance-0003
│   └── /pay-service
├── /config            # 配置中心
│   ├── /db.properties
│   └── /app.yaml
├── /locks             # 分布式锁
│   ├── /order-lock
│   └── /pay-lock
└── /election          # Master 选举
    └── /leader

Znode 4 种类型

类型生命周期编号用途
持久节点(PERSISTENT)客户端断开后仍存在配置、命名服务
临时节点(EPHEMERAL)客户端 session 失效即删除服务注册、心跳
持久顺序节点(PERSISTENT_SEQUENTIAL)持久是(递增 10 位数字)分布式队列
临时顺序节点(EPHEMERAL_SEQUENTIAL)session 失效删除分布式锁、Master 选举

Znode 状态信息(Stat)

字段含义
czxid创建时的事务 zxid
mzxid最后修改的 zxid
ctime创建时间(毫秒)
mtime最后修改时间
version数据版本号(乐观锁)
cversion子节点版本号
aversionACL 版本号
ephemeralOwner临时节点拥有者 sessionId;持久节点为 0
dataLength数据长度(字节)
numChildren子节点数
pzxid子节点列表最后修改的 zxid

存储限制:单个 Znode 数据 ≤ 1MB(设计为元数据存储而非大数据),建议 ≤ 数百 KB 以保证性能。

5 Watcher 事件机制详解

答案:

Watcher 是 ZooKeeper 提供的分布式事件通知机制,允许客户端在节点上注册监听器,节点状态变化时服务端主动推送通知。

核心特性

  1. 一次性触发:Watcher 被触发后即失效,需重新注册才能继续监听
  2. 异步通知:服务端通过客户端连接异步推送事件
  3. 轻量级:Watcher 数据结构仅包含通知类型、节点路径、节点状态
  4. 全局有序:通知顺序与 zxid 顺序一致

Watcher 事件类型

事件类型触发条件
NodeCreated监听节点被创建
NodeDeleted监听节点被删除
NodeDataChanged监听节点数据变更
NodeChildrenChanged监听节点的子节点列表变化

核心 API

# getData 注册数据变更 Watcher
getData /services/order, true

# getChildren 注册子节点变更 Watcher
ls /services/order true

# exists 注册存在性变更 Watcher(用于不存在节点)
stat /lock/order true

Java 示例

// 一次性 Watcher
Stat stat = zk.exists("/config/db", watchedEvent -> {
    if (watchedEvent.getType() == Watcher.Event.EventType.NodeDataChanged) {
        // 重新获取数据
        byte[] newData = zk.getData("/config/db", false, null);
        reloadConfig(newData);
        // 关键:重新注册 Watcher(一次性失效)
        reRegisterWatcher();
    }
});

// 持久 Watcher(推荐 Curator)
NodeCache nodeCache = new NodeCache(client, "/config/db");
nodeCache.start(true);
nodeCache.getListenable().addListener(() -> {
    ChildData data = nodeCache.getCurrentData();
    reloadConfig(data.getData());
});

Watcher 使用陷阱

  • 羊群效应:大量客户端监听同一节点,节点变化时全部收到通知导致瞬时高负载。解决方案:客户端本地缓存 + 节流刷新
  • 丢失通知:客户端与服务器断开重连期间发生的事件可能丢失,需在重连后主动 sync 拉取最新状态
  • 递归 Watcher 缺失:ZooKeeper 不支持递归监听子节点,需通过 Persistent Watcher(3.6+)或应用层递归注册
6 临时节点与顺序节点的应用场景

答案:

临时节点(Ephemeral Znode)和顺序节点(Sequential Znode)是 ZooKeeper 实现分布式协调原语的两大基石,二者组合可实现多种分布式模式。

核心特性对比

特性临时节点顺序节点
生命周期与 session 绑定与节点类型绑定(持久/临时)
自动清理session 超时自动删除不自动清理
编号方式无编号名称后追加 10 位递增序号(如 /lock/order-0000000001
典型用途服务注册、心跳检测分布式锁、队列、选举

应用 1:服务注册与发现

# 服务实例启动时注册临时节点
create -e -s /services/order-svc/instance- "${INSTANCE_INFO}"

# 客户端通过 getChildren 发现所有实例
ls /services/order-svc

# 实例宕机 → session 超时 → 临时节点自动删除
# 客户端收到 NodeChildrenChanged 事件后刷新服务列表

应用 2:分布式锁(基于临时顺序节点)

// 步骤 1:所有客户端在 /lock/order 下创建临时顺序节点
String myPath = zk.create("/lock/order/guid-", null,
    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// myPath = /lock/order/guid-0000000001

// 步骤 2:获取所有子节点,排序最小的获取锁
List<String> children = zk.getChildren("/lock/order", false);
Collections.sort(children);
if (myPath.equals("/lock/order/" + children.get(0))) {
    // 获得锁
} else {
    // 监听前一个节点的删除事件
    String prevNode = children.get(indexOf(myPath) - 1);
    zk.exists("/lock/order/" + prevNode, watcher);
}

// 步骤 3:释放锁(删除节点或断开连接)
zk.delete(myPath, -1);

应用 3:Master 选举

// 多个客户端同时创建临时顺序节点,最小者当选 Master
String myPath = zk.create("/election/leader-", null,
    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zk.getChildren("/election", false);
if (myPath.endsWith(children.get(0))) {
    becomeMaster();
} else {
    // 监听最小节点删除事件,等待晋升
}

应用 4:分布式队列(FIFO)

// 生产者
zk.create("/queue/task-", data, ..., CreateMode.PERSISTENT_SEQUENTIAL);

// 消费者
List<String> tasks = zk.getChildren("/queue", false);
Collections.sort(tasks);
// 处理 /queue/task-0000000001

Curator 框架封装InterProcessMutex(可重入锁)、InterProcessSemaphoreMutex(不可重入锁)、LeaderLatch(Leader 选举)、DistributedQueue(分布式队列),屏蔽底层细节,强烈推荐生产使用。

7 典型应用场景:配置中心、命名服务、分布式锁、集群选举

答案:

ZooKeeper 四大经典应用场景覆盖了分布式系统的核心协调需求。

场景 1:配置中心(Configuration Center)

# 写入配置(持久节点)
create /config/app.properties "host=db.example.com\nport=3306"
set /config/app.properties "host=newdb.example.com\nport=3306"

# 客户端 Watcher 监听配置变更
stat /config/app.properties true

优势

  • 集中管理:所有节点共享一份配置
  • 实时推送:变更秒级生效
  • 版本控制:Stat 中 version 字段支持乐观锁
  • 高可用:ZK 集群保证配置服务可用

生产实践

  • 大配置拆分到多个子节点(避免单节点 > 1MB)
  • 客户端本地缓存 + Watcher 触发全量刷新
  • 通过 ACL 控制读写权限

场景 2:命名服务(Naming Service)

/services
  /order-service
    /instance-0001 (临时节点, host:port, 负载权重)
    /instance-0002
  /pay-service
    /instance-0001

实现方式

  • 服务提供方启动时向 ZK 注册临时节点(携带服务地址)
  • 服务消费方监听服务目录,获取实时实例列表
  • 服务下线时临时节点自动清理,避免脏数据
  • 配合 Ribbon/Consul 等实现负载均衡与健康检查

场景 3:分布式锁(Distributed Lock)

基于临时顺序节点 + Watcher 实现,关键特性:

  • 互斥性:同一时刻仅一个客户端持锁
  • 死锁避免:临时节点 + session 超时自动释放
  • 可重入:同一线程可多次获取同一锁(Curator 实现)
  • 公平性:FIFO 顺序节点保证先到先得

两种实现对比

方案优点缺点
临时节点非公平锁实现简单羊群效应,锁释放时所有等待者同时唤醒
临时顺序节点公平锁公平,无羊群实现复杂,需监听前驱节点

场景 4:集群选举(Master Election)

// Curator LeaderLatch 实现
LeaderLatch leaderLatch = new LeaderLatch(client, "/election/master");
leaderLatch.start();
if (leaderLatch.hasLeadership()) {
    // 执行业务逻辑(如 HDFS NameNode、YARN ResourceManager)
} else {
    leaderLatch.getLeader().getId(); // 获取当前 Master ID
}

应用案例

  • HDFS NameNode HA(基于 JournalNode + ZKFC)
  • YARN ResourceManager HA
  • Kafka Controller(KRaft 模式前依赖 ZK)
  • Flink JobManager HA
  • ElasticSearch Master 选举(旧版本)

场景扩展:分布式屏障(Barrier)、分布式计数器、分布式 ID 生成器、集群成员管理、Leader/Follower 协作。

8 ZooKeeper 与 etcd 的对比分析

答案:

ZooKeeper 和 etcd 都是主流的分布式协调服务,在协议、接口、生态上各有侧重。

核心对比表

维度ZooKeeperetcd
一致性协议ZAB(ZooKeeper Atomic Broadcast)Raft
API 协议自定义协议(jute 序列化),需 zkclient/curatorHTTP/JSON + gRPC(v3+)
数据模型文件系统树形(Znode 层级)扁平的 K-V(key 目录)
Watch 机制一次性,连接断开后丢失长连接 + 进度保持(progress notify)
租约/TTL依赖 session 超时原生支持 key TTL
典型部署规模3-7 节点,奇数3-5 节点,偶数奇数皆可
写性能1-2 万 QPS(Leader 单点)数千-1 万 QPS
读性能高(本地读)高(Follower 读)
客户端语言Java 为主,原生支持 C/Python多语言(Go/Java/Python/JS)
运维复杂度中等(需 jvm 调优)较低(Go 编译单二进制)
社区生态Hadoop 生态(HBase/Kafka/HDFS)Kubernetes 生态(事实标准)
存储后端自有(snapshot + txnlog)bbolt(B+ 树)
配置管理工具zkCli、zkAdminetcdctl、etcd 客户端库
多版本并发控制乐观锁(version 字段)revision + ModRevision(事务级)
生产案例HBase、Kafka、Dubbo、SolrK8s、Istio、Calico、Rook

选型建议

选 ZooKeeper

  • 项目已深度集成 ZK(Hadoop 生态)
  • 需 Java 原生客户端,强 Watcher 模型
  • 多机房级 Hierarchical Quorums 需求
  • 已是企业技术栈标准组件

选 etcd

  • 云原生 / Kubernetes 生态
  • 需多语言客户端、HTTP/JSON API
  • 需原生 key TTL 自动过期
  • 中小规模部署,关注运维便捷性

趋势观察:etcd 借助 K8s 占据云原生主导地位,ZooKeeper 在新项目中应用减少但 Hadoop 大数据生态仍不可替代。Kafka 4.0 已全面迁移到 KRaft 模式,不再依赖 ZK。

性能对比实测参考

场景ZK 3.8 (5 节点)etcd 3.5 (5 节点)
写延迟 p995-15ms10-30ms
读延迟 p991-3ms3-10ms
最大写 QPS1.5-2.5 万0.5-1 万
Watch 推送延迟< 10ms< 50ms
9 运维监控:mntr 命令与四字命令详解

答案:

ZooKeeper 内置丰富的运维命令,最常用的是 mntr(输出完整监控指标)和四字命令(Four Letter Words)系列。

mntr 命令(推荐用于生产监控)

echo mntr | nc 127.0.0.1 2181

输出关键指标

指标含义告警阈值
zk_versionZK 版本-
zk_server_state节点状态(leader/follower/standalone)非 leader 需关注
zk_num_alive_connections活跃连接数突增可能客户端异常
zk_avg_latency平均延迟(ms)> 100ms 需排查
zk_max_latency最大延迟> 1000ms 需排查
zk_outstanding_requests堆积请求数> 100 需扩容
zk_packets_sent/received网络流量监控网络瓶颈
zk_zxid当前 zxid集群各节点应接近(数据一致性)
zk_open_file_descriptor_count打开 FD 数接近 ulimit 需调优
zk_followersFollower 数(仅 Leader 输出)应等于集群配置数
zk_synced_followers已同步的 Follower 数应等于 followers
zk_pending_syncs待同步 Follower 数> 0 持续增长需告警
zk_watch_countWatcher 总数监控业务规模
zk_ephemerals_count临时节点数-
zk_approximate_data_size数据总大小监控增长趋势
snap_countsnapshot 间隔事务数默认 100000

四字命令(运维速查)

命令功能用途
stat服务状态 + 简要指标快速健康检查
srvr服务端信息(替代 stat)详细版本、角色
conf集群配置排查配置问题
cons客户端连接详情排查连接泄漏
crst重置连接统计测试用
dump未处理会话和临时节点诊断 session 问题
envi运行环境变量JVM/系统信息
ruok“Are you ok?” 检查存活心跳探活
mntr完整监控指标Prometheus 采集
wchsWatch 概览Watch 健康度
wchcWatch 按连接分组定位 Watch 泄漏
wchpWatch 按路径分组定位热点路径
srst服务器统计重置性能测试用
kill关闭会话(生产慎用)强制断开指定会话

生产监控集成(Prometheus + JMX)

# zk 配置文件启用 JMX
JMXHOST=""
JMXPORT="9999"

# 启动 JMX Exporter
java -javaagent:/opt/jmx_prometheus_javaagent.jar=12345:/opt/jmx-exporter-config.yml \
  -jar zookeeper.jar start

JMX 关键指标

  • ZooKeeperServiceProcessor0_PacketsReceived
  • ZooKeeperServiceProcessor0_PingReceived
  • ZooKeeperServiceProcessor0_NumAliveConnections
  • StandaloneServer_ServerStats_AvgLatency

Grafana 监控大盘建议面板

  • 集群拓扑(Leader/Follower 角色、zxid 同步)
  • 连接数趋势(活跃/总连接、断开速率)
  • 延迟分布(avg/max/分钟 P99)
  • 写请求堆积(outstanding_requests、pending_syncs)
  • 存储容量(数据大小、snapshot/txnlog 占用)
10 脑裂(Split-Brain)问题与防护

答案:

脑裂(Split-Brain)是分布式系统的经典问题,ZooKeeper 通过 ZAB 协议和多数派(Quorum)机制从根本上避免脑裂,但需正确配置和运维。

脑裂成因

网络分区(Network Partition)导致集群分裂为多个子集群,每个子集群各自选出 Leader 并对外提供服务,破坏数据一致性。

ZK 防护机制

  1. 多数派提交:写操作需 ≥ 半数 Follower ACK 才能提交

    • 5 节点集群:3 节点可写,2 节点分区不可写
    • 少数派(≤ ⌊n/2⌋)自动停止服务,仅可读
    • 即使双机房故障,最多只有 1 个机房可写
  2. epoch 隔离:每次新 Leader 选举时递增 epoch,旧 Leader 收到比自己 epoch 大的消息自动降级为 Follower

    • 防止旧 Leader 在分区恢复后继续写数据
    • Zxid 高 32 位为 epoch 标识
  3. zxid 顺序约束:所有写操作有全局递增 zxid,新 Leader 必须提供最大 zxid 的事务日志

    • 即使旧 Leader 复活,缺少新 Leader 的事务也无法提交

脑裂检测与告警

# 检查各节点状态是否一致
for i in 1 2 3 4 5; do
  echo "=== zk$i ==="
  echo srvr | nc zk$i 2181 | grep -E "Mode|Zxid"
done

# 期望输出:Mode 一致,Zxid 接近
# zk1: Mode: leader   Zxid: 0x200000123
# zk2: Mode: follower Zxid: 0x200000123
# zk3: Mode: follower Zxid: 0x200000123

异常情况

现象含义处置
多个节点显示 leader严重:协议层异常立即人工介入,保留现场
zxid 差异大落后节点数据陈旧检查网络、磁盘 IO
Mode 频繁切换选举抖动优化 JVM、加大 tickTime
半数节点 offline集群不可写恢复节点或扩容

跨机房部署方案

方案拓扑Quorum 配置
同城双机房机 A 3 节点 + 机 B 2 节点多数派在机 A,机 B 故障不影响
两地三中心中心 1(2 节点)+ 中心 2(1 节点)+ 中心 3(2 节点)任意两中心可用即可写
Hierarchical Quorums自定义权重group.1=1:2:3 weight.1=...

最佳实践

  • 部署奇数节点(3/5/7),偶数不增加可用性
  • 同机房优先,跨机房需保证专线质量(延迟 < 10ms)
  • 监控 zk_election_time 指标,选举次数持续为 0 是健康表现
  • Leader 节点不要和应用共用机器,资源竞争导致脑裂风险
11 Chroot 模式与多租户隔离

答案:

Chroot 是 ZooKeeper 的命名空间隔离机制,允许客户端连接到集群后只能访问指定子路径,类似 Unix 的 chroot jail。

核心概念

完整命名空间:
/
├── /app-a
│   ├── /config
│   └── /locks
├── /app-b
│   ├── /config
│   └── /services
└── /shared
    └── /discovery

App-A 客户端连接后 chroot=/app-a:
  /config → 实际访问 /app-a/config
  /locks → 实际访问 /app-a/locks

配置方法

# 连接字符串中追加 chroot 路径
zkCli.sh -server zk1:2181,zk2:2181,zk3:2181/app-a

# 编程方式
String connectString = "zk1:2181,zk2:2181,zk3:2181/app-a";
ZooKeeper zk = new ZooKeeper(connectString, 30000, watcher);

多租户隔离场景

场景用途
SaaS 多租户不同租户数据完全隔离,避免误操作影响他人
环境隔离dev/test/staging/prod 共用集群,降低成本
应用分组同一组织多应用共用 ZK 集群
权限控制配合 ACL 实现精细化权限(digest/IP/scheme)

Chroot 与命名空间区别

特性Chroot独立集群
隔离强度弱(共享底层存储)强(独立数据)
运维成本低(单集群)高(多集群)
资源利用低(独立资源)
故障影响集群故障全租户单租户故障
适用规模中小大型多业务

ACL 权限控制(配合 Chroot)

# 创建节点时指定 ACL
create /config/db "jdbc:mysql://..." digest:user:passwd:cdrwa

# ACL 模式:
# - world:anyone(默认)
# - auth:user
# - digest:user:BASE64(SHA1(password))  生产推荐
# - ip:192.168.1.0/24
# - x509:subject
# - sasl:user

# 权限位:cdrwa = create/delete/read/write/admin

生产实践

  • Chroot 路径建议在客户端 SDK 层统一封装,业务无感知
  • 配合 Curator 的 PathChildrenCache 等高级 API 简化开发
  • 重要业务独立集群(金融交易、订单),次要业务共享集群
  • 监控 Chroot 内的 Znode 数量,避免单一租户占用过多资源

限制

  • Chroot 路径必须在连接时指定,连接后无法切换
  • 客户端代码必须正确处理 chroot 路径(不要写绝对路径后又叠加 chroot)
  • Chroot 内的 Watcher 事件路径会自动包含 chroot 前缀
12 生产案例:Kafka 集群 ZooKeeper 故障与优化实践

答案:

以下案例基于真实生产环境 ZooKeeper 集群运维经验,涵盖故障、排查与优化全流程。

案例 1:ZK 集群频繁 Leader 选举导致 Kafka 服务抖动

现象

  • Kafka 集群出现间歇性 Produce/Consume 失败
  • ZK 服务端日志大量 ConnectionLoss 警告
  • 业务监控告警 Kafka 请求延迟 P99 突增到 5s+

根因排查

# 步骤 1:检查 ZK 各节点状态
echo mntr | nc zk1 2181 | grep -E "avg_latency|outstanding_requests|followers"
# 发现 zk1 的 avg_latency 持续 > 200ms,outstanding_requests > 500

# 步骤 2:检查 GC 日志
jstat -gcutil <pid> 1000
# 发现 Full GC 频率 30s 一次,单次 1-2s

# 步骤 3:检查堆内存配置
echo envi | nc zk1 2181 | grep -i heap
# 发现仅 2GB 堆,业务量增长后不足

根因:ZK 节点 Full GC 频繁导致 STW,期间无法响应 Leader 心跳,被动触发新一轮选举,形成"抖动-选举-抖动"恶性循环。

优化措施

# 1. 堆内存扩容
export KAFKA_HEAP_OPTS="-Xms8g -Xmx8g"  # 推荐 8-16G

# 2. 启用 G1GC(4.0+ 默认)
export KAFKA_JVM_PERFORMANCE_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200"

# 3. 调大 tickTime 和 initLimit
tickTime=4000         # 4s(默认 2s),降低对网络抖动敏感度
initLimit=30          # 120s 初始化超时
syncLimit=10          # 40s 同步超时

# 4. 独立磁盘用于事务日志
dataLogDir=/data/fast-ssd/zk-log  # 独立 SSD,避免与 snapshot 同盘 IO 竞争

效果:GC 频率降到 1 小时 1 次,选举次数降为 0,Kafka 延迟恢复 < 50ms。

案例 2:Znode 数量爆炸导致 ZK 性能下降

现象

  • 业务方使用 ZK 做服务注册,单实例创建 100+ 子节点
  • 集群部署 500+ 服务实例后 ZK 数据量 > 50GB
  • getChildren 延迟从 5ms 飙升到 500ms

优化方案

// 反例:每个服务实例创建大量子节点
create /services/order-svc/instance-001/health
create /services/order-svc/instance-001/metrics
create /services/order-svc/instance-001/config
// 单实例 100+ 节点,500 实例 = 5 万+ 节点

// 正例 1:单实例多数据合并到一个 Znode
create -e /services/order-svc/instance-001 "{\"health\":\"ok\",\"qps\":100,...}"

// 正例 2:使用有序节点 + JSON 数据,限制子节点数
// 推荐:实例元数据写入 Znode data,目录只用临时节点标识存在性

架构优化

  • 改用 etcd v3 的扁平 K-V 模型(key 包含层级)
  • 引入 Consul 替代 ZK 做服务发现
  • 限制单路径 Znode 数 ≤ 1 万,单 Znode data ≤ 100KB

案例 3:ZK 集群脑裂应急处理

现象

  • 5 节点 ZK 集群跨双机房部署
  • 机房 A 网络抖动,机房 B 4 节点形成多数派选出新 Leader
  • 5 分钟后机房 A 恢复,旧 Leader 仍持有连接,尝试写入

应急处理

# 步骤 1:确认是否真的发生脑裂(极少见,但需验证)
for node in zk1 zk2 zk3 zk4 zk5; do
  echo "=== $node ==="
  echo srvr | nc $node 2181 | grep -E "Mode|Zxid"
done

# 步骤 2:如果发现多个 Leader,强制重启旧 Leader
ssh zk1 "kill -9 $(pgrep -f QuorumPeerMain)"

# 步骤 3:清理旧 Leader 的临时数据
rm -rf /data/zk/version-2/*

# 步骤 4:旧节点以全新状态加入新集群
/data/zookeeper/bin/zkServer.sh start

预防措施

  • 部署前进行网络分区演练(ChaosBlade 注入故障)
  • 配置 quorumListenOnAllIPs=true 监听所有网卡
  • 监控 ServerState 指标,多 Leader 立即告警
  • 关键业务使用独立的 ZK 集群,避免共享故障域

案例 4:Watch 泄漏导致 OOM

现象

  • 某个 Dubbo 服务消费者反复重启,ZK 服务端内存持续增长
  • 客户端收到大量失效通知,业务逻辑异常

根因:客户端代码未在断开连接时清理 Watcher,每次重连都注册新的 Watcher。

修复

// 反例:每次连接都注册
zk.exists("/config", new Watcher() {
    @Override
    public void process(WatchedEvent event) {
        // 重新加载配置但未重新注册
    }
});

// 正例:使用 Curator 的 NodeCache(自动管理 Watcher)
NodeCache nodeCache = new NodeCache(client, "/config");
nodeCache.start(true);
nodeCache.getListenable().addListener(() -> reload());

// 资源清理
Runtime.getRuntime().addShutdownHook(() -> {
    CloseableUtils.closeQuietly(nodeCache);
    CloseableUtils.closeQuietly(client);
});

监控指标

# 监控 Watch 数量
echo wchp | nc zk1 2181 | head -20
# 单路径 Watch > 1000 需告警

生产优化清单

维度优化项建议值
JVM堆大小8-16G
JVMGC 算法G1GC(4.0+ 默认)
配置tickTime3000-5000ms
配置initLimit/syncLimit30/10
配置autopurge.snapRetainCount3
存储独立 SSD 用于 txnlog必须
存储定期清理 snapshot/txnlog3-5 份保留
网络节点间延迟< 10ms
网络防火墙放行2888/3888/2181
监控ZK Exporter集成 Prometheus
告警关键指标latency/connections/elections