☰
Zookeeper集群搭建与原理拆解:三节点部署、选举机制与生产避坑
2026/10/1 19:30:02 网站建设 项目流程

做过分布式系统的人都知道,Zookeeper集群是绕不开的一道坎。Hadoop、Kafka、HBase、Dubbo、Spark这些组件,只要涉及高可用、协调、元数据管理,底层几乎都站着Zookeeper。但说句实话,很多人搭Zookeeper集群就是照着教程抄一遍配置文件,能启动就以为完事了,等真正上了生产环境,节点一挂、选举卡住、数据不同步,才发现之前压根没搞明白这个集群是怎么运作的。

这篇我会以三节点集群为例,把从环境准备、配置解读、启动验证到扩容缩容的完整过程拆开讲清楚,重点放在“为什么这么做”上。文章里的配置文件、操作命令都是我在实际部署中验证过的,适合刚入门想做本地试验的,也适合准备上线、想避开常见坑的运维和开发朋友。看完之后,你不仅能搭出一个能用的集群,还能在别人问你“为什么是奇数节点”“为什么两个端口”的时候,答得上来了。

1. 动手之前,先把这三个问题想明白

1.1 Zookeeper在分布式体系里到底干什么

网上很多教程一上来就让你下载、解压、改配置,但很少人告诉你Zookeeper解决的是什么问题。我个人的理解是:Zookeeper是一个给分布式系统提供“共识”的组件。分布式环境下,多台机器各干各的,总有需要集体决策的时候——谁是主节点、配置变更是谁先知道、分布式锁谁拿到了,这些都要有个大家都认账的结论。Zookeeper提供的就是这种一致性协调能力。

结合到实际组件里会更直观。Hadoop的NameNode做高可用时,Active和Standby的自动切换,靠的是Zookeeper来做主节点选举和状态感知;Kafka的broker列表、topic分区leader选举,也都挂在Zookeeper上;HBase的RegionServer元数据、Master选举同样依赖它。所以Zookeeper集群搭得不稳,上层所有组件都跟着受影响,这不夸张。

1.2 节点数为什么不是越多越好:过半机制的数学题

Zookeeper集群最经典的问题就是这个:为什么要奇数个节点?

答案藏在它的选举机制里。Zookeeper的Leader选举和所有写入操作,都要求“过半节点同意”才算成功。三节点集群,允许挂掉一个节点后继续正常工作;四节点集群呢?同样只能允许一个节点故障,因为挂两个就达不到“过半”了。所以四节点和三节点的容错能力一模一样,还白白增加了一台机器的维护成本。这就是奇数节点更优的原因——用最少的机器,换取最大的容错空间。

我画过一张表方便记忆:

集群节点数可容忍故障数是否存在冗余浪费
10-
21有(但2节点是坏配置,见下文)
31无
41有
52无
62有

注意看,2节点也挺特殊的。它能容忍1个故障,但一旦挂了一台,剩下那一台凑不齐过半数,整个集群就会进入只读不可写入的假死状态。生产环境我建议直接从3节点起步,机器充裕就直接上5节点。

1.3 单机模式和三节点集群怎么选

我看过不少人在开发环境直接跑单机Zookeeper,其实这也是合理的。Zookeeper本身支持standalone模式,单机部署也能提供命名空间、分布式锁这些基础能力,适合本地调试代码。

但需要警惕的是:如果上层应用配置的是集群模式,而Zookeeper实际运行的是单机模式,一旦这台机器重启,所有依赖它的组件可能集体异常。我们之前有个测试环境就吃过这个亏,连接串写的是三个IP,其中两个压根没部署,每次重启Kafka,Zookeeper那台一重启,整个链路就断。

所以开发环境可以单机,但连接串要如实配置成单机模式;测试和生产环境强烈建议直接用三节点,别为省服务器留隐患。

2. 环境与版本:动手前先把坑填平

2.1 版本选型与JDK匹配

