☰
Hadoop高可用配置实战:ZooKeeper与ZKFC自动故障转移详解
2026/10/21 16:30:08 网站建设 项目流程

1. 为什么高可用这件事值得单独写一篇配置说明

做大数据平台运维的人,迟早会碰到一个绕不开的坎:单点故障。NameNode 挂了整个集群就瘫了,ResourceManager 挂了所有作业都提不上去。早期搭测试集群的时候,大家习惯性地把什么服务都往一台机器上塞,跑得好好的,一旦上了生产环境,半夜一个进程崩了,电话就被打爆。Hadoop 的高可用方案就是在这种血泪教训里被逼出来的,而 ZooKeeper 在其中扮演的角色,说白了就是那个“裁判”——它不干活,但它决定谁来干活。

这篇内容主要面向已经搭好基础 Hadoop 集群、准备从单点模式切换到 HA 模式的中级运维人员,也适合刚接触大数据平台、想搞清楚 HA 到底改了哪些配置的初学者。我会把 ZooKeeper 高可用配置文件的修改逻辑、每个参数背后的含义、启动顺序以及踩过的坑,从头到尾捋一遍。不是照搬官方文档那种“改这几个值就行”的写法,而是把为什么改、改了会怎样、不改会出什么问题都讲清楚。

整个方案的核心思路其实不复杂:用 ZooKeeper 做分布式协调,让多个 NameNode 通过 ZK 选举决定谁是 Active、谁是 Standby;JournalNode 负责在 Active 和 Standby 之间同步元数据编辑日志;ZKFC 进程监控 NameNode 健康状态,出问题时触发自动切换。听起来简单,但配置文件里每一个参数写错一个字母,整个 HA 就起不来,而且报错信息往往含糊得让人抓狂。

2. 高可用架构的核心组件与配置逻辑拆解

2.1 ZooKeeper 在 HA 中的真实角色

很多人第一次接触 Hadoop HA 的时候会有一个误解,觉得 ZooKeeper 是“存储数据”的。其实 ZooKeeper 在 Hadoop HA 里只做三件事:维护 Active NameNode 的锁、存储选举信息、以及在故障时协调切换。它不存 HDFS 的文件数据,也不存元数据镜像,这些活是 JournalNode 和 NameNode 自己干的。

打个比方,ZooKeeper 就像一个小区的物业前台,它手里有一把“唯一钥匙”,谁拿到这把钥匙谁就是 Active。当 Active 那台机器出问题了,钥匙会被释放,Standby 那边通过 ZKFC 监控到这个变化,就会去抢钥匙,抢到了就变成新的 Active。整个过程不需要人工干预,这就是自动故障转移的核心机制。

理解了这个角色定位,你就能明白为什么 ZooKeeper 集群本身必须是奇数节点——3 个、5 个、7 个。因为 ZK 内部用 ZAB 协议做一致性保证,需要过半节点存活才能对外提供服务。3 个节点允许挂 1 个,5 个允许挂 2 个,奇数节点在容错能力相同的情况下成本最低。

2.2 配置文件全景:到底要改哪几个文件

Hadoop HA 涉及的配置文件不算多,但每个文件里的改动点比较分散,容易漏。我整理了一个清单,按文件维度列出来:

配置文件核心改动内容作用范围
core-site.xmlfs.defaultFS 指向 HA 逻辑名称全局
hdfs-site.xmlnameservice、NameNode ID、JournalNode、ZKFC 相关HDFS
yarn-site.xmlResourceManager HA 配置YARN
zoo.cfgZK 集群节点列表、数据目录、端口ZooKeeper
hadoop-env.sh日志目录、JVM 参数全局

这里有个经验:改配置之前一定要备份原始文件。我见过太多次改错了想回滚,结果发现没备份,只能凭记忆一个个改回来。备份命令很简单:

cp core-site.xml core-site.xml.bak cp hdfs-site.xml hdfs-site.xml.bak cp yarn-site.xml yarn-site.xml.bak

别嫌麻烦,这个习惯能救命。

2.3 命名服务(nameservice)的设计考量

HA 模式下,HDFS 不再用单台机器的地址来标识,而是引入了一个逻辑名称叫 nameservice。你可以把它理解成一个“域名”,客户端访问的时候用这个名字,具体连到哪台 NameNode 由配置决定。

