☰
ZooKeeper独立集群部署与版本状态查看实战指南
2026/10/14 13:26:34 网站建设 项目流程

1. 为什么要把 ZooKeeper 从 Kafka 里拆出来单独部署

很多人第一次接触 ZooKeeper,都是在搭 Kafka 的时候。下载 Kafka 压缩包,解压,里面自带一个zookeeper-server-start.sh,跟着教程敲一行命令,Kafka 就跑起来了。这个流程太顺了,顺到很多人根本没意识到:那个被顺手启动的 ZooKeeper,其实是一个"阉割版"的临时方案。

Kafka 自带的 ZooKeeper 有几个明显的局限。第一,它和 Kafka 共用同一台机器的资源,CPU、内存、磁盘 IO 都在抢,一旦 ZooKeeper 所在的节点负载高了,Kafka 的元数据操作就会跟着抖动。第二,它默认是单节点,没有集群的高可用能力,ZooKeeper 一挂,整个 Kafka 集群的控制器选举、Topic 元数据管理全部停摆。第三,版本升级很被动,Kafka 发行版里捆绑的 ZooKeeper 版本往往偏旧,你想单独升级 ZooKeeper 修复某个 bug,几乎不可能,因为改了可能和 Kafka 的兼容性对不上。

所以当集群规模上去之后,把 ZooKeeper 独立出来单独搭一套集群,几乎是必然选择。独立部署之后,ZooKeeper 可以跑在专门的节点上,资源隔离,三节点或五节点组成集群,具备容灾能力,版本也能自己控制。Kafka 那边只需要在server.properties里把zookeeper.connect指向你独立集群的地址就行,剩下的交给 ZooKeeper。

这篇文章就围绕两件事展开:一是怎么从零把一套独立的 ZooKeeper 集群搭起来,二是搭完之后怎么确认它的版本、状态和运行情况。内容偏实操,我会把每一步背后的原因讲清楚,也会把我在实际部署中踩过的坑一并说出来。适合正在做 Kafka 集群规划、或者想把现有 ZooKeeper 从 Kafka 里剥离出来的同学参考。

2. 独立 ZooKeeper 集群的部署前规划

2.1 节点数量为什么必须是奇数

ZooKeeper 集群的节点数有个硬性规律:用奇数,不用偶数。常见的是 3 节点和 5 节点。原因在于 ZooKeeper 的选举和写操作都依赖"过半机制"(Quorum)。所谓过半,就是超过一半的节点同意,操作才算通过。

假设你有 3 个节点,过半数是 2。挂掉 1 个,还剩 2 个,仍然能凑够过半数,集群可用。假设你有 4 个节点,过半数也是 3(4 的一半是 2,超过 2 就是 3)。挂掉 1 个还剩 3 个,刚好够;挂掉 2 个还剩 2 个,不够了。也就是说,4 节点的容灾能力和 3 节点一样,都是只能挂 1 个,但多了一台机器的成本。这就是为什么偶数节点没有意义。

5 节点的过半数也是 3,可以容忍挂掉 2 个。所以节点数越多,容灾能力越强,但写操作的延迟也会上升,因为每次写都要等更多节点确认。生产环境里,3 节点适合中小规模,5 节点适合对可用性要求更高的场景。再往上,7 节点就很少见了,因为延迟收益比不划算。

注意:ZooKeeper 集群的节点数一旦确定,后期扩容非常麻烦,需要重新分配事务日志和快照,还要处理数据同步。所以规划阶段就要想清楚,宁可多留一台,也别搭完就后悔。

2.2 硬件和系统层面的准备

ZooKeeper 本身对资源的需求不算高,它主要吃内存和磁盘 IO。内存方面,JVM 堆建议给 2G 到 4G,具体看你的数据量。ZooKeeper 的数据全部放在内存里,堆太小会导致频繁 GC,堆太大又浪费,而且 GC 停顿时间会变长。我的经验是,中小规模集群给 2G 堆就够,节点多、znode 数量大的场景给 4G。

磁盘方面,ZooKeeper 的写操作会先写事务日志(transaction log),再写内存数据库。事务日志的写入是顺序追加,对磁盘的顺序写性能要求较高。如果条件允许,把事务日志和数据快照分到不同的磁盘上,能明显提升写性能。用 SSD 当然更好,但机械盘也不是不能用,关键是别和其他高 IO 的服务共用一块盘。

系统层面,需要提前做几件事。第一,关闭防火墙或者开放 ZooKeeper 需要的端口,默认是 2181(客户端连接)、2888(集群内数据同步)、3888(选举通信)。第二,配置好主机名和 hosts 解析,ZooKeeper 的配置文件里用主机名比用 IP 更清晰,但前提是每台机器都能正确解析其他节点的主机名。第三,时间同步,集群内节点时间差太大会导致选举异常,用 NTP 把时间对齐。