选版本这件事,很多人不重视,上来就找个最新的下载,结果和已有的Hadoop、Kafka版本不兼容,排查半天。Zookeeper从3.4到3.5到3.6、3.7、3.8,接口和默认配置都有变化。

我目前的经验是:

  • Zookeeper 3.4.x:太老了,很多新组件已经不兼容,别用。
  • Zookeeper 3.5.x/3.6.x:目前生产环境的主流选择。3.5开始引入了TLS、动态重配置、四字命令白名单等特性,API和3.4基本兼容。
  • Zookeeper 3.7.x/3.8.x:新项目可以考虑,但要注意和Kafka等组件的版本兼容矩阵。

JDK方面,3.5和3.6要求JDK 8或11,3.7以上支持JDK 8、11、17。我这里用的是3.6.3和JDK 8,这个搭配在各大组件的兼容性上最稳。建议先用java -version确认一下JDK版本,不然后面启动报UnsupportedClassVersionError就尴尬了。

2.2 三台主机的规划清单

我用三台机器做演示,操作系统是CentOS 7.9,生产环境Ubuntu 22.04也完全可以,配置思路一致。

一个比较标准的规划如下:

主机名IP角色配置建议
zk0110.0.1.11Leader候选2核4G起步
zk0210.0.1.12Leader候选2核4G起步
zk0310.0.1.13Leader候选2核4G起步

看到“Leader候选”别奇怪,三台机器初始状态下谁都有可能成为Leader,没有预先指定的角色。主机名必须提前设置好,因为Zookeeper的配置文件里会用到主机名,如果主机名没设对,启动时解析不了,节点之间根本连不上。

另外,三台机器的时间最好用NTP校时,时间差太大会影响Zookeeper的会话和选举逻辑。我在实际部署中就踩过时间偏差超过30秒导致节点反复断连的坑,这个细节没多少人提,但真的很重要。

2.3 安装目录、运行用户与防火墙

Zookeeper建议用专用用户运行,不要用root。我习惯创建zookeeper用户,然后把安装目录和数据目录分离:

  • 安装目录:/opt/zookeeper
  • 数据快照目录:/data/zk/data
  • 事务日志目录:/data/zk/logs
  • PID文件目录:/data/zk/pids

数据目录和安装目录分离的好处是:升级Zookeeper版本时不用迁移数据;磁盘满时也不会把系统盘堵死。

下载源码包解压到/opt/zookeeper后,记得执行:

chown -R zookeeper:zookeeper /opt/zookeeper /data/zk mkdir -p /data/zk/{data,logs,pids} chown -R zookeeper:zookeeper /data/zk

防火墙方面,Zookeeper集群需要放行三个端口,这个非常关键。2181是客户端连接端口,2888是follower与leader之间同步数据的端口,3888是选举端口。很多集群起不来的问题,就是3888端口被防火墙挡了,节点之间选举通信失败,只能反复弹“Cannot open channel to X at election address”的报错。

如果用的是云服务器,安全组里也要放行这三个端口。

3. 配置拆解:一份zoo.cfg里的门道

3.1 从模板到生产:逐字段调整配置

Zookeeper的配置文件在/opt/zookeeper/conf/zoo.cfg。刚解压完只有zoo_sample.cfg,需要复制一份:

cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg

三台机器的zoo.cfg内容完全一致,唯一不同的地方是数据目录里的myid文件。我先放一份生产环境常用配置:

tickTime=2000 initLimit=10 syncLimit=5 dataDir=/data/zk/data dataLogDir=/data/zk/logs clientPort=2181 maxClientCnxns=60 autopurge.snapRetainCount=3 autopurge.purgeInterval=24 server.1=zk01:2888:3888 server.2=zk02:2888:3888 server.3=zk03:2888:3888 4lw.commands.whitelist=* admin.enableServer=true admin.serverPort=8081