为什么这么设计?因为如果客户端直接写死某台 NameNode 的 IP,那台挂了客户端就废了。用逻辑名称之后,客户端通过 nameservice 去找当前 Active 的节点,切换对客户端透明。这个设计思路和 DNS 很像,你访问网站用的是域名,具体解析到哪台服务器你不用管。

nameservice 的命名没有强制规范,但建议用有意义的名字,比如mycluster或者production-ns。不要用ns1这种,过两个月你自己都不记得是哪个集群了。

3. 逐文件配置修改实操与参数详解

3.1 core-site.xml:入口地址的切换

这是最基础也是最重要的一步。单点模式下fs.defaultFS写的是hdfs://主机名:8020,HA 模式下要改成hdfs://nameservice的形式。

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <property> <name>ha.zookeeper.quorum</name> <value>zk-node1:2181,zk-node2:2181,zk-node3:2181</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> </configuration>

ha.zookeeper.quorum这个参数是 ZKFC 用来连接 ZooKeeper 集群的地址列表。注意这里写的是 ZK 的客户端端口 2181,不是 ZK 集群内部通信的端口。三个节点用逗号分隔,不要有空格,我见过因为多了个空格导致 ZKFC 启动失败的案例。

hadoop.tmp.dir建议指向一个独立的、空间充足的磁盘目录。默认值在/tmp下面,很多系统重启会清理/tmp,导致集群数据丢失。这个坑我踩过一次,重启服务器之后整个集群起不来,排查了半天才发现是临时目录被清了。

3.2 hdfs-site.xml:HA 配置的主战场

这个文件是改动最多的地方,我分几块来说。

第一块:nameservice 与 NameNode 定义

<property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>nn-node1:8020</value> </property> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>nn-node2:8020</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>nn-node1:9870</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>nn-node2:9870</value> </property>

dfs.ha.namenodes.mycluster定义了这个 nameservice 下有哪些 NameNode,名字可以自定义,但后面所有引用都要保持一致。RPC 地址是给客户端和 DataNode 用的,HTTP 地址是给 Web UI 用的。端口 8020 和 9870 是 Hadoop 3.x 的默认值,2.x 版本 RPC 端口通常是 8020,HTTP 端口是 50070,这个要注意区分。

第二块:JournalNode 配置

<property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://jn-node1:8485;jn-node2:8485;jn-node3:8485/mycluster</value> </property> <property> <name>dfs.journalnode.edits.dir</name> <value>/data/hadoop/journal</value> </property>

JournalNode 至少需要 3 个节点,因为它的写入也遵循过半确认机制。qjournal://后面的格式是主机:端口用分号分隔,最后跟 nameservice 名称。dfs.journalnode.edits.dir是 JournalNode 存放编辑日志的本地目录,这个目录一定要用独立磁盘,因为写入非常频繁。

第三块:故障转移代理配置

<property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/home/hadoop/.ssh/id_rsa</value> </property>

ConfiguredFailoverProxyProvider是客户端侧的故障转移实现类,基本不用改。dfs.ha.fencing.methods定义的是隔离机制,当需要切换时,如何确保旧的 Active 真的已经停止服务。sshfence是最常用的方式,通过 SSH 登录到旧 Active 节点把它杀掉。ssh.private-key-files指定免密登录的私钥路径。

这里有个关键点:sshfence 要求 NameNode 节点之间能够互相免密 SSH 登录。如果没配好,切换的时候会卡住,日志里会报unable to fence之类的错误。配置免密登录的步骤:

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa ssh-copy-id nn-node1 ssh-copy-id nn-node2

第四块:自动故障转移开关

<property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property>

这个参数设为 true 之后,ZKFC 才会真正起作用。如果设为 false,即使 ZKFC 进程起来了也不会自动切换,需要手动执行hdfs haadmin -failover命令。

3.3 yarn-site.xml:ResourceManager HA 配置

YARN 的 HA 配置相对简单一些,核心是 ResourceManager 的 ID 定义和 ZK 地址。

<property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <property> <name>yarn.resourcemanager.cluster-id</name> <value>mycluster-rm</value> </property> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>rm-node1</value> </property> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>rm-node2</value> </property> <property> <name>yarn.resourcemanager.zk-address</name> <value>zk-node1:2181,zk-node2:2181,zk-node3:2181</value> </property>

yarn.resourcemanager.cluster-id是 YARN 集群的唯一标识,在 ZK 里用来区分不同的集群。如果你有多个 YARN 集群共用一套 ZK,这个 ID 绝对不能重复,否则会出现一个集群的 RM 抢另一个集群的锁的诡异情况。