2.3 版本选择与 JDK 依赖

ZooKeeper 的版本选择有个基本原则:选稳定版,别追最新。3.4.x 和 3.5.x 是两个长期被广泛使用的分支,3.6.x 和 3.7.x 也逐步成熟。3.5 之后的版本在动态配置、TLS 支持上有改进,但如果你只是做 Kafka 的元数据管理,3.4.14 或 3.5.9 这种经过大量生产验证的版本反而更省心。

JDK 方面,ZooKeeper 3.4.x 用 JDK 8 就行,3.5 及以上建议 JDK 8 或 JDK 11。注意别用太新的 JDK,比如 JDK 17 在某些老版本 ZooKeeper 上会有模块化相关的兼容问题。我一般会在部署前先确认目标机器上的java -version,如果版本不对,要么换 JDK,要么换 ZooKeeper 版本,别硬上。

3. 从零搭建三节点 ZooKeeper 集群的完整过程

3.1 下载与解压:别用 Kafka 里那个

第一步就是去 Apache 官网下载独立的 ZooKeeper 发行包。注意,不要从 Kafka 的 libs 目录里拷那个 jar 包来用,那个是给 Kafka 内嵌启动用的,缺少完整的配置文件和启动脚本。下载地址在 Apache ZooKeeper 的官方发布页,选apache-zookeeper-x.x.x-bin.tar.gz这个带 bin 的包,不带 bin 的是纯源码包,没法直接跑。

下载完传到三台机器上,解压到统一目录,比如/opt/zookeeper。我习惯把版本号也带在目录名里,比如/opt/zookeeper-3.5.9,这样以后升级的时候能并存,回滚也方便。解压命令很简单:

tar -zxvf apache-zookeeper-3.5.9-bin.tar.gz -C /opt/ mv /opt/apache-zookeeper-3.5.9-bin /opt/zookeeper-3.5.9

三台机器都做同样的操作,保证路径一致。路径一致的好处是配置文件可以完全复用,不用每台改路径。

3.2 配置文件 zoo.cfg 的逐项拆解

ZooKeeper 的核心配置文件是conf/zoo.cfg。解压后conf目录下有个zoo_sample.cfg,复制一份改名为zoo.cfg再改。下面是我常用的配置内容,逐项说明:

tickTime=2000 initLimit=10 syncLimit=5 dataDir=/data/zookeeper/data dataLogDir=/data/zookeeper/logs clientPort=2181 server.1=zk-node1:2888:3888 server.2=zk-node2:2888:3888 server.3=zk-node3:2888:3888

tickTime是 ZooKeeper 的基本时间单位,单位毫秒,默认 2000。它决定了心跳间隔和超时计算的基础。initLimit是 follower 和 leader 之间初始连接时能容忍多少个 tick,10 乘以 2000 毫秒就是 20 秒,意思是 follower 启动后要在 20 秒内完成和 leader 的数据同步,否则算失败。syncLimit是运行期间 follower 和 leader 之间心跳的超时容忍,5 乘以 2000 就是 10 秒。

dataDir是数据快照目录,dataLogDir是事务日志目录。这两个分开配置是性能优化的关键,前面说过,事务日志是顺序写,单独放一块盘能减少竞争。如果只有一块盘,至少目录分开,别混在一起。

clientPort是客户端连接端口,默认 2181。server.X这几行是集群成员列表,格式是server.节点ID=主机名:数据同步端口:选举端口。节点 ID 是 1 到 255 之间的整数,必须和后面要创建的myid文件内容一致。

提示:主机名zk-node1这些必须在每台机器的/etc/hosts里能解析到对应 IP,否则启动时会报找不到地址。如果不想配 hosts,直接写 IP 也行,但 IP 变了就要改配置,不如主机名灵活。

3.3 myid 文件:最容易漏掉的一步

zoo.cfg里写了server.1、server.2、server.3,但 ZooKeeper 怎么知道自己是哪一个?靠的就是dataDir目录下的myid文件。这个文件里只有一个数字,对应server.X里的 X。

在 node1 上:

echo "1" > /data/zookeeper/data/myid

node2 写 2,node3 写 3。注意dataDir目录要先创建好,myid文件要放在dataDir根目录下,不是子目录。我见过有人把 myid 放到dataDir/version-2里,结果启动报错找不到,排查半天。

三台机器的myid内容必须唯一,重复了会导致集群启动异常。这是部署时最常见的低级错误之一,但一旦踩了,报错信息不一定直观,所以务必在启动前核对一遍。

3.4 启动顺序与日志观察

配置和 myid 都就绪后,就可以启动了。启动脚本在bin/zkServer.sh,命令是:

/opt/zookeeper-3.5.9/bin/zkServer.sh start

三台机器都要执行。启动顺序没有严格要求,但建议一台一台来,观察日志确认没问题再启下一台。日志在logs目录下,文件名类似zookeeper.out,或者看dataLogDir旁边的日志目录。

启动后,用zkServer.sh status查看状态:

/opt/zookeeper-3.5.9/bin/zkServer.sh status

正常的话,一台会显示Mode: leader,另外两台显示Mode: follower。如果三台都显示standalone,说明集群没组起来,通常是 myid 或 server.X 配置有问题。如果某台显示Mode: leader但其他两台连不上,检查端口和防火墙。

注意:第一次启动时,如果三台几乎同时启动,选举可能会多花几秒。如果超过 30 秒还没选出 leader,去看日志里的Cannot open channel to X at election address这类错误,基本是网络或端口问题。

4. 集群跑起来之后,怎么确认版本和运行状态

4.1 用四字命令查版本:stat 和 srvr

ZooKeeper 提供了一组"四字命令"(Four Letter Words),用来快速查询运行状态。查版本最直接的是stat和srvr。用法是通过nc或者telnet连到 2181 端口发命令:

echo stat | nc 127.0.0.1 2181

返回内容里会有一行Zookeeper version: 3.5.9-...,这就是当前运行的版本。srvr命令类似,但输出更简洁,也会带版本信息:

echo srvr | nc 127.0.0.1 2181

如果nc没装,用telnet 127.0.0.1 2181进去手动敲stat也行。注意四字命令默认在 3.5 之后有白名单限制,如果返回stat is not executed because it is not in the whitelist,需要在zoo.cfg里加一行4lw.commands.whitelist=stat,srvr,ruok,conf,然后重启。

4.2 从启动脚本和 jar 包反查版本

有时候 ZooKeeper 已经在跑,但你不方便连端口,或者四字命令被禁了。这时候可以从另外两个地方看版本。

第一个是zkServer.sh脚本本身。执行:

/opt/zookeeper-3.5.9/bin/zkServer.sh version

这个命令会直接打印出版本号,不需要连接服务。它的原理是读取lib目录下的 jar 包名。所以第二个方法就是看 jar 包:

ls /opt/zookeeper-3.5.9/lib/ | grep zookeeper

你会看到类似zookeeper-3.5.9.jar的文件,版本号一目了然。这个方法最可靠,因为 jar 包名就是版本,不会骗人。

4.3 用客户端连接验证集群可用性

版本确认了,还得验证集群真的能用。用zkCli.sh连上去:

/opt/zookeeper-3.5.9/bin/zkCli.sh -server zk-node1:2181

连上之后,敲ls /看看根节点下有什么。默认会有zookeeper这个节点。然后可以创建一个测试节点:

create /test-node "hello"

再get /test-node看能不能读到值。接着断开,换连 node2 和 node3,重复get /test-node,如果三台都能读到同样的值,说明数据同步正常,集群工作正常。

这个验证步骤很重要,因为有时候status显示 leader/follower 正常,但实际数据同步有问题,只有真正读写一次才能确认。

5. 把 Kafka 指向独立 ZooKeeper 的配置改动

5.1 Kafka 端 zookeeper.connect 的写法

独立 ZooKeeper 集群搭好之后,Kafka 那边只需要改一个配置。在 Kafka 的config/server.properties里,找到zookeeper.connect:

zookeeper.connect=zk-node1:2181,zk-node2:2181,zk-node3:2181

把三个节点都写上,用逗号分隔。这样即使其中一个 ZooKeeper 节点挂了,Kafka 还能连其他的。注意后面不要带 chroot 路径,除非你确实想把 Kafka 的数据隔离到某个子节点下。如果带 chroot,写法是zk-node1:2181,zk-node2:2181,zk-node3:2181/kafka,但那个/kafka节点必须提前创建好,否则 Kafka 启动会报错。

改完配置,重启 Kafka。重启后观察 Kafka 日志,如果看到Connected to zookeeper并且没有反复重连的日志,说明连接正常。

5.2 迁移过程中的数据一致性风险

如果是把现有 Kafka 从自带 ZooKeeper 迁移到独立 ZooKeeper,风险比全新搭建大得多。因为 Kafka 的元数据(broker 注册、Topic 配置、分区 leader 信息)全在 ZooKeeper 里,直接换地址会导致 Kafka 找不到原来的元数据,集群可能起不来。

正确的做法是:先停掉 Kafka,把原 ZooKeeper 的数据目录整体拷贝到新集群的对应节点上,保证数据一致,然后再改zookeeper.connect并启动。或者更稳妥的方式是,新集群搭好后,用 Kafka 自带的迁移工具或者手动重建 Topic 元数据。但手动重建风险高,生产环境建议在低峰期做,并且提前备份。