每个参数我都建议你弄懂再上生产:

  • tickTime=2000:Zookeeper里的基础时间单位,单位是毫秒。节点之间每次心跳的间隔就是2秒。这个值决定了后面initLimit和syncLimit的真实时长。
  • initLimit=10:follower在启动时,最多能落后Leader多少个tick,也就是最多等10 * 2000 = 20秒完成初始同步。如果网络环境差,可以调大到15甚至20,但别太大。
  • syncLimit=5:follower和Leader之间正常通信的超时,5 * 2000 = 10秒。超过这个时间没收到Leader的消息,follower会认为自己失联了,重新发起选举。
  • dataDir和dataLogDir:dataDir是快照目录,dataLogDir是事务日志目录。这两个目录一定要分开,事务日志的写入频率远高于快照,如果混在一个磁盘上,IO竞争会很严重。我在测试环境第一次部署时没分开,业务高峰期Zookeeper写延迟明显升高,就是被日志和快照同时占IO拖的。

3.2 server.1=host:port:port 到底怎么填

server.1=zk01:2888:3888这句话是集群配置的核心。它的格式是server.<序号>=<主机名>:<数据同步端口>:<选举端口>。

两个端口很多人分不清,我在这里说清楚:

  • 2888端口:Leader和follower之间同步事务日志、快照数据的端口。每次写请求到Leader,Leader要把事务发给所有follower,通过这个端口走。
  • 3888端口:集群选举Leader时,节点之间互相投票、交换选举信息的端口。

还一个容易忽略的细节:server.1这里的序号1不是随便写的,它必须和这台机器上dataDir目录里的myid文件内容完全对应。zk03这台机器上,myid文件内容必须是3。

端口这里我想多说一句:生产环境里,2888和3888千万别暴露到公网,它们只应该在内网被集群节点互相访问。这三台机器之间的内网安全组,只放行必要的端口,别图省事开个全部端口。

3.3 myid没写对,集群永远起不来

myid是Zookeeper集群里节点的身份证。在每台机器的dataDir目录下创建:

# zk01机器上 echo "1" > /data/zk/data/myid # zk02机器上 echo "2" > /data/zk/data/myid # zk03机器上 echo "3" > /data/zk/data/myid

这个文件的内容必须和zoo.cfg里的server.N对应,如果zk01上写成了2,启动时会找不到自己的身份,直接启动失败。

还要注意文件权限,我遇到过myid文件被root用户创建后,zookeeper用户读取不了,节点启动后一直报权限错误。创建完之后顺手执行一下:

chown zookeeper:zookeeper /data/zk/data/myid

3.4 用systemd把ZK托管起来

生产环境里,我强烈建议不要直接用zkServer.sh裸跑,而是用systemd托管,这样开机自启、崩溃重启、日志管理都省心。

在/etc/systemd/system/zookeeper.service里写入:

[Unit] Description=Zookeeper Service After=network.target [Service] Type=forking User=zookeeper Group=zookeeper Environment=ZOO_LOG_DIR=/data/zk/logs Environment=PID_DIR=/data/zk/pids ExecStart=/opt/zookeeper/bin/zkServer.sh start ExecStop=/opt/zookeeper/bin/zkServer.sh stop ExecReload=/opt/zookeeper/bin/zkServer.sh restart Restart=always RestartSec=10 [Install] WantedBy=multi-user.target

然后执行:

systemctl daemon-reload systemctl enable zookeeper systemctl start zookeeper

这里有个小细节:Zookeeper默认的PID文件位置可能在/tmp下,或者写不进去会导致zkServer.sh stop找不到进程。所以我在systemd里专门用PID_DIR环境变量指定了PID文件目录,并且提前创建好、授权给zookeeper用户。这一步看着不起眼,但是能避免线上“启动可以、停止失败”的诡异问题。

4. 启动与验证:别把“能连上”当成“搭好了”

4.1 第一次启动顺序与日志判断

很多人启动集群时喜欢一台一台全部启动,然后直接看状态,没问题就说“集群起来了”。我的习惯是:先启动第一台,观察日志;再启动后面两台,观察日志。