3.4 zoo.cfg:ZK 集群自身的配置

ZooKeeper 的配置文件通常放在$ZK_HOME/conf/zoo.cfg,内容如下:

tickTime=2000 initLimit=10 syncLimit=5 dataDir=/data/zookeeper/data dataLogDir=/data/zookeeper/logs clientPort=2181 server.1=zk-node1:2888:3888 server.2=zk-node2:2888:3888 server.3=zk-node3:2888:3888

tickTime是 ZK 的基本时间单位,2000 毫秒。initLimit是 follower 与 leader 初始连接时能容忍的最多 tick 数,10 表示 20 秒。syncLimit是 follower 与 leader 之间心跳超时时间,5 表示 10 秒。

server.N中的 N 是 ZK 节点的唯一 ID,需要在每个节点的dataDir下创建一个myid文件,内容就是对应的数字。比如 zk-node1 上myid文件内容就是1。这个文件没有扩展名,内容就是一个纯数字,写错了 ZK 起不来。

echo "1" > /data/zookeeper/data/myid

2888是 follower 连接 leader 的端口,3888是选举端口。这两个端口不要和别的服务冲突,我遇到过 3888 被其他进程占用导致 ZK 集群无法选举的情况,排查起来很费劲。

4. 启动顺序与初始化流程

4.1 启动顺序为什么不能乱

Hadoop HA 集群的启动有严格的依赖关系:ZooKeeper 必须先起来,因为 ZKFC 依赖它;JournalNode 必须在 NameNode 之前起来,因为 NameNode 启动时要连接 JournalNode 加载编辑日志;NameNode 格式化必须在 JournalNode 启动之后。

正确的启动顺序是:

  1. 启动 ZooKeeper 集群(所有 ZK 节点)
  2. 启动 JournalNode(所有 JN 节点)
  3. 格式化 NameNode(首次搭建时执行一次)
  4. 启动 NameNode(先启动一个,再启动另一个)
  5. 启动 ZKFC(在每个 NameNode 节点上)
  6. 启动 DataNode(所有 DN 节点)
  7. 启动 ResourceManager 和 NodeManager

这个顺序不是随便定的,每一步都有依赖。比如你先启动 NameNode 再启动 JournalNode,NameNode 会因为连不上 JournalNode 而进入安全模式或者直接启动失败。

4.2 ZooKeeper 集群启动与验证

在每个 ZK 节点上执行:

zkServer.sh start

启动后验证状态:

zkServer.sh status

输出会显示当前节点是 leader 还是 follower。三个节点中应该有一个 leader、两个 follower。如果全是 follower 或者全是 leader,说明选举出了问题,通常是 myid 配置错误或者端口被占用。

用zkCli.sh连接进去测试一下:

zkCli.sh -server zk-node1:2181

进入交互界面后执行ls /能看到 znode 列表就说明 ZK 正常工作了。

4.3 JournalNode 启动

在每个 JN 节点上执行:

hdfs --daemon start journalnode

验证方式很简单,看进程在不在:

jps | grep JournalNode

另外可以检查dfs.journalnode.edits.dir目录下是否生成了current子目录,有的话说明 JN 正常初始化了。

4.4 NameNode 格式化与启动

首次搭建时,需要在其中一个 NameNode 上执行格式化:

hdfs namenode -format -clusterId mycluster

-clusterId参数指定集群 ID,两个 NameNode 必须使用相同的 clusterId,否则无法组成 HA 对。格式化完成后,启动这个 NameNode:

hdfs --daemon start namenode

然后在另一个 NameNode 上执行同步:

hdfs namenode -bootstrapStandby

这个命令会从 Active NameNode 或者 JournalNode 同步元数据到本地。执行成功后会提示BootstrapStandby相关的成功信息。同步完成后启动第二个 NameNode:

hdfs --daemon start namenode

4.5 ZKFC 启动与自动切换验证

在两个 NameNode 节点上分别启动 ZKFC:

hdfs --daemon start zkfc

启动后,用以下命令查看 NameNode 状态:

hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2

正常情况下一个显示active,一个显示standby。如果两个都显示standby,说明 ZKFC 没有成功选举,检查 ZK 连接和ha.zookeeper.quorum配置。

手动触发一次切换来验证自动故障转移是否工作:

hdfs haadmin -failover nn1 nn2

