1. “not connected”不是一条错误日志那么简单你是不是也在某天凌晨被一条ZooKeeper not connected的告警折腾醒过日志里反复出现连接断开、重连失败、会话超时服务状态看起来又好像没挂。这个问题在 Zookeeper 3.7.0 上会出现在 3.4、3.5、3.6 上同样会出现区别只是报错上下文略有差异。很多开发第一反应是“ZooKeeper 集群挂了”赶紧往服务器上冲实际上大多数时候服务端活得好好的问题出在客户端、网络或者配置参数上。先理清一个概念not connected是客户端会话状态不是服务端状态。ZooKeeper 客户端进程内部维护着一个状态机刚启动时处于CONNECTING这时还没跟任意一台服务器建立有效连接对外表现就是“连不上”连接成功后会变成CONNECTED如果中途网络抖动、服务端选主、客户端 GC 停顿状态又会退回CONNECTING。如果超过会话超时时间仍没有恢复服务端会判定会话过期客户端收到SessionExpiredException。所以“not connected”和“session expired”是两个层面的问题前者是连接通道问题后者是会话生命周期问题。1.1 会话超时机制和 tickTime 的关系ZooKeeper 以tickTime作为基础时间单位默认是 3000ms也就是 3 秒。服务端对客户端设置的sessionTimeout并不是原样接受而是会做一次边界修正会话超时最小不能低于2 * tickTime最大不能高于20 * tickTime。如果没显式配置maxSessionTimeout服务端上限就是 20 倍 tickTime。比如默认 tickTime3000客户端想设置 60000ms 超时服务端实际只会给你 60000ms 吗不会它会把你的会话超时截断到 60000 与上限中的最小值最终生效值远小于你的预期。这就是为什么有些人明明在构造ZooKeeper对象时把超时写得很大却依然频繁出现 session expired 的根本原因。1.2 低版本为什么也适用同一套排查思路从 3.4 到 3.7ZooKeeper 的客户端连接状态机、会话机制、四字命令、zoo.cfg核心参数几乎没变过。3.7.0 在安全、可观测性、IPv6 支持方面做了增强但底层两阶段提交、leader 选举、会话管理这些模型依然稳定。所以只要你能理解“服务端状态 → 客户端配置 → 网络连通性”这条排查链不管面前跑的是哪个版本都能快速收敛问题。下面所有方案我都按 3.7.0 场景写同时会在关键点上标注低版本差异。2. 服务端排查先用三分钟确定它是否真的活着看到 not connected第一步永远是确认服务端是否可用而不是急着调客户端参数。服务端如果挂了客户端再怎么折腾都没用。2.1 四字命令和状态查询ZooKeeper 一直保留着四字命令最常用的是srvr、stat、mntr。在服务器本机执行echo srvr | nc 127.0.0.1 2181正常输出会包含Zookeeper version: 3.7.0、Mode: standalone或Mode: leader/follower以及延迟、连接数、节点数等信息。如果输出空先看端口是不是被换过再看进程是否还活着。也可以直接执行安装目录下的脚本bin/zkServer.sh status集群模式下这条命令会告诉你当前节点是 leader 还是 follower还是处于unknown。出现unknown说明节点没有成功加入集群多数派客户端自然连不上。2.2 进程、端口和启动日志优先检查进程和监听端口jps | grep QuorumPeerMain ss -lntp | grep 2181如果进程在但端口没监听多半是启动过程中报错退出了典型原因有三个。一是磁盘写满ZooKeeper 依赖事务日志落盘磁盘满会导致写入异常二是dataDir权限不对进程没有写权限三是 3.5 以后默认开启 AdminServer它占用 8080 端口如果你的业务程序也占用了这个端口ZooKeeper 会启动失败。检查启动日志tail -200 logs/zookeeper-root-server-*.out看到Exception while listening或者Address already in use就能定位。2.3 集群模式下最容易翻车的几个点服务端是多机部署时还要检查myid文件和zoo.cfg里的server.x配置是否一一对应。myid写的是数字必须和server.1zk1:2888:3888中的编号一致。如果有两个节点写了同样的 myid选主就会出现脑裂如果只有两个节点其中一个宕机剩余节点无法形成多数派整个集群对外表现为不可用客户端疯狂报 not connected。另外注意initLimit和syncLimit。initLimit是 follower 从 leader 同步初始数据的时间上限单位是 tickCount默认 10。如果这个值太小节点启动时数据量很大同步无法在限定时间内完成节点会被判定连接超时从而一直处于 looking 状态。这类问题在客户端日志里表现为“找不到可用服务器”而不是传统的连接拒绝。提示四字命令在新版本里默认有白名单如果执行echo ruok | nc没反应不代表服务端挂了。优先用srvr和stat或者通过zkServer.sh status确认。3. 客户端配置经常被忽视的三个关键点服务端确认无恙后就要回头审视客户端了。根据我自己排障经验not connected 在客户端侧的根因集中在连接字符串、会话超时和错误处理方式。3.1 connectString 的隐藏规则构造 ZooKeeper 客户端时连接字符串格式是host1:port1,host2:port2,host3:port3/chroot。这个字符串有三个容易出错的地方。第一多个地址用英文逗号分隔不是分号不能有空格。zk1:2181, zk2:2181这种写法里多了一个空格解析时会尝试连接一个带空格的地址导致连接失败。第二如果末尾带了/chroot那么这个 znode 必须已经存在客户端会自动把整个会话限制在这个命名空间下。很多人创建连接时顺手写了一层 chroot但忘记了先在服务端create /chroot创建它结果客户端一直报ConnectionLoss或SessionExpired。第三客户端机器必须能解析配置里的主机名。如果你的connectString写的是zk1:2181但应用服务器上/etc/hosts没有zk1的映射那么 TCP 连接永远无法建立。常规做法是写 IP或者保证所有节点主机名都能正常解析。3.2 sessionTimeout 到底设多少才算合理很多业务系统为了不丢数据喜欢把 sessionTimeout 调大比如 60 秒但往往忽略服务端的边界修正。建议先确认服务端tickTime再结合应用场景设置。如果 tickTime3000客户端设置在 15 秒到 30 秒之间是比较稳妥的区间。太小的话一次正常的 leader 选举切换可能持续几秒就会导致会话过期太大又会让服务端迟迟不清理故障客户端的临时节点影响锁释放速度。还有一个容易忽略的坑应用进程发生长 GC。当客户端 JVM 出现 Full GC暂停时间可能长达十几秒即使网络正常ZooKeeper 客户端也来不及发送心跳服务端会判定会话超时。这种场景下单纯调大 sessionTimeout 能缓和但不能根治必须从应用 JVM 参数和 GC 优化入手。3.3 客户端不能光创建连接就万事大吉原生new ZooKeeper(connectString, sessionTimeout, watcher)是异步连接构造方法返回后连接可能还没建立。如果马上调用getData()通常会抛ConnectionLossException。正确做法是注册一个Watcher在Event.EventType.None且getState()为SyncConnected后再执行业务操作。更工程化的做法是使用 Curator 框架它内部已经把重试、状态监听、路径校验这些脏活封装好了。下面是用 Curator 创建客户端的标准姿势RetryPolicy retryPolicy new ExponentialBackoffRetry(1000, 5); CuratorFramework client CuratorFrameworkFactory.builder() .connectString(zk1:2181,zk2:2181,zk3:2181) .sessionTimeoutMs(30000) .connectionTimeoutMs(10000) .retryPolicy(retryPolicy) .build(); client.start(); client.getConnectionStateListenable().addListener((curator, state) - { if (state ConnectionState.CONNECTED) { // 连接建立可以执行业务操作 } else if (state ConnectionState.LOST) { // 会话可能丢失需要重新构建上层状态 } });Curator 自带的ExponentialBackoffRetry会在连接失败后按指数退避重试避免造成“重试风暴”。低版本 ZooKeeper 同样适用因为 Curator 依赖的是客户端协议而协议接口是稳定的。4. 网络与运行环境那些概率小但一踩一个准的坑服务端活着客户端代码也没问题可还是 not connected问题往往就躲在网络和主机环境里。4.1 防火墙、安全组和端口可达性2181 端口看起来是开放的但很多云环境里不仅有安全组限制还有主机内部 iptables、SELinux 策略。客户端连不上第一时间用telnet或nc验证nc -vz zk1 2181如果不通从两端同时排查。先看服务端监听0.0.0.0:2181还是只监听了本机回环地址再看安全组是否放行了客户端 IP。我曾遇到过服务端没任何异常但是客户端所在的容器网络没有放行出方向到 ZK 端口的策略导致所有连接超时。这种问题用tcpdump抓包最容易确认服务端能收到 SYN但回包被丢弃。4.2 主机名解析和时钟漂移ZooKeeper 集群节点之间通信依赖zoo.cfg中配置的主机名或 IP。如果每个节点配置的是主机名就要确保所有节点包括客户端应用服务器都能解析这些主机名。不然客户端找到服务器列表后发起连接时解析失败状态一直停在 CONNECTING。时钟漂移也是隐藏杀手。ZooKeeper 会话超时依赖服务器系统时间集群节点之间时钟偏差过大leader 判断 follower 心跳就会出错。客户端机器时钟如果被业务手动改动也可能造成服务端时间戳跳变导致连接状态异常。生产环境必须统一用时间同步服务校准。4.3 GC 长停顿和线程资源竞争之前提到 GC 停顿会导致心跳中断但很多团队直到定位线上问题才会把它纳入排查范围。看客户端 JVM 日志如果一次 Full GC 超过 sessionTimeout 的三分之一就必须重视。可以启用 GC 日志-Xlog:gc*:file/data/logs/gc.log:time,uptime,level,tags另外还要关注客户端所在容器是否限制了 CPU 和内存。如果 cgroup 限额过小CPU 争抢严重ZooKeeper 客户端的发送线程可能迟迟得不到调度表现就是连接被服务端空闲超时断开。5. 一次完整排障复盘从乱象到根因光说排查步骤太抽象分享一个典型的排查过程。某个业务模块升级到 Zookeeper 3.7.0 后应用日志里开始频繁出现KeeperException$ConnectionLossException紧接着是ZooKeeper not connected服务间调用成功率掉到 80%。第一反应是怀疑新版本有问题甚至考虑回滚到旧版本。但我没有急着换版本而是按下面的顺序走了一遍。5.1 排障过程记录先在三台 ZooKeeper 服务器上执行echo srvr | nc 127.0.0.1 2181状态分别是 leader 和 follower集群健康。再执行bash bin/zkServer.sh status确认 leader 已正常选出。随后在应用服务器上执行nc -vz zk1 2181网络层通端口能访问。接着用 ZooKeeper 自带命令行工具测试bin/zkCli.sh -server zk1:2181命令行可以正常连接也能执行ls /。这说明服务端口、鉴权配置都没问题。问题范围缩小到应用代码或 JVM 环境。查看应用的 GC 日志发现每次报错前后都伴随一次老年代 Full GC暂停时间最长达到 18 秒而应用配置的 sessionTimeout 只有 10 秒。真相大白不是 3.7.0 的问题是会话超时跑不过长 GC。把 sessionTimeout 调整到 30 秒并且优化了堆内存分配减少了 Full GC 频率问题消失。这个案例特别典型因为它几乎集合了所有干扰项新版本、多个节点、中间网络代理。但如果一开始就被版本表象带走很容易浪费时间。5.2 常见问题速查表这里整理一份高频原因对照表方便你排障时直接查阅。现象可能原因快速验证处理方式所有客户端都连不上集群多数节点宕机或选主失败zkServer.sh status检查 myid、server.x、存活节点数部分客户端连不上安全组/防火墙/IP 白名单nc -vz 目标端口放通对应来源 IP 端口启动一段时间后断连客户端 GC 停顿过长查看 Full GC 频率调大 sessionTimeout优化 JVM偶发 ConnectionLoss网络抖动/leader 切换观察集群选主日志客户端增加重试策略设置了超时不生效超过服务端上限被截断查看 tickTime 配置按 2~20 倍 tickTime 重新设置带 chroot 连不上chroot 路径不存在检查 / 下 znode先创建对应路径6. 从根上减少 not connected配置与高可用优化每次都靠救火式排障太被动不如把基础配置和客户端侧机制一次到位。6.1 zoo.cfg 参数参考以下是一份经过实战验证的配置模板3.7.0 和低版本都可以直接参考tickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper dataLogDir/data/zookeeper/log clientPort2181 maxClientCnxns1000 autopurge.snapRetainCount3 autopurge.purgeInterval1 admin.enableServertruetickTime2000意味着客户端会话超时下限 4 秒上限 40 秒应用侧 sessionTimeout 建议设置在 10 到 30 秒之间。autopurge.purgeInterval1让服务端每小时自动清理旧快照和事务日志避免磁盘被历史数据撑爆。maxClientCnxns根据业务连接数调整默认 60 在某些高并发场景下明显不够用但只要服务端连接数达到上限新客户端就会直接被拒绝表现为 not connected。6.2 客户端用 Curator 管理连接状态原生客户端适合学习但生产环境我更推荐 Curator。它能维护连接状态监听自动重连并提供分布式锁、Leader 选举等高级组件。更重要的是Curator 对会话恢复有明确的语义CONNECTED表示会话可用SUSPENDED表示连接暂时断开但会话未过期LOST表示必须重新创建整个会话。这些状态能帮业务在上层实现优雅降级而不是一脸懵地看到 not connected。6.3 监控要盯准四个指标除了被动处理告警还要主动监控服务端健康指标。四字命令mntr会输出大量监控项重点盯这四个zk_avg_latency平均响应延迟持续上涨说明队列积压或磁盘慢。zk_outstanding_requests堆积请求数长时间居高不下说明处理不过来。zk_num_alive_connections活跃连接数突然暴跌可能代表客户端集体重连。zk_synced_followers同步中的 follower 数量低于正常值说明集群存在同步风险。把这些指标接入现有监控系统配置合适阈值很多连接问题会在造成大面积影响之前暴露出来。7. 最后再分享一点个人习惯排查 not connected 时我习惯先在纸上写一条时间线客户端第一次报错是什么时候服务端有没有选主网络有没有变更最近发布过什么配置。把时间线对齐基本能排除掉 80% 的干扰项。另一个习惯是每次调参只改一个变量很多人为了快速恢复同时调大了 sessionTimeout、加了 retry 次数、改了连接字符串结果问题解决了也不知道是谁的功劳下次踩坑还要再排一遍。如果你现在正在被 Zookeeper 3.7.0 的 not connected 困扰先憋住重启集群的冲动。按服务端状态、客户端配置、网络环境这条线走一遍大概率能在五分钟内找到答案。这个思路在 3.4 到 3.7 的各个版本里同样有效。
阅读完成 · 觉得有帮助?