本章定位:掌握 ZooKeeper 服务端的部署与配置,是后续所有内容的基础。集群部署是 ZooKeeper 高可用的前提,理解其拓扑和节点角色至关重要。
定义与作用
ZooKeeper 集群(Ensemble)由一组 Server 组成,通过 ZooKeeper Atomic Broadcast(ZAB)协议在节点间同步状态。集群部署的核心目标是高可用——当部分节点故障时,只要多数节点存活,服务就不会中断。
ZooKeeper 提供两种部署模式:
| 模式 | 节点数 | 适用场景 |
|---|---|---|
| 单机模式 | 1 | 本地开发、学习测试 |
| 集群模式 | ≥3(奇数) | 生产环境 |
核心原理
集群拓扑
ZooKeeper 集群中的节点有三种角色:
- Leader:所有写请求的唯一入口,负责发起 Proposal 并协调投票
- Follower:参与投票,处理读请求,将写请求转发给 Leader
- Observer:不参与投票,仅处理读请求,用于横向扩展读能力
集群中只有 Leader 处理写请求,Follower 和 Observer 均可处理读请求。Observer 不参与投票,因此增加 Observer 不会降低写性能。
配置核心参数
在 conf/zoo.cfg 中定义集群成员:
server.1=host1:2888:3888
server.2=host2:2888:3888
server.3=host3:2888:3888
- 第一个端口(2888):Follower 连接 Leader 的端口
- 第二个端口(3888):Leader 选举端口
- 每个节点需要在
dataDir下创建myid文件,内容为该节点的数字 ID
完整示例
示例一:单机模式部署
场景说明:本地开发环境搭建一个 ZooKeeper 服务。
操作前状态:已下载并解压 ZooKeeper 3.7+ 安装包。
步骤 1:创建配置文件
cd apache-zookeeper-3.7.0-bin
cp conf/zoo_sample.cfg conf/zoo.cfg
步骤 2:编辑 zoo.cfg
tickTime=2000
dataDir=/tmp/zookeeper
clientPort=2181
步骤 3:启动服务
bin/zkServer.sh start
执行结果:
ZooKeeper JMX enabled by default
Using config: /path/to/apache-zookeeper-3.7.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
步骤 4:验证服务
bin/zkCli.sh -server 127.0.0.1:2181
执行结果:
Connecting to 127.0.0.1:2181
Welcome to ZooKeeper!
JLine support is enabled
[zkshell: 0]
操作后状态:ZooKeeper 单机模式运行在 2181 端口,可执行 ls、create、get 等命令。
示例二:三节点集群部署(单机模拟)
场景说明:在单台机器上启动 3 个 ZooKeeper 实例,模拟生产集群的部署流程。
操作前状态:已有 ZooKeeper 安装包,需配置 3 套独立的环境。
步骤 1:创建 3 份配置与数据目录
mkdir -p /tmp/zk-data/{1,2,3}
echo 1 > /tmp/zk-data/1/myid
echo 2 > /tmp/zk-data/2/myid
echo 3 > /tmp/zk-data/3/myid
步骤 2:编写 zoo-1.cfg(zoo-2.cfg、zoo-3.cfg 类似,分别修改 dataDir 和 clientPort)
# zoo-1.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zk-data/1
clientPort=2181
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
# zoo-2.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zk-data/2
clientPort=2182
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
# zoo-3.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zk-data/3
clientPort=2183
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
步骤 3:依次启动 3 个实例
bin/zkServer.sh start conf/zoo-1.cfg
bin/zkServer.sh start conf/zoo-2.cfg
bin/zkServer.sh start conf/zoo-3.cfg
步骤 4:查看各节点状态
bin/zkServer.sh status conf/zoo-1.cfg
# Mode: follower
bin/zkServer.sh status conf/zoo-2.cfg
# Mode: leader
bin/zkServer.sh status conf/zoo-3.cfg
# Mode: follower
步骤 5:验证数据同步
在 Leader(2182)上写入数据:
bin/zkCli.sh -server 127.0.0.1:2182
[zkshell: 0] create /cluster_test "hello cluster"
Created /cluster_test
在 Follower(2181)上读取:
bin/zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] get /cluster_test
hello cluster
操作后状态:
| 节点 | 端口 | 角色 | myid |
|---|---|---|---|
| Server 1 | 2181 | Follower | 1 |
| Server 2 | 2182 | Leader | 2 |
| Server 3 | 2183 | Follower | 3 |
集群正常运行,数据在 3 个节点间同步。
示例三:添加 Observer 节点
场景说明:集群读压力增大,需要横向扩展但不影响写性能。
配置 Observer(zoo-obs.cfg):
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zk-data/obs
clientPort=2184
peerType=observer
server.1=127.0.0.1:2888:3888
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
server.4=127.0.0.1:2891:3891:observer
操作前后对比:
| 维度 | 操作前(3 节点) | 操作后(3+1 Observer) |
|---|---|---|
| 参与投票节点 | 3 | 3(不变) |
| 读处理能力 | 3 个节点 | 4 个节点 |
| 写性能 | 受 Leader/Follower 限制 | 不变 |
动态重配置(Dynamic Reconfiguration)
ZooKeeper 3.5.0 引入了动态重配置机制,支持在不停止集群服务的情况下增删节点。这对于在线扩容、故障节点替换、机房迁移等生产运维场景至关重要。
前置条件
- ZooKeeper 3.5.0+ 版本
- zoo.cfg 中需包含
dynamicConfigFile路径,或直接在 zoo.cfg 中声明server行 reconfig操作需要超级用户权限(或关闭skipACL)
查看当前配置
# 通过 zkCli 连接到集群
bin/zkCli.sh -server 127.0.0.1:2182
# 查看当前集群成员
[zkshell: 0] reconfig -members
返回当前集群的 server 列表:
server.1=127.0.0.1:2888:3888:participant;0.0.0.0:2181
server.2=127.0.0.1:2889:3889:participant;0.0.0.0:2182
server.3=127.0.0.1:2890:3890:participant;0.0.0.0:2183
集群扩容流程
添加节点
命令格式:
reconfig -add server.<id>=<host>:<port1>:<port2>:<role>;<clientPort>
示例:向 3 节点集群添加第 4 个节点(参与投票):
# 1. 先以 non-voting 角色加入,同步数据。
# 语法:role=observer 或 role=participant
# participant 表示参与投票,observer 表示只读节点
[zkshell: 0] reconfig -add \
server.4=192.168.1.14:2891:3891:participant;192.168.1.14:2184
# 输出:
# Committed new configuration:
# server.1=127.0.0.1:2888:3888:participant;0.0.0.0:2181
# server.2=127.0.0.1:2889:3889:participant;0.0.0.0:2182
# server.3=127.0.0.1:2890:3890:participant;0.0.0.0:2183
# server.4=192.168.1.14:2891:3891:participant;192.168.1.14:2184
操作前后对比:
| 维度 | 操作前 | 操作后 |
|---|---|---|
| 投票节点 | 3 | 4 |
| 容错能力 | 容忍 1 台故障 | 仍容忍 1 台故障(4 节点多数 = 3) |
| 集群服务状态 | 持续运行 | 持续运行(无停服) |
| dynamicConfigFile | 3 条 server | 4 条 server(自动更新) |
移除节点
# 移除 server.4
[zkshell: 0] reconfig -remove 4
# 输出:
# Committed new configuration:
# server.1=127.0.0.1:2888:3888:participant;0.0.0.0:2181
# server.2=127.0.0.1:2889:3889:participant;0.0.0.0:2182
# server.3=127.0.0.1:2890:3890:participant;0.0.0.0:2183
移除节点后,被移除节点检测到自身被移出 quorum 会自动关闭服务——无需手动停进程。
动态配置文件(dynamicConfigFile)
当使用动态重配置后,集群成员信息不再仅存在于 zoo.cfg 的 server.X=... 行中,而是持久化到 dynamicConfigFile(默认路径为 dataDir/zoo.cfg.dynamic):
# zoo.cfg 中指定
dynamicConfigFile=/var/lib/zookeeper/zoo.cfg.dynamic
dynamicConfigFile 的内容是所有集群节点自动维护的当前 quorum 配置,每次 reconfig 操作都会更新此文件。重启节点时会优先读取 dynamicConfigFile 而非静态 zoo.cfg 中的 server 行。
易错场景
1. reconfig 后忘记更新静态 zoo.cfg
dynamicConfigFile 自动更新,但 zoo.cfg 中的 server.X=... 行不会自动更新。如果运维人员手动重启节点时,zoo.cfg 中的旧 server 行可能导致节点加入失败或形成脑裂。最佳实践:执行 reconfig 后同步更新 zoo.cfg 中 server 行(注释或对齐)。
2. 新节点数据同步未完成就提升为 participant
新加入的 observer 节点需要从 Leader 同步所有事务日志和快照。如果数据尚未追平就执行 reconfig -add ...:participant,该节点在投票时没有完整状态,可能导致 Proposal 提交延迟甚至失败。等待 srvr 或 stat 命令确认 zxid 追平后再提升。
面试高频题
Q:集群如何不停服扩容?
A:使用 ZooKeeper 3.5.0+ 的 reconfig 命令。步骤:① 新节点以 observer(non-voting)角色加入,从 Leader 同步数据;② 数据追平后通过 reconfig -add ...:participant 提升为投票节点;③ 集群在扩容全程保持服务,无需任何重启。
Q:reconfig 与手动修改 zoo.cfg 再滚动重启的区别?
A:三个核心差异:① reconfig 不中断集群服务,滚动重启至少需要逐台停服;② reconfig 是原子操作——Leader 发起 Proposal 并经多数 Follower 确认后提交,配置变更有严格的一致性保证;③ reconfig 自动更新 dynamicConfigFile,避免了多节点 zoo.cfg 配置不一致的风险。
易错场景与面试考点
易错场景
1. myid 与 server.X 不匹配
错误现象:启动时日志报 server.X 与 myid 不一致。
原因:dataDir/myid 文件内容(如 1)必须与配置中 server.1 的序号严格一致。
解决:检查 myid 文件内容,尤其是前后是否有空格或换行。
2. 两节点集群无法正常工作
错误现象:一个节点宕机后集群不可用。
原因:ZooKeeper 需要过半数(majority)存活。2 节点集群中任意一个节点宕机即失去多数,集群停止服务。2 节点比单节点更不可靠——因为增加了故障点却没有提高容错能力。
3. 端口冲突
错误现象:启动失败,日志显示端口被占用。
原因:单机模拟集群时 2888/3888 端口被其他进程占用,或不同实例的端口配置重复。
解决:使用 netstat -an | grep 2888 检查端口占用情况。
面试高频题
Q:为什么推荐奇数个节点?
A:ZooKeeper 使用多数派机制。在容错能力相同时,奇数节点更经济:
- 3 节点 → 容忍 1 个节点故障
- 4 节点 → 仍只能容忍 1 个节点故障(需 3 个存活形成多数)
- 5 节点 → 容忍 2 个节点故障
4 节点比 3 节点多用了资源却未提升容错能力,因此推荐奇数。
Q:Observer 与 Follower 的核心区别?
A:Observer 不参与投票(不响应 Leader 的 Proposal ACK),因此:
- 增加 Observer 不会降低写入延迟(写入仍需多数 Follower 确认)
- Observer 只接收 INFORM 消息,不参与选举
- Observer 适合跨数据中心部署,降低跨机房带宽消耗
Q:Leader 宕机后集群如何恢复?
A:Follower 检测到 Leader 心跳超时(syncLimit * tickTime)后进入选举,通过 FastLeaderElection 选出新 Leader,新 Leader 与 Follower 同步状态后恢复服务。
小结
| 要点 | 说明 |
|---|---|
| 集群节点数 | 生产环境 ≥ 3,推荐奇数 |
| myid 文件 | 必须与 server.X 序号一致 |
| 选举端口 | 3888 默认,Leader 选举专用 |
| 通信端口 | 2888 默认,Follower→Leader 连接 |
| Observer | 不投票,仅处理读请求 |
| 动态重配置 | 3.5.0+ reconfig 命令,不停服增删节点 |
| 单机模拟 | 注意端口和数据目录唯一 |
集群部署是 ZooKeeper 使用的前提。下一节将深入配置参数详解,理解每个参数对集群行为的精确影响。