ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ZooKeeper zoo.cfg 配置文件详解:参数、集群配置与生产实践

ZooKeeper zoo.cfg 配置文件详解:参数、集群配置与生产实践 1. 为什么每个大数据工程师都该把 zoo.cfg 吃透先聊个很现实的问题你在搜索引擎里敲下“Zookeeper配置文件”这几个字说明至少已经踩到了“分布式环境搭不起来”或者“集群莫名其妙挂了”的边缘。这很正常我当年第一次搭 Hadoop 高可用集群的时候在 zoo.cfg 上耗掉的时间比配置 HDFS 和 YARN 加起来还多。Zookeeper 在整个大数据生态里的位置有点像食堂里的打菜阿姨——HDFS 的 NameNode 要选主、YARN 的 ResourceManager 要选主、HBase 的 HMaster 要选主Kafka 的 broker 要注册这些“谁说了算”的问题全归它管。而 zoo.cfg 就是给这个“指挥中枢”写规矩的文件。文件不大十来行配置但每一行都对应着集群的可用性、性能和数据安全。配错了轻则节点启动报错重则脑裂、数据不一致、客户端连不上排查起来能让人掉一层头发。这篇文章我先带你把 zoo.cfg 里每个参数从“是什么”讲到“为什么”再给出一套可以直接抄作业的配置模板最后把我这几年在生产环境踩过的坑、排查过的诡异问题一并说清楚。适合正在搭 Hadoop/HBase/Kafka 集群的朋友也适合那些集群已经跑起来但想再抠一抠细节的工程师。2. 整体设计思路配置文件是如何决定集群命运的2.1 配置文件的定位与服务端、客户端的关系Zookeeper 的配置体系分三层服务端有自己的 zoo.cfgJVM 相关的参数写在 zkEnv.sh 或 java.env 里客户端连接时还要带一串连接字符串。很多新手只盯着 zoo.cfg却忽略了另外两层导致服务端明明起来了客户端却报 “ConnectionLoss” 或者“Session expired”来回折腾找不到原因。zoo.cfg 管的是服务端的行为数据放在哪、日志放哪、端口开多少、集群成员是谁、会话超时边界是多少、选主策略要不要开。它本质上是一份“服务端进程的启动说明书”。Zookeeper 启动时用 Java 的 Properties 格式解析这个文件键值对之间用等号或空格分隔注意 Java Properties 的解析规则是不允许中文注释乱码文件编码最好保持 UTF-8 无 BOM否则可能出现各种诡异字符问题。客户端那侧则完全不读 zoo.cfg它只认连接字符串比如192.168.1.10:2181,192.168.1.11:2181,192.168.1.12:2181。连接串里其实还隐藏着一个重要参数——sessionTimeout如果不显式指定Zookeeper 会按服务端的minSessionTimeout和maxSessionTimeout来兜底。这就引申出一个很常见的问题服务端把会话超时上限调得很小客户端却想申请很长的会话时间最终只能得到一个被服务端裁剪过的值表现就是客户端莫名其妙地掉线、重连。2.2 为什么大数据组件都要认这个配置文件大数据组件选 Zookeeper 当“协调者”不是拍脑袋决定的。HDFS 的 NameNode 主备切换需要两件事一是让备用节点知道“我现在能不能转正”二是让客户端知道“现在该连谁”。这两件事都依赖 Zookeeper 里的临时节点和 Watcher 机制。NameNode 启动时会往 Zookeeper 写一个临时节点Active 节点保持着这个节点Standby 节点盯着它。一旦 Active 挂了会话结束临时节点消失Standby 通过 Watcher 收到通知立刻抢占创建新节点完成切换。这个过程对 Zookeeper 的会话超时参数极其敏感。会话超时设置太长故障发生后要等很久才能触发切换业务影响窗口变大设置太短网络抖动就会导致频繁的“假死”引发不必要的主备切换甚至双 Active 同时写数据。这个平衡点不是玄学而是直接在 zoo.cfg 的minSessionTimeout和maxSessionTimeout里限定的。理解了这一层你就知道为什么这篇讲配置的文章本质上是在讲分布式系统的“心跳”和“决断”。2.3 一套配置背后要权衡的三类问题配置 Zookeeper 不是填满参数就行本质上是在权衡三个互相牵扯的问题。第一个是一致性。Zookeeper 的 ZAB 协议要求写操作必须被超过半数的节点确认才能返回成功。所以要保证数据不丢、不乱序节点数量必须是奇数且能够容忍的宕机数量是有上限的。三节点集群能容忍挂一个五节点能容忍挂两个。很多人贪图省事搭两节点结果一个挂了另一个也拒绝服务因为凑不够多数派。第二个是性能。Zookeeper 的处理能力受限于单个节点的磁盘 I/O 和网络延迟因为它要先把事务日志刷盘再响应客户端。日志目录放机械盘和放 SSD 的差距我能用数据告诉你机械盘同步写事务日志普遍要到 10ms 以上SSD 能压到 1ms 以内吞吐差距可以是十倍甚至更多。所以dataDir和dataLogDir的分开是性能设计的第一步。第三个是可用性。集群的故障恢复速度、网络分区时的行为都写在tickTime、initLimit、syncLimit这些参数里。它们决定了一个节点要等多久才能判定伙伴失联、要多久才能重新同步数据加入集群。调大了恢复慢调小了误判多这里面的度就是工程经验的体现。这三类问题没有绝对的最优解只能根据你的业务场景去调。下面的章节我按照“从宏观到微观、从必配到选修”的顺序把每个参数掰开揉碎讲清楚。3. 核心参数拆解每一个配置项背后的逻辑3.1 必配项tickTime、dataDir、clientPort先看最基础的三件套tickTime、dataDir、clientPort。官方注释是这样说的# The number of milliseconds of each tick tickTime2000 # The number of ticks that the initial synchronization phase can take initLimit10 # The number of ticks that can pass between heartbeat messages syncLimit5 # the directory where the snapshot is stored. dataDir/data/zookeeper # the port at which the clients will connect clientPort2181tickTime是 Zookeeper 的最小时间单元单位是毫秒。它本身不是“心跳间隔”而是所有超时计算的基础刻度。集群成员之间默认的心跳间隔是 1 个 tick所以心跳实际是每tickTime毫秒一次。tickTime2000意味着每 2 秒一次心跳。如果机器负载很高网络又不太稳定2 秒可能太紧有些团队会调到 3000 甚至 4000但随之而来的是故障检测变慢。我个人的习惯是单机房低延迟环境保持 2000跨机房或高负载场景上调到 3000。dataDir是必填项它存的是 Zookeeper 的内存数据库快照。这里有个新手最容易犯的错把dataDir指向一个普通磁盘分区比如跟操作系统共用一块盘。ZAB 协议在做事务日志同步时会先写dataLogDir如果没有单独配置dataLogDir那事务日志也会写到dataDir里。快照文件加上事务日志再叠加系统其他进程的 I/O磁盘很快会成为瓶颈。生产环境我见过很多次dataDir所在磁盘 I/O 使用率彪到 90% 以上Zookeeper 请求响应延迟从几毫秒飙升到几十毫秒整个 Hadoop 集群跟着抖。dataDir还有一个隐藏作用启动时恢复数据。节点启动时先读快照文件再重放增量的事务日志两者结合恢复到最新状态。如果快照文件和日志文件被人为删掉或者目录权限不对节点启动就会失败报错还不直观常见的是 “Invalid config file” 或干脆无法连接。记住这个目录的权限必须属于启动 Zookeeper 的用户且不要放在/tmp下因为/tmp会被系统定期清理。clientPort就是客户端连接端口默认 2181这个没什么好讲的但要注意防火墙和 SELinux。我碰到过整整一下午客户端连不上最后发现是 iptables 规则里没放行 2181 端口。另外端口不要跟别的服务冲突大数据集群里 Spark 的 4040、HBase 的 16010 都是常用端口顺手检查一下没有坏处。3.2 集群模式server.X 与 myid 的配合Zookeeper 单机模式只需要上面三件套但生产环境谁用单机呢分布式协调器自己先挂掉那整个集群就是灾难。集群模式下zoo.cfg 里必须配置所有参与者participant的地址形如server.1192.168.1.10:2888:3888 server.2192.168.1.11:2888:3888 server.3192.168.1.12:2888:3888这里.1、.2、.3是节点的逻辑编号也叫 server id。它必须与各节点dataDir目录下的myid文件内容一一对应。myid文件就是一个纯文本文件里面只写一个数字比如第一台机器写1第二台写2。用 echo 命令就能生成echo 1 /data/zookeeper/myid注意myid文件放在dataDir下而不是dataLogDir下因为启动时它得跟着快照目录走。还有就是myid文件不能有换行符以外的多余字符否则启动时会解析失败。有个土办法排查用cat -A /data/zookeeper/myid如果末尾只有$换行符就没问题如果多了^MCRLF 回车符就要用sed -i s/\r$//处理一下。server.X行里的两个端口分别有特殊使命。第一个端口2888是集群内 follower 与 leader 通信用的数据同步、心跳都走这个端口第二个端口3888是投票选举用的选主阶段所有节点互相投票走这个端口。这两个端口必须互相独立不能和clientPort重叠更不能和别的服务端口冲突。从官网文档还能看到一个可选配置server.1192.168.1.10:2888:3888:observero 后缀表示该节点是观察者不参与投票。多机房容灾场景下观察者模式很实用既能提供读服务又不拖累写性能。集群模式还有一个重要细节节点数量。你不是想看大数据入门教程吗那我就直说最少三节点而且要奇数。数学原理很朴素ZAB 要求多数派才能选主、才能提交写操作。三节点挂一个还能用挂两个就整个不可用五节点挂两个还能用挂三个就不可用。生产环境最常见的规模是五节点既能扛住两节点同时故障又不会因为节点太多导致写延迟明显上升。3.3 超时控制三兄弟initLimit、syncLimit、min/maxSessionTimeout这三个参数直接决定了集群在故障场景下的“反应速度”和“容忍度”。initLimit的含义是follower 节点启动后向 leader 同步完所有数据所能消耗的最大 tick 数。默认是 10结合tickTime2000也就是 20 秒。如果集群数据量太大或者 follower 与 leader 之间网络带宽不够同步没在 20 秒内完成follower 就会被判定为“初始化失败”然后自杀退出。这时候日志里会出现 “Timeout while synchronizing with leader” 之类的字样。解决思路很简单要么增大initLimit要么把dataDir和dataLogDir拆开并换 SSD。syncLimit是 leader 与 follower 之间心跳响应的超时 tick 数。默认 5即 10 秒。如果 follower 在 10 秒内没有给 leader 任何心跳回应leader 就认为它挂了将其移出集群。这个参数对网络抖动很敏感。我之前有个机房一到大促就网络延迟飘高Zookeeper 集群隔三差五报“connection broken”后来把这值从 5 调到 10情况立刻改善。但也不能盲目调大因为syncLimit变大意味着故障检测变慢主备切换的响应时间会拉长。如果是单机房千兆内网、走独立交换机保持默认完全够用。minSessionTimeout和maxSessionTimeout是一对控制客户端会话时长的边界。官方默认是minSessionTimeout2 * tickTimemaxSessionTimeout20 * tickTime也就是 4 秒到 40 秒。客户端创建会话时可以指定 timeout但最终生效值会被服务端裁剪到这个区间内。如果你的客户端是 HBase 或者自己写的 Java 客户端想设个 60 秒的超时结果服务端根本不认只给你 40 秒。所以调这对参数时要先想清楚客户端的长会话是为了容忍 GC 停顿和网络抖动但服务端也不希望一个死掉的客户端占据资源太久。根据我的实践HBase 所在的集群把maxSessionTimeout调到 6000060 秒比较稳妥因为 HBase 的 RPC 超时本身比较大太短的会话超时会让 RegionServer 误判 Master 失联。3.4 高性能进阶dataLogDir、autopurge、maxClientCnxnsdataLogDir是单独存放事务日志的目录。Zookeeper 每次写操作都要先写事务日志并 fsync 到磁盘才算提交成功。这个 fsync 的耗时直接决定写吞吐。把事务日志放到独立的 SSD 磁盘上可以有效降低写延迟。注意这里有个细节dataLogDir不是必填项不填的话日志就写到dataDir。但生产环境强烈建议配置至少要把日志和快照分开到不同磁盘防止 I/O 互相干扰。我记得有一年的 Gaokao 热搜词里居然混进了“Zookeeper 配置文件”这种词说明这个点确实是大家普遍关注的难点我也见过有人把dataLogDir和dataDir配到同一个目录的理由是“反正公司就一块盘”。这种场景下你至少要把两个目录设成同一块盘上的不同文件系统不然万一某个目录满了整个节点会直接进入只读保护模式拒绝服务。autopurge.snapRetainCount和autopurge.purgeInterval是一对自动清理策略。Zookeeper 默认保留最近 3 个快照和对应的事务日志如果purgeInterval设置为 0就表示不自动清理。这个参数容易被忽略但一旦忽略坑很深。我在一个跑了一年的集群上见过dataDir被快照文件撑爆的情况因为每天的快照文件有几十个每个 1GB 以上磁盘直接满了。后来加了自动清理才解决autopurge.snapRetainCount5 autopurge.purgeInterval1snapRetainCount5表示保留最近 5 个快照purgeInterval1表示每 1 小时执行一次清理任务。这里不建议把snapRetainCount设成 1因为如果最新快照损坏你至少希望还有一个一次性备份。设成 3 到 5 比较合适。maxClientCnxns是单 IP 允许的最大并发连接数默认是 60。这个参数在大数据场景下尤其重要。举个例子Kafka 客户端、HBase 客户端、Spark 任务都涌向同一个 Zookeeper 节点时单个客户端 IP 的连接数很容易超过 60然后就会被无情拒绝。遇到 “Too many connections from /192.168.1.x” 这种报错十有八九是这个参数在作怪。我一般建议根据实际连接数调大比如 500或者干脆设成 0 表示不限制。不过生产环境不建议完全不限制因为每个连接都是有内存成本的连接数过多会把节点内存耗尽。3.5 选举与监听端口2888、3888、observer 的进阶用法除了server.X里带的端口Zookeeper 3.5 之后还支持一些动态配置和 SSL 配置不过大多数场景用不上我简单提一下。首先2888和3888必须在所有节点之间互相开放。有些团队安全意识强把服务器放在安全组里只开了 2181结果集群一直建不起来日志里全是 “Cannot open channel to X at election address”。不用怀疑就是安全组把投票端口挡了。检查方式很简单在两台机器上分别执行telnet 192.168.1.10 3888如果通了会进入一个黑屏输入模式如果不通会提示连接失败。其次如果你有多个机房想让某个机房的节点只提供读服务不参与选举、不影响写性能可以在server.X行后面加:observer后缀还要在 zoo.cfg 里显式标记该节点peerTypeobserver注意 observer 节点不会参与投票所以即使它挂了集群也不受影响。这个设计非常适合跨机房容灾但有个不能忽视的副作用observer 节点的数据同步依赖 leader 推送如果它所在机房的网络延迟很高写入吞吐会受影响。所以 observer 一般只用于“就近读”场景而不是无限加。4. 实操过程从零到一的配置与集群搭建实录4.1 环境准备版本选择与三台机器的规划先聊版本。Zookeeper 的版本选择看起来是个小事其实影响很大。3.4.x 是“老黄牛”稳定但缺少很多现代特性3.5.x 引入了动态配置、去除单点、Quorum 读写等能力3.6.x 和 3.7.x 继续演进但部分版本有已知的坑。我目前的推荐是 3.6.3 或 3.7.x 的最新稳定版。别选 3.4.x 的原因有二一是它没有内嵌的 Admin Server虽然这个功能后来也带来了一些新问题二是社区早已停止维护遇到 bug 只能自己啃源码。版本确认之后规划三台机器。假设有三台虚拟机或物理机IP 分别是 192.168.1.10、192.168.1.11、192.168.1.12操作系统是 CentOS 7 或 Ubuntu 20.04。Java 版本必须匹配Zookeeper 3.6.x 要求 Java 8 或 11建议装 OpenJDK 11。机器规格上如果只是学习或测试2 核 4G 内存完全够用生产环境建议 4 核 8G 以上dataDir放在独立的 SSD 分区。有人会问Zookeeper 节点需要多大内存这个真不好一概而论因为它要缓存部分数据树Data Tree数据量通常不大几 GB 以内但连接数和会话数多了之后每个连接都有对应的 Socket 缓冲区内存消耗会明显上升。4.2 下载、解压与环境变量配置我比较喜欢直接去 Apache 官网下载二进制包省去编译的麻烦。拿 3.7.1 举例wget https://dlcdn.apache.org/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz tar -zxvf apache-zookeeper-3.7.1-bin.tar.gz -C /opt/ mv /opt/apache-zookeeper-3.7.1-bin /opt/zookeeper解压后目录结构是这样的bin/是启动脚本conf/是配置目录lib/是依赖 jar 包。你会注意到conf/下面自带一个zoo_sample.cfg这是官方示例第一次配置时可以直接复制改名cp /opt/zookeeper/conf/zoo_sample.cfg /opt/zookeeper/conf/zoo.cfg然后编辑/etc/profile加上环境变量export ZOOKEEPER_HOME/opt/zookeeper export PATH$ZOOKEEPER_HOME/bin:$PATH export ZOOKEEPER_LOG_DIR/var/log/zookeeper这里强调一下ZOOKEEPER_LOG_DIR默认情况下 Zookeeper 的日志不是事务日志是运行日志会输出到当前目录的zookeeper.out或者logs/下。如果你不设置这个环境变量启动时容易找不到日志排查问题无从下手。设置成独立目录至少ls /var/log/zookeeper/能看到zookeeper.log之类。顺带一提网上很多教程喜欢用 systemd 管理 Zookeeper我建议生产环境也这么做但先别急着上 systemd。初次搭建时直接前台启动或后台启动更方便观察输出。等确认无误再写 systemd unit 也来得及。4.3 三台机器的 zoo.cfg 完整配置与 myid 写入三台机器的 zoo.cfg 基本一致只有server.X后面的 IP 需要按实际填写。下面这份配置是我自己的生产模板可以直接复制使用# 基础时间单元单位毫秒 tickTime2000 # 初始化同步超时单位 tick initLimit10 # 心跳同步超时单位 tick syncLimit5 # 数据快照目录 dataDir/data/zookeeper # 事务日志目录建议放独立 SSD dataLogDir/data/zookeeper/logs # 客户端端口 clientPort2181 # 集群成员 server.1192.168.1.10:2888:3888 server.2192.168.1.11:2888:3888 server.3192.168.1.12:2888:3888 # 自动清理 autopurge.snapRetainCount5 autopurge.purgeInterval1 # 会话超时边界 3.x 默认 4000~40000这里放宽方便大数据客户端 minSessionTimeout4000 maxSessionTimeout60000 # 单 IP 最大连接数 maxClientCnxns500在每台机器执行mkdir -p /data/zookeeper/logs echo 1 /data/zookeeper/myid # 第一台机器 echo 2 /data/zookeeper/myid # 第二台机器 echo 3 /data/zookeeper/myid # 第三台机器注意dataLogDir和dataDir虽然都在/data/zookeeper下但一个放在子目录里要确保目录存在。Zookeeper 不会主动创建这两个目录如果目录不存在启动会直接报错。血的教训我第一次部署时忘了mkdir -p一个节点起不来还以为是配置文件格式问题。myid文件写入之后就不要再改了除非你想把节点从集群中移除。改了myid但没改 IP 对应关系会出现节点身份错乱选主时逻辑混乱。另外还有一个细节myid文件的所有者要跟 Zookeeper 进程用户一致。之前遇到一个诡异问题进程明明在跑但状态一直是down后来发现是/data/zookeeper目录属主是 rootZookeeper 进程用 zookeeper 用户启动压根没权限写快照。4.4 启动顺序、状态校验与常见报错初判启动顺序上官方建议逐台启动先启动第一台再启动第二台最后启动第三台。其实顺序无所谓先后但逐台启动更容易观察日志不至于三台同时报错时手忙脚乱。启动命令/opt/zookeeper/bin/zkServer.sh start第一次启动时可以优先前台启动用下面的命令把日志全打到屏幕上方便看错误/opt/zookeeper/bin/zkServer.sh start-foreground启动成功后会看到类似于 “binding to port 0.0.0.0/0.0.0.0:2181” 的字样。然后检查进程状态/opt/zookeeper/bin/zkServer.sh status正常输出是ZooKeeper JMX enabled by default Using config: /opt/zookeeper/bin/../conf/zoo.cfg Client port found: 2181. Client address: localhost. Client SSL: false. Mode: leaderMode 有三态leader、follower、standalone。三台节点的 Mode 一定是一个 leader其余全是 follower但刚启动时可能看到大家都是 “standalone” 或者 “down”这时候别慌。第一次启动时节点之间需要发现彼此选举需要一点点时间等几秒再敲一次status就会收敛。如果一直停在 “standalone”说明节点之间没相互连通回去查防火墙和端口。还有一个非常经典的坑三台机器的 zoo.cfg 内容不完全一致比如 A 机上写的是server.3192.168.1.12:2888:3888B 机上写漏了这一行那么 B 机永远无法识别 C 机集群永远建不起来。配置文件必须在所有节点上保持除 myid 外完全一致尤其是server.X列表这一点真的是“看起来简单做起来容易翻车”。4.5 客户端连接测试与常用四字命令配置文件正确解析之后用客户端验证一下/opt/zookeeper/bin/zkCli.sh -server 192.168.1.10:2181进入客户端的交互模式后先随便创建个节点看看create /test_config_test hello get /test_config_test返回hello说明读写正常。删除测试节点delete /test_config_test然后退出。这只是最基础的功能验证真正要看集群健康状况得用四字命令。Zookeeper 内置了十几个四字命令比如ruok、stat、mntr。用nc或telnet发送即可echo mntr | nc 192.168.1.10 2181返回的内容里会有一大堆指标重点看这几个zk_server_state leader zk_num_alive_connections 100 zk_outstanding_requests 0 zk_pending_syncs 0zk_outstanding_requests表示当前排队未处理的请求数。正常情况下应该小于 10如果这个值长期很大说明节点处理不过来要么磁盘 I/O 瓶颈要么连接数过多。zk_pending_syncs表示等待同步的请求数如果持续不为 0说明 follower 跟不上 leader可能需要关注网络或者数据量。四字命令默认是开启的但如果你有安全需求也可以在配置里用4lw.commands.whitelist*或指定命令列表来控制。比如只允许stat和mntr就写4lw.commands.whiteliststat,mntr个人建议保留mntr它是监控系统采集数据的入口比stat信息更全面。5. 与大数据生态的整合实战Hadoop、HBase、Kafka5.1 Hadoop 高可用集群中 Zookeeper 的角色配置ZooKeeper 在 Hadoop 里承担的职责就是辅助 NameNode 做主备切换。HDFS 高可用HA架构里有两个 NameNode一个是 Active一个是 Standby。Active 节点负责对外提供服务Standby 节点持续同步命名空间状态。两者都要向 Zookeeper 注册并利用 Zookeeper 的临时节点和 Watcher 机制来感知彼此状态。Hadoop 的配置文件hdfs-site.xml里核心是dfs.ha.automatic-failover.enabled设为 true然后指定 Zookeeper 地址property nameha.zookeeper.quorum/name valuezk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181/value /property注意这里的地址不要带路径只是 host:port。如果你给 Zookeeper 配了 chroot在连接串后加/hadoop-ha这种那么ha.zookeeper.quorum的值也要跟着改成zk1:2181,zk2:2181,zk3:2181/hadoop-ha。chroot 的作用是给不同框架划分空间避免 HDFS、HBase、Kafka 互相干扰。我习惯给每个框架配一个独立根路径比如 HDFS 用/hdfsHBase 用/hbaseKafka 用/kafka这样一来同一个 Zookeeper 集群可以被多个框架复用。Hadoop 的自动故障转移依赖一个独立的守护进程——ZKFailoverControllerZKFC它随 NameNode 一起启动。ZKFC 做的事就是往 Zookeeper 里写临时节点/hdfs/namenode下的 lock 节点并监控健康状态。一旦 Active 的 NameNode 不再更新会话ZKFC 就会尝试删除或接管节点最终触发切换。这里有一个和 Zookeeper 配置直接相关的点Zookeeper 的maxSessionTimeout如果太小ZKFC 持有的会话很容易被服务端裁剪导致频繁误切换。我建议在 Hadoop 集群里把maxSessionTimeout至少放宽到 60 秒。5.2 HBase 的 RegionServer 与 Master 选举配置HBase 对 Zookeeper 的依赖比 Hadoop 更深。HBase 的 Master 选主、RegionServer 的注册和发现、元数据表hbase:meta的位置都放在 Zookeeper 上。HBase 启动时RegionServer 会往 Zookeeper 的/hbase/rs目录下注册一个临时节点Master 则监听这些节点。一旦某个 RegionServer 宕机节点消失Master 会检测到并对其负责的 Region 进行重新分配。HBase 的hbase-site.xml里需要指定 Zookeeper 地址核心属性有三个property namehbase.zookeeper.quorum/name valuezk1.example.com,zk2.example.com,zk3.example.com/value /property property namehbase.zookeeper.property.clientPort/name value2181/value /property property namezookeeper.znode.parent/name value/hbase/value /property注意hbase.zookeeper.quorum里只填主机名不要带端口端口单独用hbase.zookeeper.property.clientPort指定。这是 HBase 的一个历史包袱很多人在这踩坑。而且 HBase 默认的zookeeper.znode.parent是/hbase建议显式指定避免跟别的框架占用同一路径。HBase 对 Zookeeper 会话超时同样敏感。HBase 客户端默认的会话超时是 60 秒如果 Zookeeper 服务端maxSessionTimeout小于 60000客户端实际得到的超时会被裁剪。裁剪的后果是 RegionServer 在 GC 停顿或者网络抖动时容易被误判为宕机触发不必要的 Region 转移。我建议 HBase 集群的 Zookeeper 配置maxSessionTimeout60000并且 HBase 的hbase.session.timeout不要超过这个值。5.3 Kafka 的 Broker 注册与 Controller 选举配置Kafka 是另一个重度依赖 Zookeeper 的生态组件。Kafka 用 Zookeeper 保存 Broker 元数据、Topic 分区信息、消费者组状态等等。Controller 的选举也依赖 Zookeeper 的临时节点。虽然新版本 Kafka 在推进 KRaft 模式试图摆脱 Zookeeper但绝大多数生产环境仍然在用 Zookeeper 模式。Kafka 的config/server.properties里需要配置 Zookeeper 连接串zookeeper.connectzk1.example.com:2181,zk2.example.com:2181,zk3.example.com:2181/kafka这里同样建议加 chroot/kafka因为 Kafka 会在 Zookeeper 上创建大量节点如果不加隔离容易跟 HBase、HDFS 的 znode 混在一起误删风险很高。Kafka 对 Zookeeper 会话超时的处理更有特点Kafka 的session.timeout.ms控制的是客户端与 broker 之间的会话与 Zookeeper 会话不同而 broker 与 Zookeeper 之间的会话超时由 Kafka 的zookeeper.session.timeout.ms控制。这个值默认是 6000 毫秒6 秒如果你遇到 broker 与 Zookeeper 频繁断连日志里报 “Expired session”就可以调大到 10000 或 20000。但要注意调大这个值的同时Zookeeper 服务端的maxSessionTimeout必须大于该值否则 Kafka 会被裁剪超时问题依旧。5.4 配置复用的经验chroot 隔离与多框架共存如果你的大数据集群里 Hadoop、HBase、Kafka 都跑在一起却只有一套 Zookeeper 集群那么 chroot 隔离就是我强烈推荐的做法。chroot 的核心思想是将一个 Zookeeper 集群逻辑划分成多个“命名空间”每个框架只操作自己根目录下的节点。使用 chroot 时连接串变成zk1:2181,zk2:2181,zk3:2181/hbase注意/hbase这个路径必须提前存在否则客户端连接时会报 “chroot path does not exist”。所以初始化时要手动创建/opt/zookeeper/bin/zkCli.sh -server zk1:2181 create /hbase create /kafka create /hdfs 创建完之后框架的连接串就带上了各自路径。这个做法的好处是什么最直观的好处就是Kafka 的运维同学不会误删 HBase 的元数据节点HDFS 的 ZKFC 也不会跟 Kafka 的 Controller 选举抢资源。即使有层级结构冲突也不会互相踩踏。但是使用 chroot 也有两个坑。第一个坑是连接串的语义变了你不能直接在stat zk1:2181这种命令里看到根节点而是看到/hbase这个子树。监控系统和运维脚本如果按原路径去查可能查不到数据。第二个坑是如果你把同一个 Zookeeper 集群同时给 Hadoop 和 HBase 用一定要确认两个框架的连接串没有写到同一个路径否则会出现元数据混乱。我在生产环境见过一次事故就是因为 HBase 和 HDFS 的 ZKFC 都用了根路径/导致两个框架的节点互相干扰最后集群数据损坏被迫恢复快照。6. 常见问题与排查技巧实录6.1 节点启动不了配置文件导致的典型报错速查表这里我整理了一份高频报错和对应解决思路的速查表。每一个我都亲手踩过或帮人排查过不是官方文档的干巴巴翻译。现象典型日志/报错排查方向启动即退出Invalid config file检查 zoo.cfg 语法是否有非法字符、重复 key、缺少 dataDir启动后一直 standaloneCannot open channel to X at election address检查 2888/3888 端口互通防火墙、安全组、SELinuxmyid 匹配不上Unexpected exception causing shutdown伴随myid相关提示检查 myid 文件内容和 server.X 编号是否一致是否存在换行符污染数据目录不存在java.io.FileNotFoundException检查 dataDir 和 dataLogDir 是否已经 mkdir数据目录权限不足Permission denied检查目录属主是否与启动用户一致端口被占用Address already in use用ss -lntp查看 2181/2888/3888 占用情况投票超时Timeout while synchronizing with leader调大 initLimit或排查网络/磁盘瓶颈客户端连接被拒Too many connections from调大 maxClientCnxns或检查是否有循环连接风暴这张表里我重点说一下最后一条。Too many connections from这个报错非常隐蔽尤其在容器化环境里。因为容器网络会做 NAT很多客户端的出口 IP 可能都是同一个网关 IP导致 Zookeeper 认为是同一个 IP 发起了大量连接瞬间打满maxClientCnxns。解决思路有两种一是调大maxClientCnxns二是让客户端配置连接池复用。对于 HBase 客户端连接池配置在hbase-site.xml的hbase.client.ipc.pool.size对于 Kafka 客户端生产端的connections.max.idle.ms也要注意。6.2 集群脑裂与选主异常leader 消失、follower 徘徊脑裂是分布式系统里的终极噩梦。在网络分区的情况下一个集群可能会分成两个子集每个子集都试图选出自己的 leader结果客户端不知道该听谁的数据一致性彻底被破坏。Zookeeper 的 ZAB 通过“多数派”机制天然防止了这个问题只有超过半数的节点都认可某个 leader它才能合法存在。所以脑裂在这个机制下很难发生但“类似脑裂的体验”确实会有。典型场景是这样的三节点集群其中两个节点在机架 A一个节点在机架 B。机架 A 和机架 B 之间的网络断了。机架 A 的两个节点可以互相通信它们凑不够多数派2 票不够 3 的一半以上因为多数派要求超过一半即至少 2但 2 可以吗这里我解释一下三节点的多数派是 2 个节点所以机架 A 的两个节点可以组成合法集群并选主。也就是说三节点环境下网络分区后包含两个节点的那一侧依然能继续工作而剩下那个节点会超时退出。这就是为什么生产环境普遍要求奇数节点的原因它保证任何网络分区下至多只有一侧能凑齐多数派。如果真的发生了“两个节点认为自己是 leader”的情况大概率不是 Zookeeper 本身出问题而是你的使用方式出问题。比如你手动改了myid和 IP 映射导致节点身份错乱或者你在普通集群里混入了 observer 节点但配置写错让 observer 参与了投票。遇到这种问题我的排查步骤是先在每台机器执行echo stat | nc 127.0.0.1 2181对比各节点看到的 leader 信息看是否一致。然后检查网络用ping和traceroute确认节点间是否真的联通。最后检查防火墙很多“脑裂”其实是防火墙只放行了某些 IP 导致的部分连通。6.3 磁盘写满与事务日志失控的实战救援事务日志和快照的增长速度比很多人想象得快得多。默认情况下Zookeeper 不会为dataDir设置上限快照和日志会无限增长直到磁盘写满。这个问题的可怕之处在于Zookeeper 在磁盘写满后不会优雅退出而是直接拒绝写操作但读操作还能继续看起来半死不活排查起来很费劲。我处理过一个真实案例某套 Zookeeper 集群跑了一年多磁盘占用率每天涨几个 GB某天中午突然告警——写入超时。登上去一看/data/zookeeper下面堆了几百个快照文件最大的有 2GB。当时我第一反应不是删文件而是赶紧检查autopurge配置发现autopurge.purgeInterval0等于没开自动清理。于是先手动清了一批旧快照再改配置autopurge.snapRetainCount3 autopurge.purgeInterval1但这里有个操作顺序问题不要直接删除最新的快照和事务日志否则节点重启时无法恢复数据。如果需要手动清理建议用官方自带的脚本/opt/zookeeper/bin/zkCleanup.sh -n 3这个脚本会保留最近 3 个快照清理其余的快照和日志。如果连zkCleanup.sh都不幸被删了也可以手动删旧文件但务必保留最新的快照和所有比该快照更新的日志。删除时干脆把目录列的修改时间排个序只删修改时间早于最新快照日期之前的文件。另外提一个判断依据快照文件名类似snapshot.100000001数字是事务 IDzxid。后缀数字越大越新。日志文件名类似log.100000001。如果某个日志文件的后缀大于最新快照的后缀说明这个日志还没被合并进快照不能删。总之优先用官方清理脚本别自己手动算。6.4 连接风暴和端口耗尽从 maxClientCnxns 到文件描述符连接数相关的坑除了maxClientCnxns之外还有一层隐藏问题操作系统的文件描述符file descriptor限制。Zookeeper 本质上是个 socket 服务器每个客户端连接都占用一个文件描述符。Linux 默认的ulimit -n通常是 1024这在单机玩具部署时够用但在大数据集群上随便一个 Hadoop 集群几百个客户端同时连接1024 瞬间就没了。查看当前限制ulimit -n修改方式有多种。如果使用 systemd 管理 Zookeeper在 unit 文件里写LimitNOFILE65535如果直接zkServer.sh启动在启动前设置ulimit -n 65535然后启动 Zookeeper。注意这个修改要在同一个 shell 里完成否则子进程不会继承。测试环境的另一个坑是你明明改了/etc/security/limits.conf但ulimit -n还是显示 1024那是因为 SSH 登录会话和 systemd 服务的配置来源不同。用 systemd 管理是最省事的方式。Zookeeper 本身的 JVM 参数也要留余量。在zkEnv.sh里默认的堆大小可能是 1G 或者更小连接数上来之后堆内存会炸。把JVMFLAGS调大export JVMFLAGS-Xms4096m -Xmx4096m注意Xms和Xmx建议设成一样避免堆动态伸缩引起不必要的 GC 停顿。Zookeeper 对 GC 停顿很敏感Official 文档曾明确建议尽量减少 full GC 的发生。6.5 生产环境配置模板一份可以直接上生产的 zoo.cfg把前面所有要点整合起来我给出一个我认为“在绝大多数生产场景下都站得住脚”的 zoo.cfg 模板。它针对的是 5 节点的中型集群同时也兼容 3 节点。# 时间单元单位毫秒 tickTime2000 # 初始化同步允许的 tick 数 initLimit15 # 心跳超时允许的 tick 数 syncLimit8 # 数据快照目录务必放在 SSD 上 dataDir/data/zookeeper # 事务日志目录务必独立于 dataDir dataLogDir/data/zookeeper/logs # 客户端端口 clientPort2181 # 集群成员5 节点示例 server.1192.168.1.10:2888:3888 server.2192.168.1.11:2888:3888 server.3192.168.1.12:2888:3888 server.4192.168.1.13:2888:3888 server.5192.168.1.14:2888:3888 # 自动清理快照保留最近 5 个每小时检查一次 autopurge.snapRetainCount5 autopurge.purgeInterval1 # 会话超时边界适配 Hadoop/HBase/Kafka minSessionTimeout4000 maxSessionTimeout60000 # 单 IP 最大连接数容器化场景按需调大 maxClientCnxns500 # 四字命令白名单防止管理命令被滥用 4lw.commands.whiteliststat,mntr,ruok,conf这套模板的关键决策点我解释一下initLimit15和syncLimit8比默认值略大因为大数据集群普遍存在跨机架流量、GC 停顿等不可控因素稍微放宽能减少“假死误判”。maxSessionTimeout60000是为了配合 HBase 和 Kafka 的长会话需求。maxClientCnxns500比默认大很多但依然不是无限防止单 IP 异常连接把资源耗尽。4lw.commands.whitelist限制四字命令防止别人随手一个dump就把整个数据树导出来。别急着把这份模板直接复制到所有环境先核对你的业务场景。如果你主要是跑 Kafka且 broker 数量多maxSessionTimeout可以不改倒是要看 Kafka 的zookeeper.session.timeout.ms是不是超过了 60000。不同框架对会话超时的需求不一样配置的取舍就体现在这里。7. 后续还可以怎么玩监控、动态配置与调优方向Zookeeper 的配置文件并不是一劳永逸的集群规模扩大、业务负载变化、网络环境调整都会催生新的配置项变化。抛开具体的参数修改后续可以向三个方向延伸。第一个方向是监控体系搭建。四字命令里的mntr是最轻量的监控数据源。用 Prometheus 的 zookeeper exporter定期执行mntr拉取指标可以覆盖节点状态、连接数、未处理请求数、延迟等核心指标。再加上 Prometheus 的告警规则比如“zk_num_alive_connections 5000就告警”、“zk_outstanding_requests 10持续 5 分钟告警”基本上能在故障发生前就发出预警。别等到节点挂了才想起来看日志监控能帮你提前发现问题。第二个方向是动态配置Dynamic Reconfiguration。从 3.5.0 开始Zookeeper 支持动态修改集群成员列表不需要重启全部节点。配置文件里加一行dynamicConfigFile/data/zookeeper/zoo.cfg.dynamic然后通过reconfig命令添加或移除节点。这个功能的实际价值很大比如你要对集群做扩容传统做法得逐台停止、修改 zoo.cfg、再启动而动态配置允许你在线添加新的 observer 或 participant过程中集群服务不中断。但我要提醒一句动态配置是有学习门槛的操作不当会直接把整个集群搞崩。刚开始练习时一定先在测试环境多演练几遍。第三个方向是JVM 与 GC 调优。Zookeeper 本身不是 CPU 密集型应用它的瓶颈通常在磁盘 I/O 和内存。生产环境建议用 G1 垃圾回收器减少 Full GC 频率。在zkEnv.sh里这样设置export JVMFLAGS-Xms4g -Xmx4g -XX:UseG1GC -XX:MaxGCPauseMillis150GC 停顿时间要控制在 150ms 以下否则集群节点容易在 GC 期间“失联”被其他节点误判。如果发现 GC 频繁第一步先检查堆大小是否合理第二步检查是否有客户端连接数暴涨导致的对象分配压力。我个人在实际操作中最大的体会是Zookeeper 的配置项看着少但每一个都和集群的“生死时速”直接挂钩。有时候改一个syncLimit就能把一个天天误报警的集群稳住改一个maxSessionTimeout就能让 HBase 不再三天两头切换 Master。配置文件的每一行都值得你反复权衡而不是照抄网上的模板。最后再分享一个小技巧无论配置怎么改改完之后都先在一台节点上启动用zkServer.sh status确认模式正确再逐步灰度到其他节点别一次性全改完再重启那种“改完就全挂”的滋味经历过的都懂。
返回列表