第一台启动时,它会一直处于选举状态,因为凑不齐过半节点。日志里会出现:

LOOKING -> LEADING LOOKING -> FOLLOWING

看到类似“LOOKING”的日志别慌,这是正常现象,说明节点在等另外的机器加入选举。

启动命令是:

systemctl start zookeeper # 或者不带systemd的环境 /opt/zookeeper/bin/zkServer.sh start

日志位置可以这样找:

tail -f /data/zk/logs/zookeeper-zookeeper-server-zk01.out

zookeeper.out是Zookeeper的启动运行日志,排查问题先看这个文件。注意zookeeper.out并不是zookeeper.log,Zookeeper的业务日志(连接数、事务、选举日志)在配置了log4j后会有独立的zookeeper.log文件,但启动阶段的关键输出一般都在zookeeper.out里。

4.2 四字命令与状态验证

集群三台全部启动后,验证方式就是Zookeeper的四字命令。先看单台节点状态:

echo stat | nc localhost 2181

输出里最关键的是最后一行:

Mode: follower

或者:

Mode: leader

如果三台里恰好有一台是leader,其他是follower,说明选举成功了。

再检查集群健康:

echo ruok | nc localhost 2181

返回imok就表示节点正常。还有一个很实用的命令:

echo srvr | nc localhost 2181

能看到当前节点的角色、连接数、Znode数量、延迟数据,比stat更精简,我一般用这个做快速巡检。

注意:Zookeeper 3.5以后,四字命令默认全部关闭,需要在zoo.cfg里加一行4lw.commands.whitelist=*,或者只开放你需要的命令,比如4lw.commands.whitelist=stat,ruok,srvr,cons。生产环境我建议只开必要命令,别图省事用通配符。

4.3 集群自愈能力测试

集群搭起来后,最重要的验证不是看它跑得正不正常,而是看它挂了节点后能不能自动恢复。我每次搭完集群都会做一次这样的演练:

先在zk01上用客户端建一个测试节点:

/opt/zookeeper/bin/zkCli.sh -server zk01:2181 # 进入客户端后执行 create /cluster_test "ok"

然后在另一台机器上查询:

/opt/zookeeper/bin/zkCli.sh -server zk03:2181 get /cluster_test

有时候读不到,别急着下结论,先确认一下你连接的节点是不是follower。Zookeeper的follower是可以处理读请求的,如果你的客户端连接串里写的是Leader地址,读请求一般也能正常返回。这里真正要验证的是:挂掉Leader后,集群能不能自动选出新Leader,并且数据不丢。

模拟故障:

systemctl stop zookeeper # 在Leader那台执行

等10秒左右,再用剩下的任意一台节点执行echo srvr | nc localhost 2181,你会发现原来两台follower里有一台变成了leader。这就完成了自愈验证。

4.4 一台台上线时数据是怎么同步的

我在做集群演练时,还喜欢做一件小事:观察新节点上线后,数据是怎么追平的。

Zookeeper的机制是:新启动的follower会在initLimit限定时间内,从Leader那里获取最新的快照和之后的事务日志,然后重新执行这些事务,把数据追平。这个过程叫数据同步。

有人会问:如果新节点落后太多,事务日志已经过期被清理了怎么办?别担心,Zookeeper用了一个叫“快照加事务日志”的机制。Leader会先给新节点发一个全量快照,快照之后的新事务再增量同步。只要快照和日志目录是好的,新节点就能追回来。这也是为什么autopurge参数要合理设置——如果日志被清理得太快,某些极度落后的节点可能追不回来。

5. 从3节点到5节点:集群扩容的平滑路径

5.1 扩容前要做的检查