这个命令会强制把 nn1 切到 standby,nn2 切到 active。如果切换成功,说明 fencing 机制和 ZKFC 都配置正确了。

5. 常见问题排查与避坑经验

5.1 启动失败类问题速查

现象可能原因排查方向
NameNode 启动报 “Cannot assign requested address”RPC 地址配置的主机名解析不了检查 /etc/hosts 和 DNS
ZKFC 启动后立即退出ZK 连接失败或 quorum 配置错误检查 ha.zookeeper.quorum 和 ZK 状态
JournalNode 启动后 NameNode 仍报无法写入edits.dir 权限问题检查目录权限和磁盘空间
两个 NameNode 都是 standbyZKFC 未启动或 ZK 中无 active 锁检查 ZKFC 进程和 ZK 中 /hadoop-ha 节点
切换时卡在 fencingSSH 免密未配置或私钥路径错误手动测试 ssh 免密登录

5.2 那些文档里不会写的坑

坑一:主机名解析问题。Hadoop 配置里大量使用主机名而不是 IP,如果/etc/hosts没配好,或者 DNS 解析不稳定,会出现各种莫名其妙的连接超时。我的建议是在所有节点的/etc/hosts里把集群所有机器的主机名和 IP 都写死,不依赖 DNS。

坑二:端口冲突。JournalNode 的 8485 端口、ZK 的 2181/2888/3888 端口、NameNode 的 8020/9870 端口,这些如果在同一台机器上跑多个服务,很容易冲突。启动前用netstat -tlnp | grep 端口号检查一下。

坑三:格式化次数。NameNode 格式化只能执行一次,重复格式化会导致 clusterId 不一致,两个 NameNode 无法组成 HA 对。如果不小心格式化了多次,需要把两个 NameNode 的数据目录都清空重新来。这个操作会丢失所有 HDFS 数据,生产环境一定要慎重。

坑四:时间同步。ZooKeeper 和 Hadoop 对节点间时间差有要求,如果机器时间差太大,会出现各种心跳超时和选举异常。建议所有节点配置 NTP 时间同步,时间差控制在 1 秒以内。

坑五:日志目录权限。ZKFC 和 NameNode 写日志的目录如果权限不对,进程启动时会报权限拒绝然后退出。确保hadoop-env.sh里配置的HADOOP_LOG_DIR目录存在且对运行用户可写。

5.3 日常运维中的检查清单

集群跑起来之后,日常巡检建议关注这几个点:

  • ZooKeeper 集群是否保持奇数节点存活,zkServer.sh status确认 leader 存在
  • JournalNode 的 edits 目录磁盘使用率,超过 80% 要清理或扩容
  • NameNode 的堆内存使用情况,频繁 Full GC 会影响切换速度
  • ZKFC 日志中是否有 fencing 失败记录
  • 两个 NameNode 的元数据是否同步,可以通过对比fsimage时间戳来检查

6. 一些实操后的个人体会

这套 HA 配置我前后搭过不下十次,每次都会遇到不一样的问题。最开始的时候觉得配置项太多太杂,后来慢慢理解了每个参数背后的设计意图,改起来就有底了。ZooKeeper 的 quorum 配置、JournalNode 的过半写入、ZKFC 的 fencing 机制,这三个东西构成了 Hadoop HA 的基石,任何一个环节出问题都会导致切换失败。

我个人的建议是,第一次搭建的时候不要急着上生产,先在测试环境把整个流程走通,包括手动触发切换、模拟 NameNode 宕机、模拟 ZK 节点故障这些场景都验证一遍。只有亲眼看到自动切换成功,你才能对这套机制有信心。

另外,配置文件的版本管理很重要。每次修改之前 commit 一下,出问题了可以快速 diff 出改了什么。我现在的习惯是用一个简单的 git 仓库管理所有节点的配置文件,改完 push,其他节点 pull 更新,比手动 scp 靠谱得多。

最后说一个细节:dfs.ha.fencing.methods除了sshfence还有shell方式,可以自定义脚本。如果环境里 SSH 免密不好配,可以考虑用shell加一个简单的 kill 脚本作为兜底。但sshfence始终是最推荐的方式,因为它能确保旧 Active 真的被隔离了,避免脑裂问题。脑裂在 HA 集群里是最危险的情况,两个 NameNode 同时认为自己是 Active,会导致数据不一致甚至丢失,所以 fencing 机制绝对不能省。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询