ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Zookeeper集群搭建与原理拆解:三节点部署、选举机制与生产避坑

Zookeeper集群搭建与原理拆解:三节点部署、选举机制与生产避坑 做过分布式系统的人都知道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其中两个压根没部署每次重启KafkaZookeeper那台一重启整个链路就断。所以开发环境可以单机但连接串要如实配置成单机模式测试和生产环境强烈建议直接用三节点别为省服务器留隐患。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或113.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/logsPID文件目录/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文件。我先放一份生产环境常用配置tickTime2000 initLimit10 syncLimit5 dataDir/data/zk/data dataLogDir/data/zk/logs clientPort2181 maxClientCnxns60 autopurge.snapRetainCount3 autopurge.purgeInterval24 server.1zk01:2888:3888 server.2zk02:2888:3888 server.3zk03:2888:3888 4lw.commands.whitelist* admin.enableServertrue admin.serverPort8081每个参数我都建议你弄懂再上生产tickTime2000Zookeeper里的基础时间单位单位是毫秒。节点之间每次心跳的间隔就是2秒。这个值决定了后面initLimit和syncLimit的真实时长。initLimit10follower在启动时最多能落后Leader多少个tick也就是最多等10 * 2000 20秒完成初始同步。如果网络环境差可以调大到15甚至20但别太大。syncLimit5follower和Leader之间正常通信的超时5 * 2000 10秒。超过这个时间没收到Leader的消息follower会认为自己失联了重新发起选举。dataDir和dataLogDirdataDir是快照目录dataLogDir是事务日志目录。这两个目录一定要分开事务日志的写入频率远高于快照如果混在一个磁盘上IO竞争会很严重。我在测试环境第一次部署时没分开业务高峰期Zookeeper写延迟明显升高就是被日志和快照同时占IO拖的。3.2 server.1host:port:port 到底怎么填server.1zk01:2888:3888这句话是集群配置的核心。它的格式是server.序号主机名:数据同步端口:选举端口。两个端口很多人分不清我在这里说清楚2888端口Leader和follower之间同步事务日志、快照数据的端口。每次写请求到LeaderLeader要把事务发给所有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/myid3.4 用systemd把ZK托管起来生产环境里我强烈建议不要直接用zkServer.sh裸跑而是用systemd托管这样开机自启、崩溃重启、日志管理都省心。在/etc/systemd/system/zookeeper.service里写入[Unit] DescriptionZookeeper Service Afternetwork.target [Service] Typeforking Userzookeeper Groupzookeeper EnvironmentZOO_LOG_DIR/data/zk/logs EnvironmentPID_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 Restartalways RestartSec10 [Install] WantedBymulti-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.outzookeeper.out是Zookeeper的启动运行日志排查问题先看这个文件。注意zookeeper.out并不是zookeeper.logZookeeper的业务日志连接数、事务、选举日志在配置了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.whiteliststat,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 扩容前要做的检查新节点加入集群前建议先检查几件事版本一致性新节点的Zookeeper版本尽量和集群现有版本保持一致跨大版本混布容易出兼容性问题。JDK版本同样尽量一致Zookeeper对跨JDK版本混跑兼容性还行但没必要冒险。数据目录初始化新节点的dataDir目录要预先创建好并且是空的。如果里面残留了老数据启动后可能报快照不一致错误。5.2 滚动重启的节奏3节点扩到5节点的操作流程是这样的在现有三台机器的zoo.cfg里把server.4和server.5的配置加上server.4zk04:2888:3888 server.5zk05:2888:3888然后把新节点的配置文件同步好创建myid文件分别写4和5。接下来是滚动重启三台老节点。顺序很重要不要三台同时重启而是逐一重启。每次重启后等集群恢复稳定过半节点在线再重启下一台。如果三台同时重启集群会面临短暂的完全不可用。我在生产环境扩容时还会提前把新节点的网段加入防火墙白名单确保集群里的老节点能访问新节点的2888和3888端口不然新节点加进来后会反复报连接失败。5.3 缩容与下线的正确姿势缩容比扩容更敏感因为下线节点会影响集群的过半机制。比如5节点缩到3节点一次下线两台机器就会导致剩下3台仍然过半没大问题。但如果你在3节点集群里试图下线1台集群会变成2节点一旦再挂一台整体写入就卡死了。正确的下线顺序是先在zoo.cfg里把要下线的server.N配置注释或删除。再把该节点的myid文件备份后移走。最后停止该节点进程。这里还有个细节注释掉server.N后剩余节点重启时不会等待被移除的节点加入选举这样就算被下线机器还开着防火墙端口也不会影响集群。6. 高危操作与性能调优速查6.1 五个高危操作清单这些是我在实际维护中踩过或看别人踩过的坑整理成一份清单比背参数更有用重启前不检查磁盘空间dataDir目录所在磁盘写满后ZK会直接停止接受写入请求但进程还在排查时极难定位。直接删除快照目录有些人觉得数据不重要直接把dataDir清了。这里要提醒如果没有完整的事务日志清掉后可能恢复出来的数据是缺失的。确认数据真的没用了再删。添加节点时写错端口同一个端口被多个server.N复用会导致节点互相干扰日志里出现“Address already in use”。乱动选举时间参数initLimit和syncLimit不是越大越好调得太大节点失联很久才触发重连会影响整体故障转移速度。用旧版本日志查新版问题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.enableServerfalse如果你需要监控接口就改成高端口并做好访问控制。第三数据目录权限盯紧。dataDir和dataLogDir目录权限设为zookeeper用户所有不要给其他用户读写权限。快照和事务日志里包含业务数据权限太松会带来数据泄露风险。7. 常见问题与排查技巧实录7.1 启动失败类问题现象1启动日志里有“Cannot open channel to X at election address”这个报错意味着当前节点连不上其他节点的3888端口。排查顺序是先ping业务IP确认网络通不通。再检查3888端口的防火墙和安全组是放行。最后确认zoo.cfg里server.N的主机名解析是否正常。现象2节点启动后一直LOOKING始终选不出Leader这种情况通常是过半机制达不到。如果是3节点集群看看是不是有一台机器没启动、卡死了。如果一台机器确实有问题最好直接修好或下线别让故障节点留在配置里反复干扰选举。现象3日志提示“Invalid config, unexpected value: server.4”这个情况常见于老版本没有开启动态端口配置或者配置文件格式写错。检查一下是不是把server.4host:2888:3888写成了server.4host:2888:3888:participant3.5以下不支持这种带角色后缀的写法。7.2 状态异常类问题现象1三台都显示follower没有leader集群已经故障了。先看日志找哪台机器抛了异常多半是磁盘满了或者选举端口不通。恢复方法通常是先把故障节点隔离再重新启动其余节点让它们重新触发选举。现象2节点状态频繁在follower和LOOKING之间切换这是典型的网络不稳定或者心跳超时。优先检查集群节点之间的网络延迟和丢包率其次检查syncLimit设置是否过小。7.3 与Hadoop/Kafka整合时的经典坑和Hadoop整合时最常见的坑是Zookeeper连接串写法和Hadoop配置文件里的写法不一致。Hadoop的高可用配置里dfs.ha.zookeeper.quorum要写完整的三节点列表property namedfs.ha.zookeeper.quorum/name valuezk01:2181,zk02:2181,zk03:2181/value /property只写一个IP或者写错端口NameNode的自动故障转移就会失效。Kafka整合时我建议使用chroot路径也就是在连接串后面加路径为不同集群做隔离zookeeper.connectzk01: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挂掉、把整个集群停掉再拉起来的几个场景都演练一遍然后再上生产。到时候你就会发现真正出问题的时候你对这个集群的理解程度决定了你的恢复速度。
返回列表