提示:迁移前一定要备份原 ZooKeeper 的dataDir和dataLogDir,这是最后的退路。我见过有人直接改配置重启,结果 Kafka 起不来,原数据又没备份,最后只能重建集群。

6. 部署和运维中那些文档不会写的坑

6.1 磁盘写满导致 ZooKeeper 拒绝服务

ZooKeeper 有个特性:当磁盘空间不足时,它会主动拒绝写操作,甚至停止服务。这是为了防止事务日志写不进去导致数据损坏。但问题是,它的日志里不一定直接说"磁盘满了",可能报的是No space left on device或者更隐晦的IOException。

我的做法是给dataDir和dataLogDir所在的磁盘设监控,使用率超过 80% 就告警。另外,ZooKeeper 不会自动清理旧的事务日志和快照,需要配置autopurge.snapRetainCount和autopurge.purgeInterval:

autopurge.snapRetainCount=3 autopurge.purgeInterval=24

意思是保留最近 3 份快照,每 24 小时清理一次。不配这个,日志会一直涨,迟早把盘写满。

6.2 客户端连接数被打满

ZooKeeper 默认的maxClientCnxns是 60,意思是单个客户端 IP 最多 60 个连接。如果 Kafka 集群的 broker 多,或者有其他客户端频繁连接,很容易打满。打满之后新连接会被拒绝,报Too many connections。

解决办法是在zoo.cfg里调大这个值,比如maxClientCnxns=200。但别调太大,否则单个客户端可能耗尽服务端资源。更好的做法是排查为什么会有这么多连接,是不是客户端没正确关闭连接,或者连接池配置有问题。

6.3 选举端口被占用导致集群分裂

ZooKeeper 的选举端口默认是 3888,数据同步端口是 2888。如果这两个端口被其他进程占用,或者防火墙没放行,集群会分裂成多个小集群,每个都以为自己有 leader。这种情况很危险,因为客户端可能连到不同的分区,看到不一致的数据。

排查方法是看日志里的Looking for leader和Notification time out这类信息,然后用netstat -tlnp | grep 3888确认端口监听情况。部署前把防火墙规则配好,别等出问题了再查。

6.4 版本不一致导致的兼容问题

如果集群里三台机器的 ZooKeeper 版本不一样,比如两台 3.5.9,一台 3.4.14,启动时可能能跑,但运行中会出现各种奇怪的问题,比如数据同步失败、选举异常。ZooKeeper 要求集群内所有节点版本一致,至少大版本要一致。

所以部署时一定要核对每台机器的 jar 包版本,用前面说的ls lib | grep zookeeper确认。升级时也要滚动升级,先升 follower,再升 leader,别三台一起换。

7. 日常巡检该看哪些指标

集群搭好只是开始,日常运维才是重头戏。我一般会定期看几个关键指标。第一个是zk_server_state,确认 leader 和 follower 的角色分布正常,没有出现多个 leader。第二个是zk_avg_latency和zk_max_latency,延迟突然升高往往意味着磁盘 IO 或网络有问题。第三个是zk_outstanding_requests,这个值持续大于 0 说明有请求堆积,服务端处理不过来。

这些指标可以通过四字命令mntr获取:

echo mntr | nc 127.0.0.1 2181

返回的是一堆键值对,可以接入监控系统做趋势分析。另外,ruok命令用来做存活检查,返回imok表示服务正常,适合放在健康检查脚本里。

巡检频率看集群规模,中小规模每天看一次就行,大规模或者业务高峰期可以做到分钟级采集。关键是别等出事了才去看,平时有基线数据,异常时才能快速判断。

8. 关于版本查看这件事,再补充两个实用技巧

前面说了stat、srvr、zkServer.sh version和看 jar 包这几种方法。再补充两个场景。第一个是如果你只有客户端连接权限,没有服务器登录权限,可以用zkCli.sh连上去之后,有些版本支持version命令,但并非所有版本都有,所以不保证通用。更稳的方式还是四字命令。

第二个是如果你在容器环境里,ZooKeeper 跑在容器内,宿主机上可能没有nc或者telnet。这时候可以用docker exec进容器,容器里一般有bash和nc,或者直接用zkServer.sh version。如果容器镜像精简到连zkServer.sh都没有,那就只能看镜像 tag 或者挂载的 jar 包了。

我个人在实际操作中的体会是,版本查看这件事看似简单,但在故障排查时特别重要。因为不同版本的 ZooKeeper 在配置项、四字命令白名单、日志格式上都有差异,确认版本能帮你快速定位问题范围。所以每次接手一个新集群,我第一件事就是确认版本,然后再看配置和状态。这个习惯帮我省了不少时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询