ZooKeeper 部署运维与监控实战¶
作者:JiangChong | 撰写时间:2026年10月
适用场景:动手搭建 ZooKeeper 集群、用 zkCli 做日常操作、搭建监控告警、排查 ZK 故障。建议先读 ZooKeeper 分布式协调服务核心概念 建立概念,再读本文动手实操。环境承接 HDFS 高可用集群部署实战(QJM 手动切换版):本文的 ZK 集群部署在 HDFS 08 篇搭好的 3 台机器上,第 5 节把该集群升级为自动故障转移。
阅读前回顾(30 秒)¶
ZooKeeper 分布式协调服务核心概念 讲了四件事:znode 四种类型(临时节点「人走茶凉」)、session 心跳、watch 一次性通知、Leader/Follower + ZAB 多数派。本文把它们全部落到命令行上:
- 搭:3 节点集群部署到 HDFS 08 篇的既有环境(zoo.cfg / myid / 启动验证)
- 用:zkCli 创建临时节点、watch 一个配置节点
- 看:四字命令监控集群健康
- 修:5 个典型故障的排查路径
- 联:与 HDFS 高可用(QJM、ZKFC、Federation) 的 HA 联动验证
关联文章¶
- ZooKeeper 分布式协调服务核心概念 — 前置理论:znode/session/watch/ZAB
- HDFS 高可用(QJM、ZKFC、Federation) — 第 5 节升级的理论依据(ZKFC / 自动故障转移)
- HDFS 高可用集群部署实战(QJM 手动切换版) — 环境承接:本文部署与第 5 节升级所基于的集群
- Apache Kafka — ZK 的另一大用户(Kafka 篇规划中)
理解全文脉络¶
按「搭 → 用 → 看 → 修 → 联」组织:第 1 节部署(在 HDFS 08 篇的既有环境上搭起 3 节点集群),第 2 节客户端实操(临时节点/watch 亲手演示一遍,理论立刻变手感),第 3 节监控(哪些指标必须盯、阈值多少),第 4 节排障(5 个高频故障场景),第 5 节把 HDFS 08 篇的手动切换集群升级为自动故障转移,并做联动验证。
本文命令在 Linux(systemd 发行版)上执行;版本以 3.5+ 为准(3.4 及更早版本部分行为不同,差异处已标注)。所有默认值以 Apache ZooKeeper 官方文档为准。
1 部署实战:3 节点集群从零搭建¶
整体四步:① 环境准备(每台机器通用,含防火墙放行)→ ② 写配置(zoo.cfg 三台相同、myid 各不相同)→ ③ 逐台启动并验证角色 → ④ 开机自启收尾。下面按这个顺序展开。
1.1 规划与环境准备¶
| 项目 | 建议 | 原因 |
|---|---|---|
| 节点数 | 3(生产)/ 1(学习) | 2F+1:3 台容 1 台故障;奇数台是标准形态(见 01 篇第 4.2 节) |
| 磁盘 | 数据盘分两目录:快照 + 事务日志 | 事务日志是顺序写,快照是周期性写;两者抢同一块盘会互相拖慢(见 1.2 的 dataLogDir) |
| JVM | 堆 2-4G 起步,GC 日志开启 | ZK 是 Java 进程,GC 停顿过长会直接导致会话超时、触发误切换 |
| 网络 | 三台低延迟互通(同机房) | 选举和广播都对延迟敏感 |
生产环境不要与业务节点混布——ZK 是仲裁层,一次硬件故障不能同时带走「裁判」和「选手」(与 01 篇运维要点一致)。
学习环境可以先用单机 standalone 模式:zoo.cfg 不写任何
server.N,一台机器即可起单节点 ZK(zkServer.sh status显示Mode: standalone)。它只是单点,适合本地实验,不能用于生产。
部署在哪?——承接 HDFS 08 篇的环境:本文的 3 台 ZK 机器就是 HDFS 高可用集群部署实战(QJM 手动切换版) 搭 HDFS HA 的那 3 台(hadoop001/2/3,对应下文的 zk1/zk2/zk3)。混布布局如下(测试环境标准做法,生产禁止混布):
| 机器 | HDFS 08 已有 | 本文新增 |
|---|---|---|
| hadoop001(= zk1) | NN1 + JN① + DataNode | ZK ① |
| hadoop002(= zk2) | NN2 + JN② + DataNode | ZK ② |
| hadoop003(= zk3) | JN③ + DataNode | ZK ③ |
内存预算:HDFS 08 各组件约 3-4G,再加 ZK 堆 2-4G,每台 8G 仍可跑。端口不冲突(ZK 2181/2888/3888 vs JN 8485 vs NN 8020/9870,HDFS 08 篇 1.2 已核对)。混布下做第 4 节的故障演练请停单个进程而不是关整机——整机关掉 = ZK + JN + NN 故障叠加,观察不到单点故障的干净现象。
环境准备(每台机器执行一遍;承接 HDFS 08 环境时大部分已就绪):
# ① 安装 JDK:ZK 3.5+ 需 8+(承接 HDFS 08 环境则已装好,跳过)
yum install -y java-1.8.0-openjdk-devel
java -version
# ② 创建专用用户(生产不要用 root 跑 ZK;与 Hadoop 的 hdfs 用户分开,互不干扰)
useradd -m -s /bin/bash zk
# ③ 下载并解压(版本号以官网最新 stable 为准)
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz
tar -xzf apache-zookeeper-3.8.4-bin.tar.gz -C /opt
ln -s /opt/apache-zookeeper-3.8.4-bin /opt/zookeeper # 软链接,日后升级只换指向
# ④ 创建数据目录(快照与事务日志分开)
mkdir -p /data/zookeeper/snapshot /data/zookeeper/txnlog
chown -R zk:zk /data/zookeeper /opt/zookeeper*
# ⑤ 主机名互认(承接 HDFS 08 环境则已配置,跳过):三台机器的 /etc/hosts 都加上这三行
# 172.20.65.69 hadoop001
# 172.20.65.71 hadoop002
# 172.20.65.70 hadoop003
# ⑥ 防火墙放行(承接 HDFS 08 环境则 ufw 已启用,直接追加放行即可;Debian 13 默认未启用防火墙,可跳过)
# ZK 三个端口:2181 客户端连接 / 2888 节点间数据同步 / 3888 选举
ufw allow 2181,2888,3888/tcp
# 若 HDFS 08 未配过防火墙(ufw 不存在或 inactive):
# apt install -y ufw
# ufw allow ssh # 先放行 22,否则远程 SSH 下 enable 会切断自己
# ufw --force enable
ufw status # 确认 2181/2888/3888 已放行(CentOS 系用 firewall-cmd --add-port={2181,2888,3888}/tcp)
1.2 zoo.cfg:一个文件讲清楚¶
安装很轻:官网下载二进制 tar 包解压即可(无需编译),把 /opt/zookeeper/conf/zoo_sample.cfg 复制为 zoo.cfg 再按下面修改。每台机器的 /opt/zookeeper/conf/zoo.cfg:
# 基础心跳单位:毫秒。心跳、超时、选举都以它为刻度
tickTime=2000
# 初始连接超时:Follower 追赶 Leader 同步数据的时间上限(单位 tick,10×2s=20s)
initLimit=10
# 运行期心跳超时:Leader-Follower 心跳延迟上限(单位 tick,5×2s=10s)
syncLimit=5
# 集群成员列表:server.<myid>=<主机>:<集群通信端口>:<选举端口>
server.1=hadoop001:2888:3888
server.2=hadoop002:2888:3888
server.3=hadoop003:2888:3888
# 客户端连接端口
clientPort=2181
# 快照目录(周期性落盘的内存状态)
dataDir=/data/zookeeper/snapshot
# 事务日志目录(每次写请求的 WAL,建议独立磁盘)
dataLogDir=/data/zookeeper/txnlog
# 连接限制:单个来源 IP 最多并发连接数(默认 60,防连接泄漏拖垮集群)
maxClientCnxns=60
# 自动清理:保留最近 3 份快照,每 24 小时清理一次(默认关闭!)
autopurge.snapRetainCount=3
autopurge.purgeInterval=24
# 四字命令白名单:3.5.3 起默认禁用,监控命令需放行(详见 3.1,不放行 ruok 会报 not in the whitelist)
4lw.commands.whitelist=srvr,mntr,ruok,stat,conf
逐项解释:
tickTime是一切时间参数的「最小刻度」:session 超时默认在2~20 个 tick之间(即 4s~40s),initLimit(默认 10 tick)是 Follower 追赶 Leader 的时间上限,syncLimit(默认 5 tick)是心跳延迟上限——排查「为什么会话超时/选举超时」时,先把 tickTime 换算成秒。server.N的 N 必须与每台机器dataDir/myid文件里的数字一致——这是集群成员的「身份证」,不一致是起不来的头号原因。autopurge默认是关闭的:不配置的话,快照和事务日志会无限堆积,磁盘写满后集群直接停止服务。生产必配。
快照与事务日志的关系(贯穿本文的两个概念,先在这里说清):快照(dataDir)是内存数据树约每 10 万次写(snapCount 可调)的完整落盘;事务日志(dataLogDir)是每次写请求的 WAL。节点恢复时先载入最近快照、再重放之后的日志。日志是高频顺序写、快照是低频大块写,特性不同,所以要分盘。
1.3 myid、启动验证与开机自启¶
# 三台机器分别执行(注意数字各不相同!)
echo 1 > /data/zookeeper/snapshot/myid # zk1
echo 2 > /data/zookeeper/snapshot/myid # zk2
echo 3 > /data/zookeeper/snapshot/myid # zk3
cat /data/zookeeper/snapshot/myid # 写完后回读确认数字正确
# 逐台启动,并查看状态
zkServer.sh start
zkServer.sh status
# zk1: Mode: follower
# zk2: Mode: leader ← 三台中恰好一台是 leader
# zk3: Mode: follower
启动顺序有个新手容易误判的点:第一台单独启动会一直卡在选主状态(凑不够多数派),第二台起来后选举才完成,第三台加入即各就各位——看到第一台「没动静」不要以为是故障。
zkServer.sh status 是部署完成后必须逐台执行的一步:三台都起来、恰好一个 leader、其余 follower,才算成功。如果某台报 Error contacting service,按第 4.1 节排查。
生产环境收尾——开机自启(防火墙放行见 1.1 ⑥):
systemd 服务文件(三台相同,/etc/systemd/system/zookeeper.service):
[Unit]
Description=Apache ZooKeeper
After=network.target
[Service]
User=zk
Environment="JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk"
ExecStart=/opt/zookeeper/bin/zkServer.sh start-foreground
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
Restart=on-failure
[Install]
WantedBy=multi-user.target
用 systemd 管理后,启停统一走 systemctl;zkServer.sh status 仍可随时用来查角色。
注意:手动启动过 ZK 再切 systemd,服务秒退 failed。现象:systemctl status zookeeper 显示 Duration: 820ms 即退出,日志出现 Start request repeated too quickly。原因:手动 zkServer.sh start 的进程仍占着 2181,systemd 实例 BindException 秒退,Restart=on-failure 重试 5 次后被 systemd 限流标记 failed。排查与修复:
journalctl -u zookeeper -n 30 --no-pager # 定位:见 BindException / Address already in use
ss -lntp | grep 2181 # 确认旧进程占用
pkill -f QuorumPeerMain # 杀掉手动启动的旧进程
systemctl reset-failed zookeeper # 清失败计数!重试 5 次已被限流,不 reset 拒绝再启动
systemctl start zookeeper
systemctl status zookeeper # 多等几秒再确认,不是秒退才算成功
1.4 起不来的高频原因¶
| 现象 | 原因 | 处理 |
|---|---|---|
启动报 java: command not found / Unsupported major.minor version |
JAVA_HOME 未配置或 JDK 版本不匹配(ZK 3.5+ 需 JDK 8+) | 检查 echo $JAVA_HOME、java -version |
启动报 Unable to access datadir / Permission denied |
dataDir/dataLogDir 目录不存在,或启动用户无写权限 | 创建目录并赋权给启动用户(如 chown -R zk:zk /data/zookeeper) |
| 启动即报 myid 相关错误 | myid 文件缺失、数字与 server.N 不符、含换行/空格 |
检查文件内容为纯数字、与配置一一对应 |
| 单台 status 正常但一直不是 leader/follower | 三台端口不通(2888/3888)、防火墙 | 端口互通检查;2888 是通信、3888 是选举,缺一不可 |
| 启动慢/卡在选举 | 时钟漂移、网络抖动 | 三台配置 NTP;选举对延迟敏感 |
2 客户端实操:zkCli 十分钟上手¶
连接任意一台:
进客户端后先敲 help——zkCli 内置命令帮助,命令用法忘掉时比翻文档快。
[zk: hadoop001:2181(CONNECTED) 0] help
ZooKeeper -server host:port -client-configuration properties-file cmd args
addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b|-N|-B] path
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
ls [-s] [-w] [-R] path
printwatches on|off
quit
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b|-N|-B val path
stat [-w] path
sync path
version
whoami
Command not found: Command not found help
2.1 基本操作:增删改查¶
# 创建持久节点
create /config "db_host=10.0.0.5"
# 读取(get 显示数据 + 版本号等元信息)
get /config
# db_host=10.0.0.5
# cZxid = 0x100000001 ← 创建该节点的事务 id,每次写 +1
# 更新
set /config "db_host=10.0.0.6"
# 查看状态
stat /config
# cZxid = 0x300000002
# ctime = Mon Aug 17 21:27:53 CST 2026
# mZxid = 0x300000003
# mtime = Mon Aug 17 21:28:03 CST 2026
# pZxid = 0x300000002
# cversion = 0
# dataVersion = 1
# aclVersion = 0
# ephemeralOwner = 0x0
# dataLength = 16
# numChildren = 0
# 删除(3.6 起用 deleteall 递归删除;rmr 已废弃)
delete /config
stat 输出里新手最常问的四个字段:
| 字段 | 含义 |
|---|---|
ephemeralOwner |
0x0 = 持久节点;非 0 = 临时节点,值就是持有者的会话 id |
dataVersion |
数据版本号,每次 set 后 +1(01 篇 2.3 节「乐观锁」用的就是它) |
numChildren |
子节点数量 |
ctime / mtime |
创建时间 / 最后修改时间 |
2.2 临时节点:亲手演示「人走茶凉」¶
# 先创建父节点 /members——ZK 的 create 不会自动建父节点(没有 mkdir -p),路径每一级都要先存在
create /members "group"
# 创建临时节点:-e
create -e /members/node1 "alive"
# 再开一个终端,用另一个会话查看——能看到
ls /members
# [node1]
# 关掉第一个终端(正常退出 = 连接干净关闭,节点立即消失)
# 在第二个终端再看:节点自动消失了
ls /members
# []
两个细节:
- ① 「等待超时」只发生在进程被
kill -9或网络突然断开这类「不辞而别」时——ZK 要等心跳停满 sessionTimeout 才判死;正常退出(zkCli 里close/quit)是干净关闭连接,服务器立即删除临时节点,实验不用干等。 - ② 想体验「不辞而别」,可以开第三个终端把持锁的 zkCli 进程
kill -9掉,观察等满超时后节点才消失的完整过程。
这个 30 秒的实验,就是 HDFS HA 里 ZKFC 锁的底层机制(HDFS 高可用(QJM、ZKFC、Federation) 第 4 节)——ZKFC 进程一死,会话超时,锁节点自动删除,另一个 ZKFC 才能抢到。
2.3 Watch:亲手演示「一次性门铃」¶
# 2.1 末尾已删除 /config,先重建(否则 get 报 Node does not exist)
create /config "db_host=10.0.0.5"
# 终端 A:对 /config 挂 watch 并读取
get -w /config
# 终端 B:修改
get /config
# db_host=10.0.0.5
set /config "db_host=10.0.0.7"
# 终端 A 会立即收到:
# WatchedEvent state:SyncConnected type:NodeDataChanged path:/config
注意:通知只来一次。终端 B 再改一次,终端 A 不会再收到——必须重新 get -w 再挂一次。这就是 01 篇第 3.2 节「watch 一次性」的含义,配置中心类应用(各节点 watch 配置节点)就是靠「收到通知 → 重读 → 重新注册」这个循环工作的。
2.4 顺序节点:排队与锁的基础¶
# 先建父节点 /queue
create /queue ""
# 创建持久顺序节点:-s,名字自动加自增序号
create -s /queue/task- ""
# Created /queue/task-0000000000
create -s /queue/task- ""
# Created /queue/task-0000000001
create -s /queue/task- ""
# Created /queue/task-0000000002
ls /queue
# [task-0000000000, task-0000000001, task-0000000002]
序号由 ZK 保证单调递增、全集群唯一——分布式锁的排队和任务队列都建立在这个特性上。此时 ls /queue 会看到三个节点按序号排好,多个客户端同时创建也不会撞名。
3 监控实战:四字命令与关键指标¶
3.1 四字命令:一行命令看健康¶
ZK 提供一套「四字命令」(4 letter words),通过客户端端口发送纯文本命令即可获取状态:
echo srvr | nc 127.0.0.1 2181
# Zookeeper version: 3.8.x
# Mode: follower ← 角色:leader / follower / standalone
# Node count: 1234 ← znode 总数
echo ruok | nc 127.0.0.1 2181
# imok ← 进程活着(只回 imok,其他不管)
没有 nc 时可用 telnet 替代:telnet 127.0.0.1 2181 后直接输入 srvr 回车。
⚠️ 3.5.3 起四字命令默认受白名单限制,需在 zoo.cfg 配置
4lw.commands.whitelist=srvr,mntr,ruok,stat,conf后重启(具体默认值以官方文档为准)。「mntr 没输出」最常见的原因就是没加白名单。
常用四字命令速查:
| 命令 | 用途 |
|---|---|
ruok |
进程是否存活(最轻量,适合探活) |
srvr |
角色 + 基本信息 |
stat |
连接数、收发包数、延迟 |
mntr |
机器可读的完整指标(监控采集用这个) |
conf |
查看生效的配置 |
3.2 mntr 关键指标解读¶
echo mntr | nc 127.0.0.1 2181
# zk_server_state leader ← 角色
# zk_avg_latency 2 ← 平均请求延迟 ms,持续 >20 要查
# zk_outstanding_requests 0 ← 积压请求,>0 持续说明处理不过来
# zk_num_alive_connections 15 ← 当前连接数
# zk_znode_count 1540 ← znode 总数
# zk_followers 2 ← leader 视角:应 = 2(3 节点集群)
# zk_synced_followers 2 ← 已同步的 follower,< followers 说明有人落后
# zk_pending_syncs 0 ← 待同步队列,持续 >0 是 follower 落后信号
# zk_approximate_data_size 2097152 ← 数据量,警惕异常增长
# zk_open_file_descriptor_count 45 ← fd 使用数,接近上限会拒绝连接
注意 zk_followers、zk_synced_followers、zk_pending_syncs 是 leader 视角的字段——在 follower 上执行 mntr 通常没有这几项,采集时请对 leader 执行(或全节点采集、只用 leader 的值)。
三个必须上告警的指标:
| 指标 | 告警阈值(参考) | 意味着 |
|---|---|---|
zk_synced_followers < zk_followers |
持续 >5 分钟 | 有 follower 落后/失联,多数派接近临界 |
zk_avg_latency |
持续 >20ms | 磁盘或 GC 问题,session 超时风险 |
zk_pending_syncs |
持续 >0 | follower 追不上 leader 的写速度 |
3.3 告警规则清单¶
- 进程探活(ruok/srvr)失败 → 立即告警
- leader 角色变化 → 告警(频繁切换是网络/GC 故障的典型信号)
zk_synced_followers下降 → 告警- 磁盘使用率 >80%(快照+事务日志目录)→ 告警
- JVM GC 停顿 > session 超时的一半 → 告警
4 故障排查实战:5 个典型场景¶
4.1 场景一:集群起不来¶
排查顺序:
- 进程起来了吗:
ps -ef | grep zookeeper、查看logs/下最新的.out(3.5+ 命名zookeeper-<用户>-server-<主机>.out); myid对不对:三台机器cat dataDir/myid,与 zoo.cfg 的server.N逐一比对——数字不符、多写一个换行,都会导致成员身份不识别;- 端口通不通:2888(通信)、3888(选举)逐台互测;
- 三台是否同版本、zoo.cfg 是否一致(server 列表不一致 = 各认各的成员名单)。
4.2 场景二:Leader 频繁切换(抖动)¶
排查顺序:
- 网络:三台之间丢包/延迟升高(选举对网络极敏感);
- GC 停顿:查 leader 的 GC 日志,full GC 超过 session 超时的一半即会误判失联;
- session 超时配置:超时太短(如网络环境差却只配 4s),抖动即触发会话超时 → 锁释放 → 重选。超时不是越短越好:短 = 发现快但误判多,长 = 误判少但故障发现慢,按网络质量取平衡(见 01 篇最佳实践第 9 条)。
观察切换轨迹:grep -E "LEADING|FOLLOWING" logs/zookeeper-*.out——每次角色切换都会留下日志,切换次数和时点一目了然。
4.3 场景三:磁盘写满,集群停止服务¶
原因:autopurge 默认关闭,快照(dataDir)与事务日志(dataLogDir)无限堆积。
处理:
- 紧急:确认多数派还有多少磁盘空间,必要时先扩容;
- 手工清理:停止服务 → 保留最近 N 份快照与对应事务日志,删除更旧的(务必逐台操作,保持多数派在线;不要直接删正在写入的日志);
- 根治:配置
autopurge.snapRetainCount=3+autopurge.purgeInterval=24,重启生效。
4.4 场景四:连接数暴涨¶
排查:客户端连接泄漏——应用创建 ZK 客户端后没有 close(典型:每次请求 new 一个客户端对象)。定位方法:echo stat | nc 127.0.0.1 2181 输出的 Clients: 段会列出每个连接的来源 IP 和端口,找出占大头的来源,逐台统计客户端进程数。
处理:修复客户端连接池;短期可调大 maxClientCnxns 救急,但不解决泄漏根源。
4.5 场景五:读到旧数据 / follower 落后¶
原因:读落在落后的 follower 上(01 篇第 4.3 节:顺序一致性,读可能滞后)。
处理:
- 看
zk_pending_syncs是否持续 >0——follower 追不上 leader 的写速度(磁盘慢是常见原因); - 应用侧:强一致读用
sync操作; - 追不上的 follower 排查磁盘 IO,必要时从集群摘除重做(数据由快照+日志恢复)。
5 与 HDFS HA 联动验证:升级为自动故障转移¶
前置:HDFS 高可用集群部署实战(QJM 手动切换版) 搭好的手动切换集群(本文的 ZK 就部署在它的机器上)。本节把该集群升级为自动故障转移,然后在升级后的环境上做联动实验。
5.1 升级:手动切换 → 自动故障转移¶
在 HDFS 08 篇的 hdfs-site.xml 里补两个参数(其余不动;完整注释版见 HDFS 高可用(QJM、ZKFC、Federation) 第 8 节):
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>hadoop001:2181,hadoop002:2181,hadoop003:2181</value>
</property>
同步到两台 NN 主机后,执行三步:
# 1. 在 ZK 里初始化 HA 状态(⚠️ 只在首次接入时执行一次;会清空该 nameservice 在 ZK 中的旧数据)
hdfs zkfc -formatZK
# 2. 重启两个 NameNode(配置变更生效;JN 与 DataNode 无需任何改动)
hdfs --daemon stop namenode && hdfs --daemon start namenode # hadoop001
hdfs --daemon stop namenode && hdfs --daemon start namenode # hadoop002
# 3. 启动两个 ZKFC(与各自 NN 同机)
hdfs --daemon start zkfc # hadoop001
hdfs --daemon start zkfc # hadoop002
# 验证:应为一主一备
hdfs haadmin -getAllServiceState
# nn1: active
# nn2: standby
从这一刻起,「谁当 Active」由 ZKFC 在 ZooKeeper 里抢锁自动决定——第 2.2 节的临时节点实验就是这把锁的底层机制。
5.2 看状态¶
# 两个 NameNode 的 HA 状态
hdfs haadmin -getAllServiceState
# nn1: active
# nn2: standby
# 在 ZK 里能看到 HA 的锁节点(临时节点!)
zkCli.sh -server hadoop001:2181 ls /hadoop-ha
# [mycluster]
zkCli.sh -server hadoop001:2181 get /hadoop-ha/mycluster/ActiveStandbyElectorLock
# nn1 ← 当前持锁者 = Active
在 ZK 里亲手看到这把锁,是「理论变手感」的关键一步——它就是 2.2 节演示的临时节点,持有者(ZKFC)失联后自动消失。
5.3 手动切换演练¶
# 优雅切换(维护场景的标准动作)
hdfs haadmin -failover nn1 nn2
hdfs haadmin -getAllServiceState
# nn1: standby
# nn2: active
# 再看 ZK 里的锁,持有者已经变成 nn2
进阶玩法:先在另一终端对锁节点挂 watch(get -w /hadoop-ha/mycluster/ActiveStandbyElectorLock),再执行上面的 failover——你会实时收到锁节点变化的通知。2.3 节的「一次性门铃」和 5.1 节的锁在这里串成一条线。
5.4 模拟 ZKFC 故障(仅测试环境)¶
# ⚠️ 仅测试环境:停止 nn1(Standby 侧)上的 zkfc 进程(生产用系统服务管理,如 systemctl stop hadoop-zkfc)
# 观察点:
# 1. ZK 里的锁节点纹丝不动——它由 nn2(Active 侧)的 ZKFC 会话持有,nn1 的 ZKFC 只是 watch 者
# 2. nn2 侧 zkfc 未受影响,继续持锁
# 3. hdfs haadmin -getAllServiceState 不变——Active 本身还活着,无需切换
补充一点:「怎么停」有讲究——systemctl stop 是干净退出、连接立即关闭;kill -9 才是模拟真实故障的「不辞而别」,若被 kill 的是持锁者,锁要等会话超时(默认 4s~40s 量级)才释放(01 篇 3.1 节)。
(进阶)把 nn1 的 zkfc 拉起来后,再停 nn2(Active 侧)的 zkfc:(过个几秒)这次锁真的释放,nn1 的 zkfc 抢到锁,触发 failover——hdfs haadmin -getAllServiceState 变成 nn1: active。一正一反两次实验,「ZKFC 挂了」的两种后果就都摸过了。
这个实验能直观看到「ZKFC 挂了 ≠ 立即切换」的含义:只有当 Active 侧 ZKFC 挂掉时才会触发 failover,Standby 侧 ZKFC 挂了只是「自动切换能力少了一半」,是隐性单点——这也是 HA 篇最佳实践第 5 条要求监控 ZKFC 的原因。
6 工具箱速查¶
| 目的 | 命令 | 说明 |
|---|---|---|
| 启动/状态/停止 | zkServer.sh start / status / stop |
status 逐台必查 |
| 进客户端 | zkCli.sh -server <host>:2181 |
连任意一台 |
| 创建临时节点 | create -e /path data |
会话断开自动删除 |
| 创建顺序节点 | create -s /path/prefix- data |
全集群唯一自增序号 |
| 挂 watch 读 | get -w /path |
一次性通知,需重新注册 |
| 断开会话 | close(zkCli 内) |
临时节点立即删除,演示「人走茶凉」更快 |
| 探活 | echo ruok \| nc 127.0.0.1 2181 |
只回 imok |
| 完整指标 | echo mntr \| nc 127.0.0.1 2181 |
监控采集用 |
| 看角色 | echo srvr \| nc 127.0.0.1 2181 |
leader/follower |
| HDFS HA 状态 | hdfs haadmin -getAllServiceState |
联动验证 |
最佳实践清单¶
autopurge生产必配(snapRetainCount=3,purgeInterval=24) —— 为什么:默认关闭导致磁盘无限堆积,写满即停服,这是 ZK 生产事故的第一大来源。- 事务日志(dataLogDir)独立磁盘 —— 为什么:事务日志是每次写请求的 WAL,与快照抢盘会互相拖慢,直接抬高写延迟。
- 部署完成逐台
zkServer.sh status验证一主两从 —— 为什么:三台都起来但角色不对(两个 leader、全是 follower)说明 myid/网络/配置不一致,必须当场处理。 4lw.commands.whitelist显式配置 —— 为什么:不配置时 mntr/srvr 等监控命令无输出,监控系统采集不到任何数据。- 监控盯「三个必告警」:synced_followers 下降、avg_latency 持续 >20ms、pending_syncs 持续 >0 —— 为什么:这三个指标是「多数派接近临界」的领先指标,比进程死亡告警早得多。
- leader 角色变化必须告警 —— 为什么:频繁切换是网络抖动/GC 故障的典型信号,也是客户端大量断连的前兆。
- myid 用纯数字、无空格无换行,与 server.N 严格对应 —— 为什么:myid 是集群成员身份证,格式问题是最常见的起不来原因。
- session 超时按网络质量取平衡,不盲目调短 —— 为什么:太短误判频繁(抖动即锁释放重选),太长故障发现慢,两个方向都有代价。
- 逐台滚动重启/升级,保持多数派在线 —— 为什么:同一时刻最多停 1 台(3 节点配置),停第 2 台即凑不齐多数派,集群写请求全部阻塞。
- 在测试环境做一次「模拟 ZKFC 故障 + 手动 failover」演练 —— 为什么:把「锁节点消失 → 重选 → fencing」的纸面流程变成手感,首次真实故障时才不慌。
附录:新手常见疑问(FAQ)¶
Q1:zkServer.sh status 报 Error contacting service,但进程明明在?
多半是 myid 或 zoo.cfg 的 server 列表问题(4.1 节四步排查)。也有可能是 3.5+ 的 admin server 端口(默认 8080)冲突,status 命令会连它。
Q2:mntr/srvr 命令没有任何输出?
3.5.3 起四字命令走白名单,zoo.cfg 里配置 4lw.commands.whitelist=*(或显式列出需要的命令)后重启(3.1 节)。
Q3:能只部署 2 台节省资源吗? 不能。2 台的多数派 = 2 台全在线,挂 1 台就停摆——与 1 台容错相同但更贵(01 篇第 4.2 节的公式)。3 台是生产最小配置。
Q4:ZK 能跑在 Docker/K8s 里吗? 可以,但要注意:数据目录(快照+事务日志)必须持久卷且磁盘性能有保障;网络抖动对选举的影响在容器网络下更明显,session 超时建议按环境实测调整。生产环境不少团队仍选择裸机部署以规避容器网络的抖动风险。
Q5:session 超时配多少合适? 默认范围 4s~40s(tickTime 的 2~20 倍)。机房内低延迟网络常用 10s~20s;网络质量差或跨机房时适当调大。原则:宁可慢一点发现故障,也不要频繁误判(4.2 节)。
Q6:怎么确认三台数据是一致的?
leader 上 mntr 看 zk_synced_followers 是否等于 zk_followers(3 节点集群应为 2),再对比各台 srvr 的 Node count 是否一致——不一致说明有 follower 落后(4.5 节)。
Q7:快照和事务日志分别是什么?为什么分两个目录?
快照(dataDir)是内存数据树约每 10 万次写(snapCount)的完整落盘;事务日志(dataLogDir)是每次写请求的 WAL。恢复流程:载入最近快照 + 重放之后的日志。日志是高频顺序写、快照是低频大块写,混在同一块盘会互相拖慢(1.2 节)。
扩展阅读¶
- ZooKeeper 分布式协调服务核心概念 — 前置理论:znode/session/watch/ZAB
- HDFS 高可用(QJM、ZKFC、Federation) — 第 5 节升级的理论依据(ZKFC / 自动故障转移)
- HDFS 高可用集群部署实战(QJM 手动切换版) — 环境承接:本文部署与第 5 节升级所基于的集群