ARTICLE DETAIL

资讯详情

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

ZooKeeper安装配置与集群搭建实战:从单机到三节点调优排错

ZooKeeper安装配置与集群搭建实战:从单机到三节点调优排错 1. 先把 ZooKeeper 这套东西想明白再动手1.1 它到底解决的是什么问题很多人第一次接触 ZooKeeper是在别人的架构图里看到一个小小的方框标注着协调服务然后教程第一句就是下载解压改配置启动。照着做完zkServer.sh status显示Mode: standalone心里却没什么底——这东西到底干嘛的我用一句人话概括ZooKeeper 是一个高可用的小容量配置与状态存储中心它保证多个进程看到的数据是完全一致的并且能在节点故障时自动重新选主继续服务。拆开来看有三个关键点。第一是小容量单个 znode 默认限制 1MB整棵树的数据全放在内存里所以别拿它当数据库用塞几十个 G 的日志进去必然崩。第二是一致性一次写成功集群里所有节点后续读到的都是这个值这是它最核心的价值分布式锁、选主、配置下发全靠这个特性。第三是高可用只要集群里过半节点活着服务就不中断。哪些场景会用到它最典型的是Hadoop HA 的自动故障切换ZKFC、HBase 的 RegionServer 状态管理与元数据入口、Kafka 老版本的 Controller 选举与元数据存储、HiveServer2 的动态服务发现以及各种自研的分布式锁、命名服务、配置中心。所以你在搜 Hadoop 和 ZooKeeper 整合实战、HBase 安装与配置这些内容时绕不开的第一步就是先把 ZK 本身跑稳。1.2 部署形态怎么选单机、伪集群、真集群这是个必须先想清楚的问题选错了后面白折腾。我见过太多人在一台 4G 内存的测试机上强行起三个 JVM 做集群结果内存吃满、节点互相抢 CPU选举来回抖动最后判定ZooKeeper 不稳定。形态节点数适用场景需要配 server.x 吗故障容忍单机 standalone1本地开发、跑单元测试、客户端联调不需要无挂了就是挂了伪集群3学习选举流程、验证集群配置语法需要端口要错开容忍 1 台进程退出真集群3 / 5 / 7开发 / 预发 / 生产环境需要3 节点容忍 15 节点容忍 2这里有个必须记住的数学关系集群可用节点数必须严格大于总节点数的一半也就是n 2f 1f 是允许挂掉的节点数。所以 4 个节点的集群和 3 个节点一样只能容忍 1 台故障多出来的那台纯属浪费。这就是为什么你几乎见不到偶数节点的 ZK 集群。我的建议很直接本地开发用单机学习集群用伪集群测试环境起步就上 3 台真机。千万别用伪集群冒充生产环境因为三台机器的时间同步、网络延迟、磁盘 IO 干扰模式跟单机三进程完全不同很多真集群才暴露的问题你在伪集群里永远看不到。1.3 版本与运行环境的前置确认版本这块踩过的坑值得单独说一下。ZooKeeper 从 3.5.5 开始官网下载页上的apache-zookeeper-x.y.z.tar.gz是源码包只有apache-zookeeper-x.y.z-bin.tar.gz才是能直接跑的二进制包。如果你下错了包解压后会发现在bin/目录里找不到zkServer.sh或者勉强找到脚本却报Could not find or load main class org.apache.zookeeper.server.quorum.QuorumPeerMain。这不是配置问题是包下错了。版本选择上我给几个参考区间3.4.x老项目的稳定选择配置项少日志走 log4j很多老教程基于这个版本。但已经不推荐用于新项目。3.5.x ~ 3.6.x引入了动态配置、AdminServer、logback 日志体系是目前存量最多的一代。3.8.x / 3.9.x当前主推版本Java 8 及以上都能跑3.9 官方要求 Java 8实测 Java 11 / 17 也稳定TLS、指标导出、四字命令白名单都更完善。JDK 方面不要用 JDK 6/7 了直接上JDK 8 的较新小版本或 JDK 11。如果你机器上还跑着 Hadoop 2.x、Hive 1.x 这类老组件统一用 JDK 8 最省心避免出现组件之间类库版本打架。还有两个容易忽略的前置条件文件句柄数和主机名解析。ZK 每个客户端连接、每次快照文件读写都占文件描述符官方建议把ulimit -n调到 65535 以上主机名必须能从每台机器上互相解析到用/etc/hosts或内网 DNS 都行否则集群配置里写的server.1zk1:2888:3888会在启动时卡在无法连接的状态上。2. 安装前的环境准备与依赖梳理2.1 JDK 的安装与验证别跳过这一步JDK 安装本身没什么技术含量但它是最容易埋雷的环节。我见过JAVA_HOME指向了一个 JRE 目录而不是 JDKZK 启动脚本里的java也能跑起来但某些依赖反射和工具类的组件就会出问题。还有人把JAVA_HOME写在~/.bashrc里然后用sudo或 systemd 启动服务环境变量根本没加载脚本直接报JAVA_HOME is not set。标准操作是这样# 1. 解压 JDK示例路径按你实际情况调整 tar -zxvf jdk-8u381-linux-x64.tar.gz -C /usr/local/ mv /usr/local/jdk1.8.0_381 /usr/local/jdk8 # 2. 配置全局环境变量写在 /etc/profile.d/ 下更稳妥 cat /etc/profile.d/java.sh EOF export JAVA_HOME/usr/local/jdk8 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar EOF # 3. 立即生效并验证 source /etc/profile.d/java.sh java -version echo $JAVA_HOME注意/etc/profile只对登录 shell 生效。如果你打算用 systemd 托管 ZK 服务systemd 不会读取/etc/profile必须在 service 文件里用EnvironmentJAVA_HOME/usr/local/jdk8显式声明这是我自己被坑过两次才记住的细节。验证的时候别只看java -version要确认三件事java -version输出的版本和你预期一致、which java指向的是/usr/local/jdk8/bin/java而不是系统自带的/usr/bin/java、echo $JAVA_HOME有值。三者都对才算过关。2.2 主机规划、免密登录与时间同步假设我们规划三台机器事先约定好主机名和 IP后面所有配置都基于这个表来写避免写错一个字母排查半小时。主机名IPmyid角色zk1192.168.10.111任意选主后可能成为 Leaderzk2192.168.10.122任意zk3192.168.10.133任意每台机器上执行hostnamectl set-hostname zk1然后统一在三台机器的/etc/hosts里加上三行映射。这一步比配 SSH 免密还重要因为 ZK 集群内部的server.x列表用的是主机名或 IP如果解析失败启动日志里会反复刷Cannot open channel to 2 at election address。时间同步是另一个隐形杀手。ZK 的会话超时、心跳检测都基于时间如果两台机器时间差超过几十秒跟随者会被 Leader 判定为失联触发反复选举表现为集群状态在LOOKING和LEADING之间来回跳。用 chrony 配一下yum install -y chrony # CentOS/RHEL 系 systemctl enable --now chronyd chronyc sources -v # 确认已同步到上游时间源 date # 三台机器分别执行误差应在秒级SSH 免密不是 ZK 运行的必需条件但它能让你在分发配置和批量启动时省掉大量重复劳动。生成密钥后ssh-copy-id到另外两台之后一条for循环就能搞定所有节点的启停。2.3 端口开放与防火墙策略ZK 用到三个端口各自职责完全不同务必记牢2181客户端连接端口所有业务程序连这个。2888集群内部数据同步端口Follower 连 Leader 用的。3888选举通信端口选主阶段所有节点互相通信用的。生产环境只对业务网段开放 21812888 和 3888 只对集群内部三台机器开放这是基本的安全边界。如果你用的是 firewalld# 只在集群内部互相开放 firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.10.0/24 port port2888 protocoltcp accept firewall-cmd --permanent --add-rich-rulerule familyipv4 source address192.168.10.0/24 port port3888 protocoltcp accept firewall-cmd --permanent --add-port2181/tcp firewall-cmd --reload另外提醒一个 3.5 之后版本特有的坑新版本会自动启动一个AdminServer默认占用8080 端口。如果你的机器上已经跑了 Tomcat、Nacos 或者别的什么占着 8080ZK 启动时会在日志里报端口绑定失败。解决办法是在zoo.cfg里加一行admin.enableServerfalse如果不需要这个 HTTP 管理接口或者改成别的端口admin.serverPort8081。这个小东西坑了不少人因为它的报错信息不算特别醒目。3. 单机模式安装实操从解压到跑起来3.1 安装包获取与目录规划先把安装包拿到本地。官网地址可能会变用wget下载的时候认准带-bin的那个文件名。假设我们在/opt下操作cd /opt wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.4/apache-zookeeper-3.8.4-bin.tar.gz tar -zxvf apache-zookeeper-3.8.4-bin.tar.gz mv apache-zookeeper-3.8.4-bin /opt/zookeeper目录规划这块我要多说两句因为这是很多人后期扩容和排查问题的痛点。数据目录和事务日志目录最好分开并且都不要放在系统盘上。原因在于 ZK 的写路径是这样的事务日志transaction log是顺序追加写入的每来一个写请求都要fsync到磁盘才会应答客户端而快照snapshot是定期全量落盘。顺序写加上频繁 fsync对磁盘的写延迟极其敏感。如果事务日志和系统盘上的其他 IO 抢资源写延迟就会上升进而拉长事务提交时间严重的时候会触发会话超时整集群跟着抖。我给一个常用的目录结构参考mkdir -p /data/zookeeper/data # 存放 myid 和快照 mkdir -p /data/zookeeper/logs # 存放事务日志 chown -R zookeeper:zookeeper /data/zookeeper # 如果建了专用账号实操心得/data最好是独立的 SSD 或高性能云盘。如果预算有限只能一块盘那至少保证数据目录不在根分区避免磁盘写满导致整个系统挂掉——ZK 的快照目录不清理的话是真的能把盘写满的后面我会讲自动清理配置。3.2 zoo.cfg 逐行拆解与参数含义配置文件在conf/目录下。官方给了个zoo_sample.cfg模板直接复制改就行cd /opt/zookeeper/conf cp zoo_sample.cfg zoo.cfg单机模式的最小可用配置长这样# 基本时间单元毫秒。ZK 里所有时间相关的参数都是它的倍数 tickTime2000 # 数据目录必须存在且可写 dataDir/data/zookeeper/data # 客户端连接端口 clientPort2181 # 单个客户端 IP 允许的最大并发连接数默认 60 maxClientCnxns200 # 关闭 AdminServer避免 8080 端口冲突 admin.enableServerfalse # 四字命令白名单生产环境按需开启不要图省事写 * 4lw.commands.whitelistmntr,ruok,conf,stat我把几个关键参数的真实含义讲清楚这样你改配置的时候心里有数tickTime是整个 ZK 的时间度量基准单位毫秒默认 2000。它本身不控制任何具体行为而是作为其他参数的乘数因子。理解这一点很重要后面讲集群参数时你会看到initLimit10实际代表 20 秒。dataDir存两样东西一是myid文件集群模式才需要二是快照文件放在version-2/子目录里文件名类似snapshot.0后面跟一串十六进制的事务 ID。注意如果没配dataLogDir事务日志也会写到这里这就是我前面说要把两者分开的原因。maxClientCnxns限制的是单个客户端 IP 的连接数不是总连接数。生产环境默认 60 往往不够因为一个应用服务器上可能起了几十个线程池连同一个 ZK加上连接池复用不当很容易撞上限。撞上之后的表现是客户端报ConnectionLoss或者干脆连不上但服务端日志看不出明显异常排查起来很费劲。还有一个容易被忽略的配置是clientPortAddress。默认情况下 ZK 监听所有网卡的 2181 端口如果机器有多张网卡比如一张业务网卡一张管理网卡建议用clientPortAddress明确指定只监听业务网卡减少暴露面。3.3 启动、验证与关闭的三板斧配置写完就能起了cd /opt/zookeeper ./bin/zkServer.sh start # 启动后台运行 ./bin/zkServer.sh status # 查看状态 ./bin/zkServer.sh stop # 停止 ./bin/zkServer.sh restart # 重启启动后status应该输出Mode: standalone。如果输出的是Client port found: 2181. Client address: localhost.之类的信息但没有 Mode说明进程可能没起来去看日志。日志位置取决于版本。3.5 之前走 log4j日志默认在$ZOO_LOG_DIR指向的目录通常是启动脚本所在的目录3.5 之后走 logback配置文件是conf/logback.xml日志目录由ZOO_LOG_DIR环境变量控制。如果找不到日志直接看nohup.out或者用ps -ef | grep zookeeper看进程在不在。# 确认进程和端口 ps -ef | grep QuorumPeerMain | grep -v grep ss -lntp | grep 2181 # 用四字命令验证服务活着 echo ruok | nc 127.0.0.1 2181 # 期望返回 imokruok返回imok是服务健康的标志。注意 3.4.10 之后四字命令默认是白名单制的只有srvr在白名单里其他命令不配置就返回ruok is not executed because it is not in the whitelist.。这不是故障是安全策略按需在zoo.cfg里加上4lw.commands.whitelistmntr,ruok,conf,stat就行。3.4 用 systemd 接管别再用脚本硬启zkServer.sh start是能跑但它有几个问题机器重启后不会自动拉起、日志分散、进程挂了没人管、多节点批量操作只能靠 SSH 循环。生产环境建议用 systemd 托管。# /etc/systemd/system/zookeeper.service [Unit] DescriptionApache ZooKeeper Afternetwork.target [Service] Typeforking Userzookeeper Groupzookeeper EnvironmentJAVA_HOME/usr/local/jdk8 EnvironmentZOO_LOG_DIR/data/zookeeper/logs ExecStart/opt/zookeeper/bin/zkServer.sh start ExecStop/opt/zookeeper/bin/zkServer.sh stop ExecReload/opt/zookeeper/bin/zkServer.sh restart Restarton-failure RestartSec10 LimitNOFILE65535 [Install] WantedBymulti-user.targetsystemctl daemon-reload systemctl enable --now zookeeper systemctl status zookeeper注意Typeforking是必须的因为zkServer.sh start会派生后台进程然后退出。这个字段写错成simple的话systemd 会认为进程已经结束然后反复重启日志里能看到明显的循环。另外LimitNOFILE65535这行务必加上systemd 默认的句柄数限制是 1024对 ZK 来说远远不够。4. 集群模式搭建伪集群与三节点真集群4.1 伪集群怎么搭一台机器演三个角色伪集群的价值在于验证配置语法和观察选举过程学习阶段非常有意义但别拿它当生产环境的替身。核心思路是三个实例各自独立的dataDir、独立的clientPort、独立的日志目录但共用同一份安装包通过三个配置文件区分。假设做在/opt/zookeeper下mkdir -p /data/zk-cluster/{zk1,zk2,zk3}/data mkdir -p /data/zk-cluster/{zk1,zk2,zk3}/logs三个配置文件片段分别放到conf/zoo1.cfg、conf/zoo2.cfg、conf/zoo3.cfg# zoo1.cfg tickTime2000 initLimit10 syncLimit5 dataDir/data/zk-cluster/zk1/data dataLogDir/data/zk-cluster/zk1/logs clientPort2181 admin.enableServerfalse server.1127.0.0.1:2888:3888 server.2127.0.0.1:2889:3889 server.3127.0.0.1:2890:3890zoo2.cfg只需要把clientPort换成 2182、dataDir/dataLogDir换成 zk2 的路径而server.x列表必须三个文件里完全一致。这是伪集群最容易写错的地方——很多人只改自己那一行结果三个实例互相找不到对方一直卡在LOOKING状态。然后给每个实例写 myidecho 1 /data/zk-cluster/zk1/data/myid echo 2 /data/zk-cluster/zk2/data/myid echo 3 /data/zk-cluster/zk3/data/myid启动时通过环境变量指定配置文件名ZOO_LOG_DIR/data/zk-cluster/zk1/logs \ ./bin/zkServer.sh --config conf start zoo1.cfg依次启动三个实例然后分别status你应该能看到一个Mode: leader、两个Mode: follower。如果三个都是Mode: standalone说明server.x配置没被识别检查配置文件路径和文件名是否对应上了。4.2 真集群的完整操作流程真集群的配置反而比伪集群简单因为三个文件内容完全一样除了 myid。以第一节规划的三台机器为例每台机器上都执行# /opt/zookeeper/conf/zoo.cfg三台机器内容完全相同 tickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper/data dataLogDir/data/zookeeper/logs clientPort2181 maxClientCnxns500 autopurge.snapRetainCount5 autopurge.purgeInterval24 admin.enableServerfalse 4lw.commands.whitelistmntr,ruok,conf,stat server.1zk1:2888:3888 server.2zk2:2888:3888 server.3zk3:2888:3888注意server.x后面的两个端口格式是主机:数据同步端口:选举端口。两个端口号在三台机器上可以相同因为 IP 不同但在同一台机器上跑多个实例时绝对不能冲突这就是伪集群要错开端口的原因。myid 是每台机器唯一不同的地方# zk1 上执行 echo 1 /data/zookeeper/data/myid # zk2 上执行 echo 2 /data/zookeeper/data/myid # zk3 上执行 echo 3 /data/zookeeper/data/myid注意myid 文件里只写一个数字不要有多余的空格或换行符形式的内容数字后面跟一个换行符是允许多数版本接受的但为了保险用echo -n更稳。我遇到过有人用echo 1 myid写了个带空格的结果启动时报java.lang.NumberFormatException: For input string: 1 报错信息在一大堆日志里特别不显眼找了很久。启动顺序没有强制要求依次启动即可。等所有节点起来每个都执行zkServer.sh status正常情况下应该有一个 Leader 两个 Follower# zk1 Mode: follower # zk2 Mode: leader # zk3 Mode: follower验证集群是否真的在工作最直接的方法是往一个节点写数据从另一个节点读# 在 zk1 上连接 ./bin/zkCli.sh -server zk1:2181 [zk: zk1:2181(CONNECTED) 0] create /cluster-test hello-zk # 在 zk3 上连接 ./bin/zkCli.sh -server zk3:2181 [zk: zk3:2181(CONNECTED) 0] get /cluster-test # 能读到 hello-zk 就说明数据同步正常4.3 myid 与 server.x 的对应关系以及集群参数推导myid和server.x的对应关系是集群配置里唯一需要配对的地方配错的后果很严重轻则节点起不来重则两个节点认为自己是同一个 ID导致选举混乱。规则很简单某台机器的 myid 文件内容必须等于zoo.cfg里代表这台机器的那一行的编号 x。比如 zk1 的 myid 是 1那server.1zk1:2888:3888这一行的主机名就必须是 zk1。如果你把 zk1 的 myid 写成 2那 zk1 会以 ID2 的身份加入集群而配置里 ID2 对应的是 zk2 的地址其他节点会尝试往 zk2 发数据整个集群的拓扑就乱了。再来说集群参数怎么推导这两个参数很多人都是照抄别人的从来不知道为什么是 10 和 5。initLimit10的含义是Follower 在启动阶段与 Leader 完成连接和数据同步的最长时间限制单位是tickTime的倍数。默认tickTime20002 秒所以initLimit10代表 20 秒。如果你的数据量很大比如几十万个 znode 的快照Follower 同步需要更久20 秒可能不够这时就要调大initLimit比如 30 甚至 50。syncLimit5的含义是运行期间 Follower 与 Leader 之间心跳超时的限制同样是 tickTime 倍数默认 10 秒。如果网络抖动严重或者机器负载很高心跳可能延迟syncLimit太小会导致 Follower 被踢出集群触发重新选举。网络条件一般的环境可以调到 10。还有一个跟客户端直接相关的推导会话超时时间session timeout的取值范围是 2 倍到 20 倍的 tickTime。默认 tickTime 是 2000 毫秒那客户端能协商的超时就是 4 秒到 40 秒之间。这个细节很关键——如果你在客户端代码里设置了 60 秒的超时ZK 服务端会拒绝并强制降到 40 秒你在日志里会看到Session establishment complete on server之后超时值和预期不一致。这就是为什么调整tickTime会连带影响客户端行为改之前一定要评估现有应用设置。5. 关键参数调优JVM 与运行时5.1 JVM 堆内存到底给多少才合适这是个高频问题网上的回答从 512M 就够 到 给 16G 都有差距大得离谱。我用一个粗算模型来推导你照着套自己的数据量。ZK 把所有 znode 的元数据都放在内存里包括 znode 路径字符串、数据内容字节数组、ACL、状态信息以及几个 watch 相关的哈希表。经验上单个 znode 的固定内存开销大致在 150 到 250 字节之间再加上路径字符串本身和数据内容。粗略估算总内存需求 ≈ znode 数量 × (路径长度 数据大小 200 字节) × 2公式里乘 2 是给 DataTree 内部多份结构和 GC 碎片留的余量。假设你有 30 万个 znode平均路径长度 40 字节每个 znode 数据 100 字节那单份大约是 30万 × 340 字节 ≈ 100MB乘 2 就是 200MB 左右。这种情况下堆给 1G 绰绰有余。但现实里 ZK 内存爆炸往往不是因为 znode 多而是因为客户端滥用比如把配置信息整个塞进一个 znode动辄几百 KB、创建海量临时节点做服务注册每秒几千个、或者 watch 数量失控。我见过一个案例某个服务用 ZK 做分布式 ID 生成器每次请求创建一个 znode一晚上积累了两百万个节点堆直接 OOM。所以实际给堆大小的时候我按这个顺序判断znode 规模建议堆大小说明10 万以内1G ~ 2G绝大多数中小规模场景绰绰有余10 万 ~ 50 万2G ~ 4G需要通过 mntr 监控 znode 数量增长趋势50 万以上4G ~ 8G并考虑架构优化堆越大 GC 停顿越长反而影响会话稳定性堆不是越大越好这是我想强调的重点。ZK 对延迟极其敏感一次 Full GC 停顿 5 秒就可能让一批客户端的会话超时、触发重连甚至重新选举。所以宁可把 znode 数量控制住也不要靠堆来硬扛。配置 JVM 参数的地方在conf/java.env这个文件默认不存在需要自己创建cat /opt/zookeeper/conf/java.env EOF export JVMFLAGS-Xms4g -Xmx4g -XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m \ -XX:UseG1GC -XX:MaxGCPauseMillis200 \ -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/zookeeper/logs/ \ -Dzookeeper.skipACLno EOF几个选择理由-Xms和-Xmx设成一样避免运行期堆伸缩带来的额外开销和停顿用 G1 而不是 CMSCMS 在新版 JDK 已经被移除G1 的MaxGCPauseMillis200能帮助我们约束单次停顿加上 HeapDumpOnOutOfMemoryError万一真 OOM 了至少有现场可查不然只能靠猜。5.2 日志与快照的清理不做迟早出事我在前面反复提磁盘会被写满现在说清楚为什么。ZK 每次写操作会往事务日志追加记录每隔snapCount默认 100000次事务会做一次全量快照快照和日志都是新文件旧文件不删。一个中等写入压力的集群事务日志一天能长几百 MB 到几个 G快照文件单个可能上百 MB。跑上几个月磁盘就满了。官方提供了自动清理参数# 保留最近 5 份快照以及与之相关的日志 autopurge.snapRetainCount5 # 每 24 小时清理一次单位是小时设为 0 表示不自动清理 autopurge.purgeInterval24这两个参数必须一起配只配purgeInterval不配snapRetainCount的话默认保留 3 份可能不够只配snapRetainCount不配purgeInterval的话清理任务根本不会启动这是新手最容易犯的错。如果因为某些原因比如老版本不能用自动清理那就写个定时任务手工清理思路是保留最近 N 份快照文件和它们对应的事务日志删除更早的。但我要提醒一句删除事务日志之前必须确认这批日志对应的快照已经存在。日志是用来在快照基础上回放的如果删掉了比最新快照还新的日志数据就丢了。所以手工清理比较危险能用官方参数就别自己写脚本。5.3 数据目录的权限与文件系统选择数据目录的所有者必须是运行 ZK 的用户。如果你用 root 启动过一次version-2目录就归 root 了之后换 zookeeper 用户启动会报Permission denied而且这个报错有时候只出现在日志文件里启动脚本返回的信息看起来很正常。养成习惯先chown -R再启动。chown -R zookeeper:zookeeper /data/zookeeper chmod 755 /data/zookeeper文件系统方面XFS 和 ext4 都能跑但要注意挂载参数。如果用了datawriteback之类的延迟写模式ZK 的fsync语义会打折扣虽然性能数据好看但掉电时的一致性就没了保证。普通业务用默认挂载参数就行别为了跑分去调这些。还有一点是关于dataLogDir的磁盘预分配。ZK 会为事务日志预分配空间默认preAllocSize65536单位 KB也就是 64MB。这个值的含义是每次预分配 64MB 的文件空间如果写入量很小会浪费一些空间如果写入量大增大它比如 128MB可以减少文件扩容次数。只有在你的日志盘空间特别紧张时才需要关注这个参数一般保持默认。6. 客户端连接与节点基础操作验证6.1 用 zkCli 走一遍节点基本操作集群搭完用自带的命令行客户端验证是最直接的方式。zkCli.sh的用法./bin/zkCli.sh -server zk1:2181,zk2:2181,zk3:2181连接串里写上多个地址是个好习惯客户端会自动尝试列表里的节点某个挂了能切换到下一个。虽然内网环境一般不会频繁切换但生产环境的客户端代码都应该这么写。进去之后是交互式命令行我把最常用的几个操作列一下这也是节点基本操作这一系列内容的核心# 创建持久节点 create /app app-root # 创建带数据的子节点 create /app/config timeout3000 # 列出子节点 ls /app # 读取节点的数据和状态信息 get /app/config # 输出里 cZxid / ctime / mZxid / pZxid / cversion / dataVersion # 这些字段是排查并发写问题的关键后面细说 # 修改数据 set /app/config timeout5000 # 创建临时节点会话结束自动删除 create -e /app/lock-001 holder-1 # 创建顺序节点自动在名字后加 10 位序号 create -s /app/task/task- job # 删除节点只能删没有子节点的 delete /app/config # 递归删除不同版本命令名不同先敲 help 确认get命令返回的那些字段值得单独解释很多人看不懂就直接忽略了。dataVersion每改一次数据加 1这是实现乐观锁的基础——客户端可以用set的版本参数做 CAS 操作。cversion是子节点列表的版本号每增删一个子节点加 1。ephemeralOwner如果不为 0说明这是个临时节点值是持有该会话的 ID。实操心得删除带子节点的路径时delete会直接报错。老版本3.4有rmr命令可以递归删3.5 之后这个命令被移除改成了别的形式而且不同小版本之间命令有增减。最稳的做法是进命令行先敲help看一眼当前版本支持什么别照着两年前的教程硬敲报 Command not found 还以为是环境问题。6.2 四字命令监控比看日志快得多排查 ZK 问题的时候日志往往是滞后的、海量的而四字命令是即时的、结构化的。前面已经在zoo.cfg里放开了白名单现在可以用了echo mntr | nc 127.0.0.1 2181mntr返回的是一行一个key value的指标重点关注这几个指标含义异常判断zk_avg_latency平均请求延迟毫秒持续超过 10ms 就要关注磁盘 IOzk_max_latency最大请求延迟偶发高值可能是 GC 停顿zk_outstanding_requests排队请求数长期大于 0 说明处理不过来zk_znode_countznode 总数增长趋势比绝对值更重要zk_watch_countwatch 数量异常增长可能是客户端泄漏zk_num_alive_connections活跃连接数突然暴涨要查客户端连接池zk_open_file_descriptor_count打开的文件句柄数接近上限时服务会开始拒绝连接zk_followersFollower 数量少于预期说明有节点掉线zk_server_state节点角色leader / follower / standalone另外两个常用的echo stat | nc host 2181输出概览信息包含连接列表和节点角色echo cons | nc host 2181输出每个连接的详细信息包括客户端地址、会话 ID、排队请求数排查到底是哪个客户端在压我的时候特别有用。6.3 和 Hadoop / HBase / Hive 整合时的对接要点这是很多人搭完 ZK 之后的下一步也是报错最多的地方。核心原则是ZK 只提供存储和一致性其他组件通过命名空间znode 路径来隔离自己的数据。不同组件默认用的路径不一样路径对不上就是各种读不到配置。几个常见组件的默认路径和各自主的配置项组件关键配置项默认根路径说明Hadoop HAha.zookeeper.quorum/hadoop-haZKFC 用来做自动主备切换HBasehbase.zookeeper.quorum/zookeeper.znode.parent/hbaseHBase 的元数据入口HiveServer2hive.zookeeper.quorum/hive.zookeeper.namespace/hiveserver2动态服务发现Kafka老版本zookeeper.connect无默认根路径需显式指定现在新版本已逐步弃用 ZK拿热词里提到的那个典型报错来说unable to read hiveserver2 configs from zookeeper。这个报错的字面意思是从 ZK 读不到 HiveServer2 的配置根因通常有三种客户端的hive.zookeeper.quorum指向的 ZK 地址不对hive.zookeeper.namespace配置的路径跟 ZK 上实际注册的路径不一致或者 HiveServer2 实例根本没启动完还没往 ZK 上写自己的连接信息。排查顺序就是先确认 ZK 地址通不通再用zkCli连上去ls /hiveserver2看路径存不存在、里面有没有子节点两者信息一对照问题基本就定位了。注意如果你的 HBase 和 Hadoop 共用同一个 ZK 集群务必确认两边的根路径不同比如/hbase和/hadoop-ha。同时强烈建议给 ZK 加权限控制开启 ACL 并配置zookeeper.DigestAuthenticationProvider.superDigest否则任何能访问 2181 端口的人都能读写这些关键节点把整个集群的元数据删掉也不是不可能。生产环境把这个当安全底线别嫌麻烦。7. 踩坑实录与常见问题速查7.1 启动后进程直接没了日志也找不到这是最让人抓狂的情况敲完start命令返回了ps一看进程不存在日志目录空空如也。这类问题的排查路径固定按顺序走第一步看标准输出有没有被重定向。新版zkServer.sh会把启动日志写到$ZOO_LOG_DIR/zookeeper.out或者logs/zookeeper.out。找不到就全局搜find / -name zookeeper.out -mmin -10。第二步手动前台启动看报错。这是最快的方法cd /opt/zookeeper bin/zkServer.sh start-foreground前台启动会把日志直接打在屏幕上报错信息一目了然。常见的几种报错和对应原因报错关键词根因解决方向java.net.BindException: Address already in use2181/2888/3888/8080 被占用ss -lntp找占用进程或关掉 AdminServerNo such file or directory: .../myiddataDir 下缺 myid 文件检查路径和文件名拼写NumberFormatExceptionmyid 内容不是纯数字去掉多余空格用echo -n重写JAVA_HOME is not set环境变量没加载在 systemd 或启动前显式 exportClassNotFoundException: QuorumPeerMain下错包源码包换成-bin.tar.gzPermission denied写 version-2dataDir 属主不对chown -R修正第三步检查磁盘空间。磁盘满了的话ZK 启动时会因为写不了快照文件而失败报错信息有时候很含蓄。df -h看一眼尤其检查dataDir和dataLogDir所在分区。7.2 集群起不来一直卡在 LOOKING 状态集群搭好后zkServer.sh status一直显示Mode: Looking或者报Error contacting service. It is probably not running.。这是集群模式下最典型的问题按下面的清单挨个对第一三个节点的zoo.cfg里的server.x列表是否完全一致。这是最高频的失误。三台机器的配置文件除了 myid 之外其他内容必须一模一样。用md5sum快速对比md5sum /opt/zookeeper/conf/zoo.cfg # 三台机器分别执行结果应该一致myid 是单独的文件不影响 zoo.cfg 的哈希第二myid 和 server.x 的编号是否配对。前面讲过zk1 的 myid 必须是 1且server.1那行必须写 zk1 的地址。第三2888 和 3888 端口是否互通。从 zk1 上测试nc -zv zk2 2888 nc -zv zk2 3888如果其中一个不通就是防火墙或安全组的锅。第四主机名能否解析。ping zk2看是否通。用 hostname 配置的话三台机器或它们的/etc/hosts都必须能解析到彼此。第五时间是否同步。时间差过大会导致选举消息里的逻辑时间戳乱掉。用date对比三台机器。第六看选举日志。日志里搜Notification、Looking、Vote这些关键词能看到完整的选举过程。如果日志里有节点反复在LOOKING和LEADING之间切换多半是网络不稳或者时间不同步。7.3 客户端连接报错会话频繁掉线客户端侧的问题表现是ConnectionLoss、SessionExpired、OperationTimeout或者应用日志里刷KeeperErrorCode ConnectionLoss。先区分两类问题。ConnectionLoss说明请求发出了但没收到响应通常是网络抖动或服务端处理不过来SessionExpired更严重说明客户端在会话超时时间内没能和服务端心跳成功这个会话已经彻底失效会话上创建的临时节点全部被删除了。排查方向第一检查会话超时设置是否合理。客户端设置的超时要落在2×tickTime到20×tickTime之间。很多人为了更稳把超时设成 60 秒结果被服务端强制降到 40 秒客户端代码里如果按 60 秒做重试逻辑就会错位。第二检查 GC 停顿。服务端如果发生长 GC所有客户端都会受影响。用mntr看zk_max_latency如果它和你的 GC 停顿时间量级接近那问题就在堆配置上。第三检查网络是否有丢包或延迟抖动。ZK 的心跳对网络质量比较敏感跨机房部署尤其要注意能用同机房就别跨。第四检查客户端连接池实现。有些连接池在检测到会话失效后不会主动重建就一直拿着死会话重试表现是永久性的失败。这种情况要在代码层加会话事件监听收到Expired事件时重建客户端。7.4 常见问题速查表我把上面这些零散的经验整合成一张表出问题的时候可以直接对照现象最可能的原因快速验证方法处理动作单机启动失败无日志JDK 环境变量或包下载错误bin/zkServer.sh start-foreground看报错修 JAVA_HOME / 换 bin 包集群一直 Lookingzoo.cfg 不一致或 myid 配错md5sum 对比配置核对 myid统一配置纠正 myid节点反复切主时间不同步 / 网络抖动date对比ping测丢包配 chrony排查网络客户端 ConnectLoss会话超时设置不合理检查客户端超时是否在 2x~20x tickTime调整超时并加重试磁盘持续增长未开自动清理du -sh看 version-2 目录配 autopurge 参数8080 端口冲突AdminServer 默认启用ss -lntpgrep 8080四字命令返回白名单提示未配置 4lw 白名单直接执行 echo mntrncOOMznode 数量过多或客户端滥用看堆 dumpmntr看 znode_count清理无用节点优化客户端逻辑句柄耗尽ulimit 限制过低mntr看 open_file_descriptor_count调大 LimitNOFILE 到 65535写延迟高事务日志盘 IO 瓶颈mntr看 avg_latency日志盘换 SSDdataLogDir 分离7.5 版本升级时最容易忽略的两件事如果你的 ZK 已经跑了几年想升级到 3.8 或 3.9有两件事必须先确认不然升级完会有一堆莫名其妙的报错。第一是日志框架的切换。3.5 之后从 log4j 换成 logback配置文件从log4j.properties变成logback.xml。如果你之前改过log4j.properties来调整日志级别和滚动策略升级后这些改动全部失效日志会按新版本的默认策略输出。更麻烦的是如果你的安装目录里还残留着老的 log4j jar 包可能会出现类路径下有多个 SLF4J 绑定的警告甚至启动时类加载冲突。第二是 AdminServer 的默认开启。这是 3.5 引入的新特性默认监听 8080。老版本没有这个东西升级完如果机器上 8080 被占用启动就会失败。所以升级前先确认端口占用情况或者在配置里提前关掉它。实操心得升级 ZK 千万别直接覆盖安装目录。我的做法是新版本解压到一个新目录把老的zoo.cfg、myid、java.env复制过去停掉老版本改软链接指向新目录然后用同样的路径启动。这样出问题可以秒回滚——把软链接改回去就行。ZK 的数据目录格式在 3.4 到 3.5 之间有过兼容性说明跨大版本升级前一定要读官方的升级说明确认数据目录能不能直接复用必要时先在一个从节点上做灰度。再补一个小技巧验证新版本是否正常除了看status和mntr还可以观察一段时间内zk_avg_latency和zk_max_latency的变化趋势跟老版本的数据做对比。我自己的经验是如果升级后平均延迟没有明显变化、最大延迟没有出现规律性的尖峰基本可以判定升级平稳如果最大延迟的尖峰变得频繁多半是 GC 参数需要跟着新版本的默认值重新调整这时候回到java.env里重新调一版 JVM 参数就行了。
返回列表