新节点加入集群前,建议先检查几件事:

  1. 版本一致性:新节点的Zookeeper版本尽量和集群现有版本保持一致,跨大版本混布容易出兼容性问题。
  2. JDK版本:同样尽量一致,Zookeeper对跨JDK版本混跑兼容性还行,但没必要冒险。
  3. 数据目录初始化:新节点的dataDir目录要预先创建好,并且是空的。如果里面残留了老数据,启动后可能报快照不一致错误。

5.2 滚动重启的节奏

3节点扩到5节点的操作流程是这样的:

在现有三台机器的zoo.cfg里,把server.4和server.5的配置加上:

server.4=zk04:2888:3888 server.5=zk05:2888:3888

然后把新节点的配置文件同步好,创建myid文件(分别写4和5)。

接下来是滚动重启三台老节点。顺序很重要,不要三台同时重启,而是逐一重启。每次重启后,等集群恢复稳定(过半节点在线)再重启下一台。如果三台同时重启,集群会面临短暂的完全不可用。

我在生产环境扩容时,还会提前把新节点的网段加入防火墙白名单,确保集群里的老节点能访问新节点的2888和3888端口,不然新节点加进来后,会反复报连接失败。

5.3 缩容与下线的正确姿势

缩容比扩容更敏感,因为下线节点会影响集群的过半机制。比如5节点缩到3节点,一次下线两台机器,就会导致剩下3台仍然过半,没大问题。但如果你在3节点集群里试图下线1台,集群会变成2节点,一旦再挂一台,整体写入就卡死了。

正确的下线顺序是:

  1. 先在zoo.cfg里把要下线的server.N配置注释或删除。
  2. 再把该节点的myid文件备份后移走。
  3. 最后停止该节点进程。

这里还有个细节:注释掉server.N后,剩余节点重启时不会等待被移除的节点加入选举,这样就算被下线机器还开着防火墙端口,也不会影响集群。

6. 高危操作与性能调优速查

6.1 五个高危操作清单

这些是我在实际维护中踩过或看别人踩过的坑,整理成一份清单,比背参数更有用:

  1. 重启前不检查磁盘空间:dataDir目录所在磁盘写满后,ZK会直接停止接受写入请求,但进程还在,排查时极难定位。
  2. 直接删除快照目录:有些人觉得数据不重要,直接把dataDir清了。这里要提醒,如果没有完整的事务日志,清掉后可能恢复出来的数据是缺失的。确认数据真的没用了再删。
  3. 添加节点时写错端口:同一个端口被多个server.N复用,会导致节点互相干扰,日志里出现“Address already in use”。
  4. 乱动选举时间参数:initLimit和syncLimit不是越大越好,调得太大,节点失联很久才触发重连,会影响整体故障转移速度。
  5. 用旧版本日志查新版问题:3.5以后日志格式改了,查看日志方式也不同,遇到问题先去官方文档确认当前版本的日志级别和输出位置。

6.2 性能与稳定性相关的关键参数

zoo.cfg里值得关注的参数有两个隐藏点,常规教程很少讲:

  • maxClientCnxns:限制单台节点能接受的客户端连接数,默认60,对高并发环境来说太小了。我见过Hadoop、Kafka共用一个ZK集群的场景,连接数轻松破千,默认值会直接拒绝新连接。
  • snapCount:默认100000,意思是每10万次事务,触发一次快照。如果写入量特别大,可以把这个值调大一些,减少快照频率,但快照本身会占IO,调优时要压测对比。

还有preAllocSize,这是事务日志预分配大小,默认64MB。如果单条事务很大,频繁分配文件会造成碎片,这属于进阶调优参数,一般不需要改。

6.3 安全加固三板斧

Zookeeper作为基础组件,安全往往被忽略。我个人建议至少做三件事:

第一,限制四字命令。上面说了,别用通配符,只开放你确实要用的命令。

第二,管理端口默认是8080。Zookeeper 3.5之后自带了一个Jetty管理服务,默认端口8080。这个端口如果暴露在外网,等于给了别人一个可趁之机。我习惯在zoo.cfg里把端口改掉,或者干脆禁用:

admin.enableServer=false

