恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Hadoop高可用集群核心解析:ZooKeeper与JournalNode自动故障转移实践
首页
资讯中心
/
Hadoop高可用集群核心解析:ZooKeeper与JournalNode自动故障转移实践
Hadoop高可用集群核心解析:ZooKeeper与JournalNode自动故障转移实践
发布时间:2026/10/15 4:40:16
1. 为什么要用ZooKeeper来做Hadoop的HA自动故障转移的底层逻辑1.1 单点故障与“自动切换”的真实含义先从一个很多人理解错的概念说起。高可用不是“多装一台机器然后客户端手动切换”而是要让集群在某个核心进程宕机后自己感知、自己决策、自己接管。Hadoop 1.x 时代NameNode 是典型的单点一旦它挂了整个集群的元数据服务就停了所有写在 HDFS 上的数据虽然还在 DataNode 里但没有索引客户端连“目录路径对应哪些块”这种最基本的信息都拿不到。所以 Hadoop 2.x 之后引入了 NameNode 高可用本质就是要解决“一个 NameNode 挂了另一个能无缝补上”的问题。在 HA 架构里至少部署两个 NameNode一个 Active一个 Standby。Active 负责处理所有客户端读写请求、管理数据块与文件的映射关系Standby 主要负责接收元数据变更并把状态持续跟上备而不闲。但问题迎面而来Standby 如何才能和 Active 保持完全一致Active 每时每刻都在产生新的元数据操作日志比如创建目录、写入块、删除文件Standby 必须把这些操作完整地拿到并回放。一旦 Active 宕机Standby 如果缺失了最近几十秒的操作那它接管之后就会丢掉一堆新文件的元数据这是不可接受的。所以高可用系统的第一条腿是“元数据操作日志的实时同步”。第二条腿才是“故障发生时分秒级地选出新 Active”。这两条腿缺一个整个 HA 都是假的。我见过有些教程把重点全放在配置项上却没讲清楚这两条腿分别由哪些进程承担导致读者照着配完了却不知道日志里哪些报错是由哪条腿引起的。下面把这两条腿拆开。1.2 ZooKeeper与JournalNode各自的分工先说明一个容易混淆点ZooKeeper 在这个架构里并不负责存储 HDFS 的元数据也不负责保存 edit log。真正保存共享 edit log 的是 JournalNode。ZooKeeper 的角色更像一个“表决委员会”专门用来协调两个 NameNode 谁该成为 Active以及防止极端情况下两个节点同时以 Active 身份对外服务。JournalNode 的运作逻辑可以用“过半确认”来概括。Active NameNode 每产生一条 edit log会同时发给配置里指定的多个 JournalNode通常是三个或五个只有超过半数的 JournalNode 返回确认成功这条日志才算真正落盘。为什么要求过半因为这是分布式系统做“多数派决策”的最低成本方案。三个 JournalNode 里坏了两个集群就无法继续写日志这其实是故意的——宁可暂时不可用也不能让两个 NameNode 各自拿着不完整的日志继续写最终造成数据分叉。JournalNode 端口默认是 8485这个端口后面配置时需要留意。ZooKeeper 的工作方式和 JournalNode 完全不同。每个 NameNode 旁边都会启动一个 ZKFCZooKeeper FailoverController守护进程你可以把它理解成一个专职哨兵。哨兵会持续往 ZooKeeper 里写入临时节点谁成功持有这个分布式锁节点谁代表的 NameNode 就是当前 Active。当 Active 对应的节点挂掉其 ZKFC 与 ZooKeeper 的心跳断开临时节点会自动消失另一个 ZKFC 观察到锁被释放后会发起竞选并接管 Active 角色同时通过 fencing 机制把旧的 NameNode 隔离掉防止它因为网络抖动“复活”后继续写日志形成脑裂。很多新手第一次看 ZKFC 的日志会觉得它有点像“数据库主从切换”但它的本质是分布式锁和健康监控理解到这一层后面排障会轻松很多。2. 节点规划和版本选型高可用不是靠堆机器堆出来的2.1 节点角色怎么分两主节点三仲裁节点装 Hadoop HA 之前最值得花时间的一件事不是下载安装包而是把节点角色画清楚。高可用集群至少需要三个角色集合ZooKeeper 集群、JournalNode 集群、双 NameNode。我推荐一套在测试环境和中小规模生产都适用的方案用三台主机node1.example.com、node2.example.com、node3.example.com。节点ZooKeeperJournalNodeNameNodeZKFCnode1是是主NameNode是node2是是备NameNode是node3是是否否这套三节点合一部署最大的好处是不需要额外买机器。ZooKeeper 和 JournalNode 都需要奇数个节点才能形成完整的多数派投票三台机器正好能满足这两个要求。NameNode 只需要两台分别在 node1 和 node2 上。DataNode 通常也是所有节点都部署把存储能力铺开。如果生产环境预算比较充足我更推荐把 ZooKeeper 单独拆到三台独立机器上JournalNode 也单独拆出去。原因是 ZooKeeper 的选主和 JournalNode 的日志确认都依赖“多数派存活”如果它们和 NameNode 挤在同一台机器这台机器一挂就会同时损失掉一个仲裁节点和一个日志存储节点故障恢复窗口会明显变长。但对于刚开始接触 HA、想在一批机器上快速验证概念的读者三合一部署完全没有问题重点是先把流程跑通。2.2 版本搭配与JDK环境怎么选版本组合不写清楚配置照抄也会翻车。我实测下来比较稳的组合是Hadoop 3.3.x搭配 ZooKeeper 3.4.x 或 3.6.x。如果选 ZooKeeper 3.4配置最简单2181 客户端端口和 2888/3888 集群通信端口默认就开放适合快速跑通流程生产环境更推荐 3.5 或 3.6安全性、ACL、动态重配置这些能力更好代价是配置文件里多几个可选参数不过不影响基础使用。JDK 这里我要专门说一句。Hadoop 3.x 对 Java 8 支持最成熟如果你机器上同时还装了 openjdk 11 甚至 17运行hdfs命令时可能会出现反射类告警虽然有时候不致命但会干扰你看日志。建议所有节点统一 JDK 版本、统一安装路径并在hadoop-env.sh里显式导出 JAVA_HOME。不要依赖每个用户各自的 shell 环境变量因为通过 systemd 或者start-dfs.sh启动进程时继承的环境经常和手动执行java -version时不一样这个问题我曾经在排查“进程起不来却说找不到 JAVA_HOME”时遇到过最后就是因为在配置里写死路径解决的。另外记着把时间同步配置好。ZooKeeper 的会话判断依赖时间戳如果几个节点时间差太多可能出现明明进程健康的会话却反复超时失效的现象。ZKFC 又会把“会话失效”误判成“NameNode 挂了”从而触发不必要的故障切换。很多虚拟化平台默认时间不同步这一步不能省。3. 先搭ZooKeeper三个节点zoo.cfg、myid这些细节不能省3.1 三份关键的配置文件和myidZooKeeper 安装包解压后核心配置集中在conf/zoo.cfg、数据目录和 myid 文件上。我习惯为 ZooKeeper 单独建一个系统用户和独立数据目录避免和 Hadoop 的其他服务共用账号导致权限混乱。三个节点都执行useradd zk mkdir -p /data/zookeeper chown -R zk:zk /data/zookeeper新建conf/zoo.cfg内容如下tickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper clientPort2181 server.1node1.example.com:2888:3888 server.2node2.example.com:2888:3888 server.3node3.example.com:2888:3888这里的三个端口别搞混。2181 是 ZooKeeper 给客户端用的ZKFC 最终会通过这个端口连接 ZooKeeper。2888 是集群内部 follower 和 leader 之间同步数据用的3888 是选主时各节点互相通信用的。如果机器上有防火墙、或者跑在云上需要配置安全组这三个端口都要放行。只放行 2181 是最常见的错误集群看似都起来了实际上节点之间根本没法完成选举。server.1后面的数字 1、2、3必须和每个节点数据目录里的myid文件内容一致。我在 node1 上执行echo 1 /data/zookeeper/myidnode2 写入 2node3 写入 3。有个很隐蔽的坑是很多教程会建议“把配置复制到所有节点”如果你直接scp整个配置目录很容易把别的节点的 myid 也一起覆盖过去。三个节点如果 myid 全是 1ZooKeeper 启动后不可能选出正确 leader。跨机器复制配置时myid必须单独重新生成。3.2 启动顺序与状态自检启动 ZooKeeper 用zkServer.sh start建议先启 node1然后依次 node2、node3。严格来说顺序并不强制但这样做的好处是方便你观察节点角色变化对第一次搭集群的人来说更友好。启动后别立刻进入下一步先做两个自检动作。第一在各节点执行zkServer.sh status如果正常输出里会标明当前节点是leader还是follower。三台机器上看到了明确的 leader/follower说明选主机制已经工作。如果三台全是 standalone 或者全是 leader那就说明配置文件里的server.x地址解析有问题或者 myid 没有配对。第二用客户端连一下看基础读写zkCli.sh -server node1.example.com:2181进入交互后执行ls /能看到一个空节点列表或者默认节点就说明客户端连接正常。这一步很容易被忽略但如果在后面执行hdfs zkfc -formatZK时遇到连接超时基本就是在这个环节埋下的坑2181 端口没通或者 hosts 文件名和配置里写的不一致。4. Hadoop配置文件拆解从core-site到hdfs-site逐项说清楚4.1 core-site.xml先接上默认文件系统和ZooKeeper地址不少教程把core-site.xml当成“填两行就完事”的文件实际上它定义了客户端访问 HDFS 的入口也定义了 ZKFC 连接 ZooKeeper 的地址。我用mycluster作为 nameservice 标识两个 NameNode 的别名分别叫 nn1、nn2。configuration property namefs.defaultFS/name valuehdfs://mycluster/value /property property namehadoop.tmp.dir/name value/data/hadoop/tmp/value /property property nameha.zookeeper.quorum/name valuenode1.example.com:2181,node2.example.com:2181,node3.example.com:2181/value /property /configurationfs.defaultFS写成hdfs://mycluster而不是hdfs://node1.example.com:8020这个区别非常关键。客户端拿到hdfs://mycluster后会走 failover proxy provider 自动判断当前哪个 NameNode 是 Active用户完全不需要关心节点切换。如果这里写死了单节点地址等于把高可用对客户端藏起来了切换后客户端会一直连旧地址感知不到新 Active。hadoop.tmp.dir决定 NameNode 持久化元数据的默认位置。如果不显式配置默认落在/tmp下系统一重启就清空了到时候排障会非常痛苦。我建议所有节点统一规划数据目录把 tmp、name、journal 这些目录都放到独立磁盘分区不要让系统盘和 HDFS 元数据挤在一起。ha.zookeeper.quorum一定要把三个 ZooKeeper 节点全部写进去。如果只写一个节点ZKFC 启动时也能连上但那个节点一挂HA 的感知就断掉了自动切换瞬间失效。它其实承担的是“ZooKeeper 客户端连接串”的作用而不是“随便写一个意思意思”。4.2 hdfs-site.xmlnameservice、JournalNode、自动切换全配齐hdfs-site.xml是 HA 的核心配置文件内容多我分三组来讲。第一组定义 nameservice 和 NameNode 地址property namedfs.nameservices/name valuemycluster/value /property property namedfs.ha.namenodes.mycluster/name valuenn1,nn2/value /property property namedfs.namenode.rpc-address.mycluster.nn1/name valuenode1.example.com:8020/value /property property namedfs.namenode.rpc-address.mycluster.nn2/name valuenode2.example.com:8020/value /property property namedfs.namenode.http-address.mycluster.nn1/name valuenode1.example.com:9870/value /property property namedfs.namenode.http-address.mycluster.nn2/name valuenode2.example.com:9870/value /property这几个属性最容易犯的错误是拼写不一致。dfs.ha.namenodes.mycluster里的名字、以及后面dfs.namenode.rpc-address.mycluster.nn1里的nn1必须严格对应。少写一个字符启动时 NameNode 可能仍然能起但当客户端解析时就会提示找不到对应的 NameNode 地址。RPC 地址是 DataNode 和客户端真正通信用的HTTP 地址是给 Web 控制台用的端口默认是 Hadoop 3.x 的 9870如果你照着网上 Hadoop 2.x 的教程写 50070页面是打不开的。第二组是 JournalNode 相关配置property namedfs.namenode.shared.edits.dir/name valueqjournal://node1.example.com:8485;node2.example.com:8485;node3.example.com:8485/mycluster/value /property property namedfs.journalnode.edits.dir/name value/data/hadoop/journal/value /propertydfs.namenode.shared.edits.dir采用qjournal://协议后面接三个 JournalNode 地址用分号分隔最后加/mycluster。这个地址是 Active NameNode 写入共享 edit log 的目标也是 Standby NameNode 同步日志的数据源。任何一个节点写错端口启动时就会一直卡在等待 JournalNode 响应的状态日志里反复出现连接超时。我在某次安装时就是因为漏了末尾的/mycluster导致两个 NameNode 的共享日志目录一直对不上白白排查了两个多小时。第三组是自动切换和 fencing 配置property namedfs.ha.automatic-failover.enabled/name valuetrue/value /property property namedfs.client.failover.proxy.provider/name valueorg.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider/value /property property namedfs.ha.fencing.methods/name valuesshfence/value /property property namedfs.ha.fencing.ssh.connect-timeout/name value30000/value /propertydfs.ha.automatic-failover.enabled决定 ZKFC 要不要真正参与自动切换。这个属性写 false 不是不行但你的集群就退化成了“只能手动执行haadmin -transitionToActive的半自动模式”一旦没有人盯着故障时集群就停了。sshfence是常用隔离方式新 Active 的 ZKFC 会通过 SSH 登录到旧 NameNode 所在机器把旧的 NameNode 进程杀掉。所以之前提到的 SSH 免密配置一定不能只在主备节点之间配而要保证任何一台 NameNode 的 ZKFC 用户都能 SSH 到另一台执行 fence 命令。4.3 容易被忽略的两个辅助文件除了两个核心 XML还有两个文件会影响集群能否正常启动。一个是workers新版本 Hadoop 里它替代了旧版本的slaves文件里面写着所有 DataNode 的主机名一行一个三节点部署就写三行。如果你拿旧教程里的slaves文件名去改Hadoop 3.x 根本不会读取DataNode 一个都不会启动。另一个是hadoop-env.sh。除了设置 JAVA_HOME还要关注 JVM 堆内存参数。HADOOP_HEAPSIZE默认值偏保守如果节点内存够用建议改成 2048 或更高。NameNode 元数据会常驻内存堆太小会导致 Full GC 频繁表面看是进程还活着实际上集群已经“假死”客户端请求全部超时。5. 启动顺序里最容易翻车的一环先JN、再格式化、最后bootstrap5.1 为什么先启动JournalNode再执行格式化这一步是整个安装过程中返工代价最高的环节。我在第一次实践时就被“格式化顺序”坑过所以单独用一小节把逻辑讲清楚。正确的顺序是先把 ZooKeeper 集群全部启动然后把三个 JournalNode 全部启动再回头去格式化 NameNode。JournalNode 启动命令hdfs --daemon start journalnode三个节点上都要执行。为什么要这么严格因为hdfs namenode -format这个过程不只是生成本地元数据目录它还会尝试连接配置里指定的 JournalNode向它们初始化共享编辑日志的存储结构。如果 JournalNode 还没起来NameNode 格式化时虽然可能不报错但共享日志的初始化并不会真正完成。之后 Standby 节点同步时就会发现共享编辑日志目录是空的或者缺失 namespaceID报错信息往往又长又难懂。同理如果 JournalNode 的dfs.journalnode.edits.dir指定的目录下有旧数据一定要先清理干净再启动。多个不同集群、或者多次用不同 nameservice 格式化后残留的 journals 数据会让 NameNode 认为共享日志已经存在拒绝重新初始化最终只能把所有角色全部停掉、清空数据目录、重新来一遍。在测试环境这个代价还能接受在生产环境相当于一次数据灾难。所以做任何格式化之前先确认 JournalNode 目录是干净的。5.2 ZKFC格式化与Standby节点引导在主 NameNodenode1上先执行 ZooKeeper 格式化。这个命令会在 ZooKeeper 上创建 HA 相关的 znode 节点相当于把“选举委员会”的场地搭好hdfs zkfc -formatZK执行后会提示确认输入 Y 回车。需要特别注意的是这个命令只在主 NameNode 上执行一次不要在备节点上也执行。如果两台机器都执行ZooKeeper 里的 HA 节点会被反复清空重建故障切换时可能出现两边同时注册、互相干扰的异常。然后格式化主 NameNodehdfs namenode -format格式化结束后先把主节点自身的 NameNode 和 ZKFC 启动起来让 node1 先正常对外提供服务hdfs --daemon start namenode hdfs --daemon start zkfc接着在备节点 node2 上执行hdfs namenode -bootstrapStandby这条命令的作用是把主节点的元数据快照同步到备节点的本地目录同时把共享编辑日志回放一遍让备节点的状态追平主节点。这一步执行时一定要确保主节点 8020 RPC 端口能从 node2 访问到并且共享日志已经初始化。如果bootstrapStandby一直卡住或者报连接失败不要反复重跑先检查 netstat 确认端口是否在监听、防火墙是否放行。同步完成后在 node2 上启动备 NameNode 和 ZKFChdfs --daemon start namenode hdfs --daemon start zkfc全部启动后用jps检查各节点进程。node1 和 node2 都应该有NameNode、ZKFC、JournalNode、DataNode这几个进程node3 应该有JournalNode、DataNode。如果少了ZKFC多半是start-dfs.sh没有自动拉起它需要手动执行上述命令。最后再启动整套集群start-dfs.sh这个脚本会把配置里列出的 JournalNode、DataNode 都拉起来。如果前面的手动步骤都已经完成这里只是补齐 DataNode不会重复初始化异常。6. 模拟故障切换实测kill掉Active后系统做了什么6.1 切换前的状态确认配置和启动都完成以后千万不要直接认为集群“装好了”。HA 有没有真的生效必须通过故障注入测试来验证。先查询当前所有 NameNode 的状态hdfs haadmin -getAllServiceState正常情况下输出类似node1.example.com:8020 active node2.example.com:8020 standby如果两个节点都是 standby说明 ZKFC 没有成功注册 Active 锁优先检查ha.zookeeper.quorum是否配全、dfs.ha.automatic-failover.enabled是否为 true、ZooKeeper 里是否已经生成了 HA 相关节点。如果两个节点都是 active说明已经出现了脑裂必须在第一时间停止服务重点排查 fencing 配置和共享编辑日志是不是没有真正生效。6.2 故障注入与切换过程观察模拟故障最彻底的方式是直接杀掉 Active NameNode 进程不要走优雅停止流程。优雅停止会让 ZKFC 有机会主动释放锁没法检验故障发生瞬间系统反应。我的做法是kill -9 $(cat /data/hadoop/tmp/dfs/namenode/current/pid)杀掉 node1 上的 NameNode 后立刻在 node2 上观察日志目录里的hadoop-hdfs-namenode-*.log并循环执行状态查询hdfs haadmin -getAllServiceState我实测的场景是node1 的 NameNode 被强杀后几十秒内node2 的状态从 standby 变成了 active。切换期间 node2 先从 JournalNode 拉取剩余的 edit log 并回放进入 Active 状态后开始正常接收 DataNode 心跳和客户端请求。切换耗时的主要影响因素有三个ZKFC 与 ZooKeeper 之间的会话超时时间、JournalNode 日志同步延迟、以及回放日志的速度。如果 ZKFC 会话超时配得过大切换时间会被明显拉长。此时再回头看 node1会发现它的 ZKFC 还在活着但 ZKFC 发现自己对应的 NameNode 丢失后会自动释放 ZooKeeper 上的临时锁。如果 sshfence 配置正确它还会通过 SSH 登录 node1把残留的 NameNode 进程进程清理掉防止它后续“复活”后继续写日志。6.3 恢复节点与手工切换练习故障测试后要把 node1 恢复回来。注意不要直接执行start-dfs.sh错误地全量启动可能把刚接管 Active 状态的 node2 又拉成备节点。正确做法是单独启动 node1 上的 ZKFC 和 NameNode让它以 Standby 身份重新加入集群hdfs --daemon start zkfc hdfs --daemon start namenode启动后再次查询状态正常情况 node1 会变成 standby和 node2 形成一主一备。如果两个节点都变成了 active说明 fencing 机制没有把旧主节点拦住必须去查 SSH 免密配置和dfs.ha.fencing.ssh.connect-timeout相关日志。手工切换也建议练一次hdfs haadmin -transitionToActive node1.example.com:8020手工切换在生产环境要慎用因为系统不会帮你判断当前节点是否适合接管但测试环境里拿来验证状态机逻辑非常有用。7. 实操中的几个高频坑以及排查思路7.1 端口、防火墙和Hosts解析三大通信杀手第一个坑集中在端口放行。ZooKeeper 需要 2181、2888、3888JournalNode 需要 8485NameNode RPC 是 8020HTTP 是 9870DataNode 默认 9864。有些云环境的安全组默认只放行几个常用端口结果 ZooKeeper 进程全起来了、zkServer.sh status也显示正常但 Hadoop 侧的 ZKFC 就是连不上。排查时先用 nc 或 telnet 测试端口而不是一上来就改配置重启nc -vz node1.example.com 8485Hosts 解析的问题更加隐蔽。我在某次部署时发现一个现象ZooKeeper 选举能成功但 Hadoop 的 QJM 协议一直连不上 JournalNode。后来定位到原因是/etc/hosts里把主机名写成了短名而 Hadoop 配置文件里用的全是 FQDN两边解析对不上。去翻 JournalNode 日志时看到的错误报文又像“地址无法解析”、又像“连接超时”容易让人误判成网络问题。建议所有配置文件里的主机名统一使用 FQDN并且/etc/hosts和/etc/sysconfig/network这类系统配置保持同一套写法。不要一半用短名、一半用全名这是最折腾人的状态。7.2 数据目录权限与残留数据格式化前必须做减法数据目录权限问题常见于 ZooKeeper 先以 root 启动创建了目录之后为安全改用普通用户启动。普通用户如果没有目录的写权限ZooKeeper 启动时会直接失败或者启动成功但无法创建数据文件。Hadoop 的dfs.name.dir、dfs.journalnode.edits.dir等目录也有同样问题。最稳妥的做法是第一步就用统一规划账号把所有数据目录的所有者一次性设置好。残留数据导致的“假成功”也很常见。如果之前格式化过集群后来想重新初始化一定要把三处清干净NameNode 本地元数据目录、JournalNode 的共享编辑日志目录、ZooKeeper 里 HA 相关的 znode。只清 NameNode 的目录没用因为 JournalNode 里如果残留着旧 namespaceID新格式化的 NameNode 在初始化共享日志时就会检测到不一致然后报出一堆看似莫名其妙的一致性错误。清理 ZooKeeper 节点时我习惯直接用zkCli.sh删除对应 znode而不是动整个 ZooKeeper 数据目录。7.3 运维视角高可用不等于数据零丢失文件句柄和日志习惯要养成最后说一个认知问题Hadoop HA 解决的是“服务可用性”不等同于“数据绝对安全”。当 Active NameNode 和 JournalNode 之间发生网络分区时为了一致性系统会放弃继续写入并等待多数派恢复。真正关键的数据还是要靠定期快照、HDFS 数据迁移、异地备份来兜底。不少同学装完 HA 就以为万事大吉其实 HA 只是把单点故障的发生概率降下来并没有改变数据持久化的物理风险。生产环境的文件句柄限制也要提前调大。NameNode 和 DataNode 都是高并发 IO 的程序默认ulimit经常不够用数据量一上来DataNode 可能突然被判定为“心跳超时”表面看像网络波动实际执行ulimit -n一看才发现是文件句柄满了。把limits.conf里的 nofile 调高比在集群卡死之后抓耳挠腮强得多。最后分享一个个人体会。我搭这套 Hadoop HA 集群时最大的教训是配置项永远可以照着文档抄但启动顺序和故障时的日志阅读能力只能靠一次一次踩坑练出来。如果你真的想把这套架构掌握扎实建议装完之后别急着宣布成功主动做一次故障切换实验看一遍完整的切换日志。一次真实的自动挡切换比读十篇安装教程都有用。希望这篇内容能帮你把 ZooKeeper、JournalNode、ZKFC 这条主线理清楚让高可用集群真正成为可信赖的基础设施而不是一个跑起来就算数的玩具。