凌晨两点半被电话叫起来说某套集群的 NameNode 主备在十几分钟里来回切了三次上层写入大面积超时。爬起来先翻 NameNode 自己的日志干干净净没有任何异常堆栈再去看 ZKFC 的日志满屏的ConnectionLossException和SessionExpiredException。问题根本不在 HDFS是 ZooKeeper 那边有一台机器在做长时间的 Full GC会话超时了ZKFC 判定我这边的 NameNode 不行了把备机提了上来。这件事让我对ZooKeeper 在 Hadoop 里到底扮演什么角色有了完全不一样的认识——它不是可选的辅助组件在很多链路里它就是那个决定谁能当主、谁还活着的裁判裁判一抖全场跟着乱。这篇文章就把 ZooKeeper 在 Hadoop 生态里的实际用法掰开讲清楚HDFS 的 NameNode 主备切换靠它做了什么、YARN 的 ResourceManager 除了选举还拿它存了什么、HBase 为什么对它的依赖比 Hadoop 本身还重、Hive 这类组件又是怎么轻量地用一下。顺带把配置参数、znode 路径、我踩过的坑和排查链路都摊开讲。不管你是刚接手一套 HA 集群还是已经被主备乱切折腾过几轮下面这些内容应该都能对上你的场景。1. 先搞清楚 Hadoop 把哪些命门交给了 ZooKeeper1.1 没有 ZooKeeper 的年代HDFS 的高可用是半残的早期的 HDFS 是单 NameNode 架构SecondaryNameNode 这个名字骗了不少人。它压根不是备胎只负责定期把 fsimage 和 edits 合并缩短重启时的元数据加载时间既不接管读也不接管写。真出故障运维得手工把 SecondaryNameNode 上的元数据目录拷到一台新机器改配置、改指向、重启进程一套流程顺利的话半小时不顺利的话半天中间整个集群不可写。那时候所谓的高可用其实是高可人工恢复。后来有了 NameNode HA两台 NameNode 一主一备共享 edits 目录一般是 JournalNode 集群。但马上就撞上一个绕不开的问题**谁是主这件事谁来裁决**两台 NameNode 自己商量是商量不出来的网络一断A 不知道 B 是死了还是只是暂时联系不上两边都觉得自己该当主就成了双主脑裂两份元数据同时写数据直接废掉。必须引入一个双方都信任、能看到全局状态的第三方来做裁判。ZooKeeper 就是干这个的而且它做的远不止投票这一件事。1.2 ZooKeeper 在 Hadoop 体系里其实只干三件事别看各种组件文档里提到 ZooKeeper 的地方很多剥开看用法就三类选主靠临时节点Ephemeral Znode的唯一性。谁先创建成功谁就是主创建失败的进入待命状态并挂一个 Watch。会话一断临时节点自动消失其他人立刻收到通知重新抢。注册与心跳还是临时节点只不过每个实例建一个自己的节点父节点下ls一下就知道当前有多少活着的成员。进程挂了节点自动消失不需要额外的健康检查协议。状态存储用持久节点存一些不能丢的小状态比如 YARN 里跑了哪些应用、HBase 的 meta 表在哪台机器上。配合节点的 version 做乐观锁保证并发写入不打架。打个比方ZooKeeper 就是公司门口那块公告栏。谁想当组长就上去贴一张我是组长的便利贴但这张便利贴是用绳子拴在手腕上的人一走会话过期便利贴自动掉下来。其他人守在公告栏边上Watch一看到掉了就冲上去贴自己的。这套机制简单粗暴但极其有效因为人还在不在这件事不需要任何人去主动上报是 ZooKeeper 会话机制天然保证的。1.3 各组件到底在 ZooKeeper 上挂了什么光说概念虚直接把真实环境里ls出来的东西列出来更直观。下面这张表是我在一套跑了 HDFS YARN HBase Hive 的集群上整理出来的路径都是默认配置下的实际值组件用途关键 znode 路径节点类型HDFS NameNode主备选举/hadoop-ha/nameservice/ActiveStandbyElectorLock临时HDFS NameNode记录上次主节点供 fencing 用/hadoop-ha/nameservice/ActiveBreadCrumb持久YARN RM主备选举/yarn-leader-election/rmId/ActiveStandbyElectorLock临时YARN RM应用/容器状态恢复/rmstore/ZKRMStateRoot/RMAppRoot/...持久HBaseMaster 选举/hbase/master临时HBaseRegionServer 注册/hbase/rs/hostname,port,startcode临时HBasemeta 表位置寻址/hbase/meta-region-server持久Hive表级锁/hive_zookeeper_namespace/...临时/顺序HiveHiveServer2 实例注册/hiveserver2/...临时看到这张表你应该能反应过来一件事这些 znode 一旦丢了或者出现异常对应的组件就会失去自我认知。NameNode 不知道自己该不该是主HBase 的客户端不知道 meta 表在哪台机器上新来的 RegionServer 注册不进去。所以 ZooKeeper 集群的稳定性直接决定了上面这堆组件的稳定性它不是辅助是地基。2. NameNode 主备切换ZKFC 与 ActiveStandbyElector 的真实分工2.1 ZKFC 是个独立 JVM 进程不是 NameNode 里的一个线程很多人以为自动故障转移是 NameNode 自己干的其实不是。HDFS 里有个叫ZKFCZKFailoverController的独立进程跟 NameNode 是分开启动、分开管理的。你在机器上jps一下能看到ZKFC这个名字它默认监听 8019 端口。启动命令是hdfs --daemon start zkfc停机是hdfs --daemon stop zkfc。这个设计很关键ZKFC 挂了不会拖死 NameNodeNameNode 挂了 ZKFC 还在才能做出正确判断。ZKFC 内部有两个核心模块在跑HealthMonitor周期性通过 RPC 调本地 NameNode 的getServiceStatus判断它是不是健康比如是否处于安全模式卡死、磁盘是否写满导致 edits 写不进去。一旦判定不健康它会主动触发一次退出选举。ActiveStandbyElector真正跟 ZooKeeper 打交道的部分负责抢锁、挂 Watch、在状态变化时回调 NameNode 的becomeActive/becomeStandby。这两个模块对应两条完全不同的切换路径搞清楚它们的区别排查问题时能省下大量时间。HealthMonitor 触发的切换是我知道我不行我主动让位日志里能看到明确的健康检查失败原因而 ZooKeeper 会话超时触发的切换是别人认为你不行了本地 NameNode 可能一点毛病没有纯粹是网络或者 ZK 那边的问题。2.2 一次自动切换的完整链路把这条链路拆开看每一步都能对应到日志和 znode 状态两个 ZKFC 同时连上 ZooKeeper 集群各自尝试创建/hadoop-ha/nameservice/ActiveStandbyElectorLock这个临时节点。谁先创建成功谁的 ActiveStandbyElector 就回调本地 NameNode 的becomeActive()同时往ActiveBreadCrumb这个持久节点里写一份自己的信息主机名、端口、znode 版本号。创建失败的这边通过exists()挂上 Watch进入 StandbyNameNode 保持备用状态持续同步 edits。主节点那边如果 NameNode 进程挂了、机器宕机、或者网络断开导致 ZK 会话超时临时节点被 ZooKeeper 删除。备节点上的 Watch 被触发重新发起创建请求成功之后走一遍fencing流程确认老的主节点确实不可能再写了然后才becomeActive()。如果 fencing 失败切换中止备节点继续保持 Standby宁可切不过去也不能出现双写。第 5 步的 fencing 是最容易被忽略、也是出事时最要命的一环。2.3 关键配置怎么写以及超时参数的联动陷阱一份典型的生产配置大概长这样configuration property namedfs.nameservices/name valuemycluster/value /property property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property property nameha.zookeeper.quorum/name valuezk1:2181,zk2:2181,zk3:2181/value /property property nameha.zookeeper.session-timeout.ms/name value10000/value /property property nameha.zookeeper.parent-znode/name value/hadoop-ha/value /property property nameha.zookeeper.num.retries/name value3/value /property property nameha.zookeeper.retry-interval.ms/name value1000/value /property property namedfs.ha.fencing.methods/name valuesshfence/value /property property namedfs.ha.fencing.ssh.private-key-files/name value/home/hadoop/.ssh/id_rsa/value /property /configuration这里有个极其典型的坑ha.zookeeper.session-timeout.ms写多少并不一定生效多少。ZooKeeper 服务端有两个参数minSessionTimeout默认2 * tickTime和maxSessionTimeout默认20 * tickTime客户端请求的超时值如果超出这个区间服务端会直接把它夹到边界值上。按tickTime2000算默认区间是 4000ms 到 40000ms。你配了 10 秒落在区间内没问题但如果你为了切换更灵敏配了 3000ms实际生效的是 4000ms反过来如果你配了 60000ms 想避免误切实际生效的是 40000ms——你以为设了 60 秒实际只有 40 秒。我见过有团队排查了整整两天为什么配了 60 秒还是频繁切最后发现问题就出在这儿。所以配置之前先把zoo.cfg里的tickTime和maxSessionTimeout拿出来对一遍两边必须能对上。2.4 fencing 为什么不能省fencing 的意思是隔离在把备机提成主之前必须先确认老的主机不可能再对外提供服务了。Hadoop 内置两种方式sshfence通过 SSH 登录到老的主节点用fuser -k杀掉占用 NameNode RPC 端口的进程。SSH 不通就视为失败。shell(/path/to/script.sh)自己写脚本通过 IPMI 断电、交换机端口关闭、存储隔离等方式干掉老节点。如果不配dfs.ha.fencing.methodsHadoop 启动时只给一条警告运行时退化成什么都不做。这在网络分区场景下是灾难性的主节点其实活着只是跟 ZooKeeper 断了它手里还握着活跃的写入链路备节点这时候被提上来两个 NameNode 同时对外写元数据直接分叉。等网络恢复两份 edits 已经没法合并了。我的建议是至少配sshfence并且提前把 SSH 免密和私钥路径调通。另外sshfence默认的超时时间比较短跨机房或者机器负载高的时候容易误判失败可以搭配dfs.ha.fencing.ssh.connect-timeout适当调大。3. ResourceManager 那一侧选举之外还多了一层状态存储3.1 RM 的 Active/Standby 用的是同一套选举器YARN 的 ResourceManager HA 在选举机制上跟 HDFS 几乎一模一样复用的就是 Hadoop 的ActiveStandbyElectorznode 路径换成了/yarn-leader-election/rmId/。配置在yarn-site.xml里configuration property nameyarn.resourcemanager.ha.enabled/name valuetrue/value /property property nameyarn.resourcemanager.ha.rm-ids/name valuerm1,rm2/value /property property nameyarn.resourcemanager.zk-address/name valuezk1:2181,zk2:2181,zk3:2181/value /property property nameyarn.resourcemanager.store.class/name valueorg.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore/value /property property nameyarn.resourcemanager.zk-timeout-ms/name value10000/value /property property nameyarn.resourcemanager.zk-num-retries/name value1000/value /property /configuration注意这里有两个容易混的地址参数yarn.resourcemanager.zk-address是给选举用的yarn.resourcemanager.zk-state-store.address是给状态存储用的。大多数部署里两者指向同一个集群但如果你想让选举和状态存储走不同的 ZooKeeper 集群比如担心状态存储的写入压力影响选举灵敏度是可以分开配的。不少人改配置的时候只改了一个另一处还指着老地址切过去之后状态恢复失败应用全丢。3.2 ZKRMStateStore 存的到底是什么这是 YARN 跟 HDFS 最大的不同点。HDFS 在 ZooKeeper 上只做选举元数据本身存在 JournalNode 和 NameNode 本地磁盘里而 YARN 除了选举还把运行时状态直接存进 ZooKeeper 的持久节点。具体包括所有正在运行的 Application 记录RMAppRoot每个 Application 的 Attempt 记录AppAttemptRoot已分配的 Container 信息RMContainerRoot各种 TokenApplicationToken、AMRMToken、DelegationToken这套状态的意义在于主 RM 突然挂掉备 RM 切上来之后能从 ZooKeeper 里把正在跑的应用和容器全部恢复出来不需要把整个集群上跑的作业重新提交一遍。想象一下一个 YARN 集群上有几百个长跑作业如果切换一次全丢代价是不可接受的。除了ZKRMStateStoreHadoop 还提供了FileSystemRMStateStore把状态写到 HDFS 上和MemoryRMStateStore只存内存重启即丢仅用于测试。生产上首选 ZooKeeper原因很直接如果用 HDFS 存状态而 HDFS 本身在故障恢复期间不可用那就陷入了要恢复 YARN 得先有 HDFS要恢复 HDFS 得先有 YARN的死循环。ZooKeeper 是独立部署的不受这套依赖关系影响天然是更稳的落脚点。3.3 状态存储带来的副作用znode 数量膨胀这套机制的代价是 znode 数量会随着集群规模线性增长。每提交一个应用、每分配一个容器都会在/rmstore下面产生若干个节点。一个中等规模的集群跑上几天几万个 znode 是很常见的。znode 多了会带来一连串问题ZooKeeper 的快照文件变大、重启时加载快照变慢、stat命令的响应时间变长。我见过一个集群的/rmstore下面堆了几十万个 znodeZooKeeper 重启一次要十几分钟这段时间里 YARN 完全无法提交新作业。对策有这么几条一是在 ZooKeeper 3.5 及以上版本开启autopurge.purgeInterval和autopurge.snapRetainCount定期清理旧快照二是在 YARN 侧配置yarn.resourcemanager.state-store.max-completed-applications控制保留的已完成应用数量三是运维上定期用zkCleanup.sh手工清理但要注意别在主节点上跑清理过程会占用大量 IO。4. HBase 对 ZooKeeper 的依赖比 Hadoop 本身还重4.1 三个核心 znode 撑起了整个 HBase 的寻址如果 HDFS 对 ZooKeeper 是用一下选举那 HBase 就是深度绑定。默认情况下 HBase 会自己去管理一个 ZooKeeper 集群hbase.zookeeper.quorum指向独立部署的集群更稳妥所有 RegionServer 和 Master 都通过它来发现彼此。最关键的三个节点# 进入 zk 客户端 zkCli.sh -server zk1:2181 # 1. meta 表所在的 RegionServer 地址持久节点 get /hbase/meta-region-server # 返回类似主机名,端口,startcode # 2. 当前 Active Master临时节点Master 挂了自动消失 get /hbase/master # 3. 所有存活的 RegionServer 列表每个都是一个临时节点 ls /hbase/rsmeta-region-server这个节点是整个 HBase 客户端寻址的起点客户端第一次连上集群先查这个节点拿到hbase:meta表在哪台 RegionServer 上再通过 meta 表找到自己关心的 rowkey 落在哪个 Region、哪台机器。这个节点要是丢了或者内容过期客户端会反复重试、报RegionServerFinder相关的错误表现就是明明集群是好的客户端就是连不上。/hbase/rs下面挂的是每个 RegionServer 的临时节点节点名一般是主机名,端口,启动时间戳的格式。这个列表就是整个集群的活体名册Master 靠它判断哪些机器在线客户端也靠它做负载均衡的候选。4.2 RegionServer 断连之后的自杀逻辑这是 HBase 设计里一个很值得说的点。RegionServer 跟 ZooKeeper 的会话一旦超时它的临时节点就从/hbase/rs里消失了Master 会认为它死了然后把这个 RS 上原来负责的 Region 重新分配给别的机器。问题在于那个死掉的 RS 可能还活着只是跟 ZooKeeper 断了。如果它还继续处理客户端请求就会出现两个 RS 同时服务同一个 Region 的情况数据必然不一致。所以 HBase 在 RegionServer 里加了一个自杀机制一旦检测到跟 ZooKeeper 的会话丢失且重连失败RS 会主动调用abort()把自己干掉。日志里能看到类似Aborting region server ... because we lost the ZK session的内容。这个逻辑听着粗暴但在分布式系统里是正确做法——宁可让服务中断一下也绝不能容忍脏写。这也解释了一个常见现象集群跑得好好的突然一批 RegionServer 集体退出日志里全是 session expired。十有八九不是 HBase 的问题而是 ZooKeeper 那边卡住了GC、磁盘 IO、网络或者 session timeout 配得太激进。4.3 session timeout 与 maxSessionTimeout 的联动这里有个经典坑HBase 的zookeeper.session.timeout默认是 90000 毫秒90 秒这个值明显大于 ZooKeeper 默认的maxSessionTimeout20 * tickTime在tickTime2000时是 40000 毫秒。后果就是你配了 90 秒ZooKeeper 实际只给你 40 秒集群对网络抖动和 GC 的容忍度比你预期的低了一大截稍有抖动就集体 abort。!-- hbase-site.xml -- property namezookeeper.session.timeout/name value90000/value /property property namezookeeper.znode.parent/name value/hbase/value /property property namehbase.zookeeper.quorum/name valuezk1,zk2,zk3/value /property property namehbase.zookeeper.property.clientPort/name value2181/value /property# zoo.cfg 里必须同步放开上限 tickTime2000 initLimit10 syncLimit5 maxSessionTimeout120000两边对不上你写的 90 秒就是自欺欺人。上线前拿zkCli连上去用get随便建个临时节点观察实际会话超时时间比看配置文件靠谱得多。5. Hive 和其它组件的用法轻量但同样不能少5.1 Hive 的并发控制在 ZooKeeper 上的落点Hive 早期做表级并发控制用的是 ZooKeeper 上的锁实现ZooKeeperHiveLockManager。多个会话同时写同一张表时先在 ZooKeeper 上抢一个锁节点抢不到就等待或者报错。相关配置在hive-site.xmlconfiguration property namehive.zookeeper.quorum/name valuezk1,zk2,zk3/value /property property namehive.zookeeper.client.port/name value2181/value /property property namehive.zookeeper.namespace/name valuehive_zookeeper_namespace/value /property property namehive.support.concurrency/name valuetrue/value /property /configuration需要说明的是现在主流部署里 Hive 的锁更多走的是 Metastore 自带的数据库锁DbLockManagerZooKeeper 锁反而用得少了。但hive.zookeeper.quorum这组配置在很多发行版里仍然是必填项因为 HiveServer2 的实例注册和高可用发现仍然依赖 ZooKeeper。填错的直接后果是 HS2 启动时卡在注册阶段或者 beeline 连不上。5.2 HiveServer2 的多实例注册与客户端发现多个 HiveServer2 同时服务时每个实例启动后都会在 ZooKeeper 的指定 namespace 下创建一个临时节点把自己的 host 和端口写进去。客户端用服务发现模式连接时URL 里不用写死某一台机器而是把 ZooKeeper 地址交给驱动去动态查找jdbc:hive2://zk1:2181,zk2:2181,zk3:2181/; serviceDiscoveryModezooKeeper; zooKeeperNamespacehiveserver2这条链接的意义在于某台 HS2 挂了它的临时节点自动消失客户端重新查找时就不会再连到那台死机器上。这套机制和 HBase 的 RS 注册是同一个思路用的还是临时节点 Watch这个万能组合。配置时唯一要注意的是zooKeeperNamespace必须和 HS2 服务端配的一致默认值在不同版本里有差异最好显式写死。5.3 调度和批处理系统里的分布式锁除了这几个核心组件围绕 Hadoop 生态跑的一堆调度系统、元数据服务、配置中心很多也会直接连同一个 ZooKeeper 集群做分布式锁和任务互斥。典型场景是同一个任务在同一时刻只能有一个实例在跑做法就是去 ZooKeeper 上抢一个临时节点抢到才执行执行完释放进程崩了节点自动消失不会留下死锁。这类用法有但要有节制。同一个 ZooKeeper 集群上挂的东西越多任何一个业务方写出高频创建删除节点的代码都会拖累整个集群。我见过某个自研的定时任务框架每秒往 ZooKeeper 上打几百次写请求直接把 ZooKeeper 的请求队列打满连带 HDFS 的自动切换都失灵。所以我的经验是核心组件HDFS、YARN、HBase共用一个 ZK 集群外围业务系统另开一个物理隔离别让它们互相伤害。6. ZooKeeper 自己出问题时Hadoop 侧会是什么表现6.1 三种典型现象指向完全不同的根因现象一NameNode 主备频繁来回切。业务侧看到的是连接闪断、重试增多ZKFC 日志里是成片的ConnectionLossException或SessionExpiredException。这基本可以锁定 ZooKeeper 侧的会话不稳定跟 NameNode 自身无关。现象二HBase 一批 RegionServer 同时退出。日志里是Aborting region server、region server opening反复出现/hbase/rs下的节点数量剧烈波动。这种情况通常意味着 ZooKeeper 出现了全局性的卡顿所有 RS 的会话在同一时间段集体超时。现象三YARN 提交新作业全部失败。报错往往是连不上 RM 或者状态存储写入失败。如果 RM 的 znode 还在但状态存储那部分/rmstore写入超时作业提交会一直卡在ACCEPTED状态既跑不起来也不报明确错误。这三种现象的排查起点都一样先去确认 ZooKeeper 集群本身的状态别急着翻 Hadoop 的日志。方向错了翻一天也翻不出来。6.2 从现象到根因的排查顺序我一般的顺序是这样的# 1. 先看进程活没活返回 imok 说明进程在 echo ruok | nc zk1 2181 # 2. 看整体运行指标延迟、连接数、请求队列 echo mntr | nc zk1 2181 # 3. 看当前连接都是从哪些机器来的找异常来源 echo cons | nc zk1 2181 # 4. 看这台机器的角色和 znode 数量 echo srvr | nc zk1 2181mntr里我最关注这几个指标指标含义异常阈值参考zk_avg_latency平均请求延迟ms超过 50 就要警惕超过 200 基本已经在抖zk_max_latency最大请求延迟ms出现几千的值说明有过明显卡顿zk_outstanding_requests排队中的请求数持续大于 0 说明处理不过来了zk_num_alive_connections当前连接数突然暴涨要查是不是客户端重连风暴zk_znode_count节点总数几十万级别要考虑清理zk_watch_count注册的 Watch 总数异常增长会拖慢写操作注意ruok返回imok只能说明进程还活着不代表服务正常。我遇到过 ZooKeeper 进程在、端口通、ruok正常但整个集群因为磁盘 IO 卡死而完全无法处理请求的情况。所以监控上只看ruok是绝对不够的mntr的延迟指标才是真正能提前预警的东西。6.3 常见根因对照表现象可能的根因验证方式ZKFC 反复切换ZooKeeper 节点 Full GC 或磁盘 IO 卡顿看 GC 日志、zk_max_latency、机器iostat会话频繁过期网络抖动、tickTime与客户端超时不匹配逐跳 ping、对照客户端与服务端超时区间客户端连不上、报连接拒绝maxClientCnxns打满cons数连接数对比zoo.cfg配置重启加载慢、启动十几分钟znode 数量过多、快照文件巨大看dataDir目录大小和zk_znode_countHBase RS 集体 abort客户端 session timeout 大于服务端 maxSessionTimeout对比hbase-site.xml与zoo.cfg四字命令无响应3.5 之后默认只允许srvr检查4lw.commands.whitelist7. 我在生产上踩过的坑和对应的调参思路7.1 session timeout 不是越小越好也不是越大越好这两年被问最多的问题就是超时设多少合适。答案取决于你能容忍多长的故障发现时间以及你的网络和 GC 状况有多稳。设小了一次网络微抖或者一次 3 秒的 GC就足以让会话过期触发误切换。HDFS 一次误切换的代价是几十秒的写入中断HBase 一次误 abort 的代价是 Region 重新分配、几分钟的服务不可用。设大了真故障时要等很久才能发现期间业务一直报错。我的经验值HDFS ZKFC 用 10 秒左右HBase 用 90 秒前提是 ZooKeeper 侧放开了maxSessionTimeoutYARN RM 用 10 秒左右。这个组合在多数环境里能兼顾灵敏度和稳定性。如果集群跨机房部署网络 RTT 波动大HDFS 那边可以再往上抬到 15 到 20 秒别硬扛。7.2 ZooKeeper 绝对不要和 Hadoop 混部这条我说得重一点因为踩过太多次。ZooKeeper 对延迟极其敏感它的写请求要落盘事务日志对磁盘 IO 的要求很高。如果跟 DataNode、RegionServer 这类 IO 大户混在一台机器上DataNode 一次大的 compaction 或者一次批量写入就能把磁盘打满ZooKeeper 的写延迟跟着飙升然后整个 Hadoop 集群开始抖。正确做法是单独的 3 台或 5 台小规格机器只跑 ZooKeeper磁盘用 SSD 最好至少保证事务日志和数据目录分开dataDir/data/zookeeper/data dataLogDir/data/zookeeper/logdataLogDir跟dataDir分开是必须的。ZooKeeper 的事务日志是顺序追加写快照是定期全量写两者混在一个盘上会互相干扰日志写延迟一高所有客户端跟着遭殃。这个改动成本极低收益极大。7.3 几个零碎但致命的细节maxClientCnxns默认是 60。这是一个 ZK 节点对单个客户端 IP的连接数上限。一套 HBase 集群几十台机器还好但如果有一批应用跑在同一台机器上全连同一个 ZK很容易打满客户端报Too many connections。要么调大这个值要么在客户端侧做连接复用。jute.maxbuffer默认 1MB。znode 里存的数据不能太大往/rmstore或 HBase 的节点里写大数据会直接超限报错。这也是为什么 YARN 的状态存储只存元信息不存日志内容。四字命令白名单。3.5 之后的版本默认只放行srvrmntr、cons这些都要在zoo.cfg里加4lw.commands.whitelist*或者逐个列出。很多监控系统升级之后突然采集不到数据就是这个原因。快照清理。不开autopurge.purgeInterval的话dataDir下的快照会一直堆最后把磁盘撑满。磁盘一满ZooKeeper 直接拒绝所有写请求表现就是选举失灵、HBase 全线报警。这个我建议purgeInterval24、snapRetainCount5别等出事再补。别把数据目录放 NFS 或网络存储上。ZooKeeper 对 fsync 的语义要求很严网络存储的延迟波动会让它表现极不稳定而且出问题时的现象非常迷惑人——所有指标看着都还行就是偶尔超时。7.4 关于监控我想多说一句很多人监控 ZooKeeper 就配一个端口探活这跟没配差不多。真正有用的监控是这几条曲线放在一起看zk_avg_latency的 P99、zk_outstanding_requests是否长期大于 0、单机的连接数变化、zk_znode_count的增长趋势、以及磁盘 IO 的 await 值。这几条曲线里只要出现延迟走高 排队请求增加的组合就说明 ZooKeeper 已经在处理不过来的边缘了这时候 HDFS 和 HBase 那边的会话超时随时可能触发。能在切换发生之前从监控上看到这个趋势才叫有效监控。我个人的习惯是每次做完集群变更加节点、改配置、上线新业务都会盯一天 ZooKeeper 的延迟曲线确认它没有比变更前更抖。这个习惯帮我提前发现过好几次问题——有一次是新上线的某个服务把连接全堆在了一台 ZK 上延迟曲线肉眼可见地歪了一边及时调整客户端配置才没演变成事故。分布式系统的很多故障其实在指标上都有几十分钟甚至几小时的预热期只是大多数时候没人去看。
阅读完成 · 觉得有帮助?