如果你需要监控接口,就改成高端口并做好访问控制。

第三,数据目录权限盯紧。dataDir和dataLogDir目录权限设为zookeeper用户所有,不要给其他用户读写权限。快照和事务日志里包含业务数据,权限太松会带来数据泄露风险。

7. 常见问题与排查技巧实录

7.1 启动失败类问题

现象1:启动日志里有“Cannot open channel to X at election address”

这个报错意味着当前节点连不上其他节点的3888端口。排查顺序是:

  1. 先ping业务IP,确认网络通不通。
  2. 再检查3888端口的防火墙和安全组是放行。
  3. 最后确认zoo.cfg里server.N的主机名解析是否正常。

现象2:节点启动后一直LOOKING,始终选不出Leader

这种情况通常是过半机制达不到。如果是3节点集群,看看是不是有一台机器没启动、卡死了。如果一台机器确实有问题,最好直接修好或下线,别让故障节点留在配置里反复干扰选举。

现象3:日志提示“Invalid config, unexpected value: server.4”

这个情况常见于老版本没有开启动态端口配置,或者配置文件格式写错。检查一下是不是把server.4=host:2888:3888写成了server.4=host:2888:3888:participant,3.5以下不支持这种带角色后缀的写法。

7.2 状态异常类问题

现象1:三台都显示follower,没有leader

集群已经故障了。先看日志,找哪台机器抛了异常,多半是磁盘满了或者选举端口不通。恢复方法通常是:先把故障节点隔离,再重新启动其余节点,让它们重新触发选举。

现象2:节点状态频繁在follower和LOOKING之间切换

这是典型的网络不稳定或者心跳超时。优先检查集群节点之间的网络延迟和丢包率,其次检查syncLimit设置是否过小。

7.3 与Hadoop/Kafka整合时的经典坑

和Hadoop整合时,最常见的坑是:Zookeeper连接串写法和Hadoop配置文件里的写法不一致。Hadoop的高可用配置里,dfs.ha.zookeeper.quorum要写完整的三节点列表:

<property> <name>dfs.ha.zookeeper.quorum</name> <value>zk01:2181,zk02:2181,zk03:2181</value> </property>

只写一个IP或者写错端口,NameNode的自动故障转移就会失效。

Kafka整合时,我建议使用chroot路径,也就是在连接串后面加路径,为不同集群做隔离:

zookeeper.connect=zk01:2181,zk02:2181,zk03:2181/kafka-prod

这样同一个Zookeeper集群可以给多个Kafka集群共用,互不干扰。如果你直接裸用根路径,多个Kafka集群的元数据会在根路径下碰撞,后续运维会非常头疼。

我把常见问题整理成一个速查表,方便你在排查时快速对照:

症状优先排查方向验证方法
启动即失败myid文件、磁盘权限、JDK版本查看zookeeper.out
选举超时3888端口连通性、initLimit大小两端执行nc测试端口
节点频繁抖动网络延迟、syncLimit配置ping延迟统计、查看Zookeeper日志
连接数满maxClientCnxns设置太小echo stat查看Count
管理端口意外暴露admin.serverPort未修改ss -lntp确认监听端口

每次排查完,记得把结论记录到运维文档里。这年头不涨记性,同样的问题会在三个月后换个方式再出现在你面前。

这次搭建下来,我个人最大的体会是:Zookeeper集群搭建这件事,真正花时间的不是敲命令,而是理解它的一致性模型和选举机制。配置参数就那几个,但如果你不理解过半机制,永远不知道为什么三台机器选一台Leader要等这么久;不理解两个端口的区别,就无法排查节点之间的通信故障。建议你先用三台机器在测试环境完整跑一遍,把Leader挂掉、把follower挂掉、把整个集群停掉再拉起来的几个场景都演练一遍,然后再上生产。到时候你就会发现,真正出问题的时候,你对这个集群的理解程度,决定了你的恢复速度。

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

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

立即咨询