ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Hadoop NameNode与ResourceManager高可用集群搭建

Hadoop NameNode与ResourceManager高可用集群搭建

兄弟们,如果你正在折腾Hadoop完全分布式集群,或者被NameNode单点故障搞得夜不能寐,这篇就是写给你的。这个教程聚焦Hadoop 3.1.4版本,目标只有一个:把NameNode和ResourceManager的高可用(HA)机制一次讲透,并把每一步操作都摆在你面前。什么叫保姆级?就是不看任何其他资料,照着这篇教程从一个干净的Linux环境起步,也能把集群搭起来、跑起来、看到自动切换效果。

这套配置我前前后后搭了不下十次,最早用的是2.x版本,后来切到3.1.4,踩过的坑比写出来的多得多。这篇教程里我不会只贴配置参数,每个关键配置为什么要这么写、启动顺序为什么不能乱、哪一步漏了会导致什么诡异现象,都会交代清楚。

1. 整体方案设计与集群规划

1.1 先想明白HA到底解决什么问题

Hadoop 2.x之前,NameNode是整个HDFS的“大脑”,它挂了整个集群就瘫痪了。虽然SecondaryNameNode能定期合并元数据,但它本质上是checkpoint工具,做不到秒级故障转移。HA机制的引入,就是让两个NameNode一主一备、共享元数据、自动切换。3.x版本里这套机制已经很成熟,ResourceManager也走上了同样的HA道路,避免YARN调度器单点挂掉导致计算任务全部停摆。

按照标题里的“namenode resourcemanager高可用”,我们实际要部署两套HA机制:

  • HDFS HA:双NameNode + JournalNode集群(用于同步edit log)+ ZKFC(自动故障转移控制器)
  • YARN HA:双ResourceManager + ZooKeeper(用于状态存储与领导者选举)

这两套都用ZooKeeper做协调。所以整个集群的底座是先搭好一个ZooKeeper集群,其他组件才能有“主心骨”。

1.2 节点角色规划,用3台还是5台?

我的建议是:首次练习用3台虚拟机完全够了,而且能看清HA的完整逻辑。5台当然更接近生产,但节点越多排查问题越麻烦,新手容易在“哪台机器上执行什么命令”这件事上绕晕。

3台机器方案如下:

节点角色
node01NameNode(Active) + ResourceManager(Active) + JournalNode + ZooKeeper + ZKFC
node02NameNode(Standby) + ResourceManager(Standby) + JournalNode + ZooKeeper + ZKFC
node03DataNode + NodeManager + JournalNode + ZooKeeper

但注意,如果只有3台且namenode和resourcemanager都分散在两台节点上,那剩下node03的复制因子就只能是2。如果测试环境有数据冗余需求,建议至少4台:node01、node02放NameNode和RM,node03、node04放DataNode。这里我按照3台标准方式来写,如果你机器不够,把DataNode分配到已有节点上就行,不影响配置逻辑。

1.3 版本选型:为什么卡在3.1.4

Hadoop 3.1.4是3.x系列里比较稳的一个版本。3.2.0出来以后有段时间社区反馈了一堆兼容性问题,而3.1.4既能完整支持JDK8、ZooKeeper 3.4.x/3.5.x,又能享受3.x的架构优化(比如HDFS支持纠删码、YARN Timeline Service改进)。对绝大多数学习、企业内部测试环境来说,3.1.4是“闭眼不会出错”的选择。

1.4 环境准备清单

  • 操作系统:CentOS 7.6及以上(或RHEL系),我这边统一用最小化安装
  • JDK版本:1.8(不要用11,部分Hadoop 3.1.4组件对JDK11有兼容警告)
  • ZooKeeper版本:3.4.14或3.5.7均可,本教程用3.4.14,最稳
  • 内存建议:每台至少2G以上,能上4G更好。三台加起来6~12G,搭一个HA集群绰绰有余
  • 网络:三台机器互通,关闭防火墙或者放通指定端口,后面会细说

2. 基础环境配置与集群互信

2.1 hosts映射与主机名,这一步马虎后面全是泪

很多教程里把hosts配置一笔带过,我吃过亏:曾因为hosts只配了IP和主机名、没有把三台机器都写全,导致NameNode在格式化时报“Unable to resolve hostname”,折腾了一下午。建议直接把下面内容写进每台机器的 /etc/hosts:

192.168.80.101 node01 192.168.80.102 node02 192.168.80.103 node03

同时确保每台机器的主机名是唯一的,分别执行 hostnamectl set-hostname node01/node02/node03。改完之后一定要reboot或者重新登录终端,再验证hostname是否生效。这一步不验证就往下走,后面所有节点间通信都会出问题。

2.2 SSH免密登录,一次性解决

HA集群里NameNode、ZKFC之间要频繁交互,不可能每次操作都输入密码。三台机器之间建议配置双向免密,而不是只配单向。

我推荐直接循环执行ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa生成密钥,然后把所有公钥汇总到 authorized_keys 中。最简单的方式是:

ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa ssh-copy-id node01 ssh-copy-id node02 ssh-copy-id node03

三台机器都执行一遍,然后互相 ssh node01/node02/node03 验证。如果哪台机器要求输入密码,多半是权限问题,检查 ~/.ssh 目录权限是否为700、authorized_keys文件权限是否为600。

2.3 JDK安装与环境变量

Hadoop本身是用Java写的,所以JDK是所有组件的运行基础。这里我建议直接使用OpenJDK 1.8,装好之后统一配置JAVA_HOME,否则后面启动脚本会报找不到Java。

以每台机器都安装为例:

tar -zxvf jdk-8u202-linux-x64.tar.gz -C /usr/local/ cd /usr/local/ mv jdk1.8.0_202 jdk8

然后在 /etc/profile 里加上:

export JAVA_HOME=/usr/local/jdk8 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

注意一点:Hadoop的start-dfs.sh脚本在找JAVA_HOME的时候,读取的未必是系统的环境变量,建议到hadoop-env.sh文件里也显式再加一次JAVA_HOME,防止su切换用户或脚本子进程拿不到变量。

2.4 时钟同步(NTP)与防火墙

集群节点之间的时间差异直接影响ZooKeeper的投票机制和NameNode的租约判断。ZooKeeper对时间非常敏感,两个节点时间差太大会导致leader频繁切换。最简单的方案是让所有节点都跟一台时间服务器同步:

yum install -y ntpdate ntpdate -u pool.ntp.org

然后把同步命令写到crontab里,每小时同步一次。防火墙方面,既然是自己搭集群,最省事的方式是直接清空规则:

systemctl stop firewalld systemctl disable firewalld

生产环境不建议这么干,但如果是测试环境、学习环境,防火墙会挡掉大部分“莫名其妙连不上”的问题。尤其JournalNode要监听多个端口,navicat式的逐条放行太费劲。等你在生产环境有了安全要求,再回来精细化放行也不迟。

3. ZooKeeper集群搭建:HA的地基

3.1 为什么HA必须要有ZooKeeper

一句话概括:ZooKeeper就是HA的大脑。NameNode和ResourceManager都通过ZooKeeper来进行领导者选举、故障感知和状态存储。两个NameNode谁是Active,主要不是靠“互相ping”来决定的,而是靠ZK的临时节点来做领导权仲裁。ZooKeeper挂掉,整个HA就失去协调能力,所以它的优先级最高。

3.2 下载安装与配置文件

将zookeeper-3.4.14.tar.gz解压到/usr/local/zookeeper,然后进入conf目录,复制一份zoo_sample.cfg为zoo.cfg,内容如下:

tickTime=2000 initLimit=10 syncLimit=5 dataDir=/var/data/zookeeper clientPort=2181 server.1=node01:2888:3888 server.2=node02:2888:3888 server.3=node03:2888:3888

三个端口要理解清楚:2181是客户端连接端口,2888是集群内部followers连leader的端口,3888用于leader选举投票。很多人配置时只写了IP和2181,结果集群死活起不来,就是漏了后两个端口。

在每台机器的dataDir目录下创建myid文件,node01写1,node02写2,node03写3:

mkdir -p /var/data/zookeeper echo 1 > /var/data/zookeeper/myid

然后分发到三台机器,确保myid编号对应hosts里的server.X编号。

3.3 启动验证

三台机器各自执行:

/usr/local/zookeeper/bin/zkServer.sh start /usr/local/zookeeper/bin/zkServer.sh status

status出现leader或follower就是正常的。如果三台全是follower或者全部报错,优先检查myid是否配置错误、hosts映射是否生效。一个快速排查方法是执行/usr/local/zookeeper/bin/zkCli.sh -server node01:2181,能连上就说明2181端口和ZK服务本身没问题。

ZooKeeper启动顺序没有严格讲究,但建议等一台确定成为leader后再启动其他节点,以免脑裂。我第一次搭的时候三台同时启动,结果某台节点反复重试连接,日志刷屏。其实很正常,等几十秒让它选举完就好。

4. HDFS高可用配置详解

4.1 核心设计:两个NameNode怎么共享元数据

HDFS的NameNode把元数据分为两部分:fsimage(文件系统镜像)和edits log(编辑日志)。HA的核心就是把edits log实时同步到多个JournalNode上,两台NameNode都去读写同一份edits log。Active NameNode写入,Standby NameNode读取并持续合并到内存,这样就能保证两个节点的元数据几乎实时一致。

JournalNode在这里的地位等同于“日志中心”。它的角色有点像数据库的主从复制里的binlog服务器,所有写操作先落JournalNode,再同步到Standby节点。所以JournalNode至少要有奇数个(通常是3个),这样即使挂了一个,剩下的也能正常仲裁数据一致性。

4.2 core-site.xml核心配置

进入hadoop-3.1.4/etc/hadoop目录,编辑core-site.xml。

<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> <property> <name>ha.zookeeper.quorum</name> <value>node01:2181,node02:2181,node03:2181</value> </property> </configuration>

fs.defaultFS这一项是整个HDFS访问入口。配置成逻辑名称mycluster,客户端访问时不直接写具体节点,而是交给HA框架自动找到Active NameNode。hadoop.tmp.dir指定临时文件的根目录,NameNode的元数据、DataNode的数据块存放路径都基于这里。ha.zookeeper.quorum告诉HDFS“去哪个ZK集群协调领导权”。

4.3 hdfs-site.xml最完整的配置逐行解释

这是整个搭建过程中的重中之重。先看完整配置,再看我逐个参数讲为什么:

<configuration> <!-- 集群逻辑名称 --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!-- 定义有多少个NameNode --> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <!-- nn1的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node01:8020</value> </property> <!-- nn1的HTTP页面访问地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>node01:9870</value> </property> <!-- nn2的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>node02:8020</value> </property> <!-- nn2的HTTP页面访问地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>node02:9870</value> </property> <!-- JournalNode地址,两个NameNode都从这里同步edits log --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://node01:8485;node02:8485;node03:8485/mycluster</value> </property> <!-- 客户端failover自动切换实现类 --> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <!-- 开启自动故障转移 --> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> <!-- JournalNode存放edit log的本地目录 --> <property> <name>dfs.journalnode.edits.dir</name> <value>/data/hadoop/journal</value> </property> <!-- fencing隔离方式 --> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <!-- sshfence需要免密登录 --> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/root/.ssh/id_rsa</value> </property> <!-- 默认副本数 --> <property> <name>dfs.replication</name> <value>2</value> </property> <!-- NameNode元数据本地存储目录 --> <property> <name>dfs.namenode.name.dir</name> <value>/data/hadoop/hdfs/name</value> </property> <!-- DataNode数据块存储目录 --> <property> <name>dfs.datanode.data.dir</name> <value>/data/hadoop/hdfs/data</value> </property> <!-- 关闭权限检查,学习环境更方便 --> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> </configuration>

几个关键参数背后有门道:

  • dfs.nameservices和dfs.ha.namenodes.mycluster:这两组名字必须完全一致,我用mycluster+n1/nn2是习惯,你也可以换别的,但所有配置里的字符串要统一。如果你这里写的是ha-cluster,下面三个地方也得跟着改,少改一处就会连不上。
  • dfs.namenode.http-address:3.x版本NameNode的web端口从50070改成了9870,如果你参考的是2.x教程,注意别抄错。端口不匹配的话,页面看着是起来了,但访问不到。
  • dfs.namenode.shared.edits.dir:这里用了qjournal协议,三个JournalNode地址用分号分隔,不能写错一个。有个小坑是地址里最后跟的“/mycluster”必须和dfs.nameservices一致,否则启动JournalNode时报无法创建日志目录。
  • dfs.ha.fencing.methods:这个理解起来稍微绕一点。当一台NameNode从Active变成Standby时,为了怕它“不死心”,需要有个暴力隔离手段,确保同一时刻只有一台NameNode能写数据。sshfence就是通过SSH远程执行fuser命令杀掉对方进程中真正的NameNode,从而防止双Active脑裂。
  • dfs.replication设为2,因为三台节点里如果一台是NameNode,实际存数据的DataNode可能只有两台,副本数为3会一直报块丢失。

4.4 别忘了完成一个关键动作:直接把配置分发到所有节点

在node01上配置好core-site.xml和hdfs-site.xml后,用scp把这两个文件、以及之后要配置的yarn-site.xml、mapred-site.xml等同步到所有节点。一个偷懒的方法是直接同步整个etc/hadoop目录:

cd /usr/local/hadoop-3.1.4/etc scp -r hadoop root@node02:/usr/local/hadoop-3.1.4/etc/ scp -r hadoop root@node03:/usr/local/hadoop-3.1.4/etc/

注意一个常见错误:有人只同步了core-site.xml,忘了hdfs-site.xml,结果node02启动NameNode时报找不到逻辑名称mycluster。这俩文件是配套的,必须一起过去。

5. YARN高可用配置详解

5.1 ResourceManager为什么要配HA

NameNode解决了HDFS的元数据单点,但YARN的ResourceManager如果挂了,新的计算任务提交不了,现有的任务也全部中断。生产环境里RM的可用性和NameNode一样重要。好在Hadoop 3.x的YARN HA设计得很完整,Active RM把运行时状态(如已提交的application)写到ZooKeeper,Standby RM接管后能从ZK恢复状态。

有人会问:YARN HA用的是ZooKeeper,那为什么不直接用ZK里的临时节点来选举?实际是用ZK做协调,但RM状态存储走的是ZK中专门路径。你只需要知道一点:YARN HA要求ZooKeeper集群必须正常运行,否则两边RM都会变成standby,集群无法运行新任务。

5.2 yarn-site.xml逐项配置

<configuration> <!-- 开启RM HA --> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <!-- 集群ID,同个HA组内保持一致 --> <property> <name>yarn.resourcemanager.cluster-id</name> <value>yarncluster</value> </property> <!-- 两个RM的逻辑ID --> <property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property> <!-- rm1的地址 --> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>node01</value> </property> <!-- rm2的地址 --> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>node02</value> </property> <!-- RM web应用访问地址 --> <property> <name>yarn.resourcemanager.webapp.address.rm1</name> <value>node01:8088</value> </property> <property> <name>yarn.resourcemanager.webapp.address.rm2</name> <value>node02:8088</value> </property> <!-- ZooKeeper地址列表 --> <property> <name>yarn.resourcemanager.zk-address</name> <value>node01:2181,node02:2181,node03:2181</value> </property> <!-- 开启RM状态恢复 --> <property> <name>yarn.resourcemanager.recovery.enabled</name> <value>true</value> </property> <!-- 状态存储的类,这里配置为ZK --> <property> <name>yarn.resourcemanager.store.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value> </property> <!-- NodeManager附属服务,shuffle服务给MapReduce用 --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name> <value>org.apache.hadoop.mapred.ShuffleHandler</value> </property> <!-- 日志聚合,方便查看作业日志 --> <property> <name>yarn.log-aggregation-enable</name> <value>true</value> </property> <property> <name>yarn.log.server.url</name> <value>http://node01:19888/jobhistory/logs</value> </property> </configuration>

几个容易错的地方重点说一下:

  • yarn.resourcemanager.hostname.rm1和rm2:注意这个参数只填主机名,不要带端口。有些人习惯写成node01:18080,反而导致RM起不来。
  • yarn.resourcemanager.store.class必须显式配置,默认是不启用状态恢复的。如果漏了这行,RM切换后所有application信息丢失,表现就是任务列表为空、提交任务异常。
  • yarn.log-aggregation-enable开启后,Container的日志会集中保存到HDFS,历史服务器的日志url要配合yarn.log.server.url,后面日志查看才能正常跳转。

5.3 mapred-site.xml:历史服务器准备工作

历史服务器(JobHistoryServer)用来记录和查询已经跑完的MapReduce作业。它不在HA体系里,但一个完整的集群搭建必然要配上它,否则作业结束之后想回头分析日志和指标就无从着手。

<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <property> <name>mapreduce.jobhistory.address</name> <value>node01:10020</value> </property> <property> <name>mapreduce.jobhistory.webapp.address</name> <value>node01:19888</value> </property> <property> <name>mapreduce.jobhistory.intermediate-done-dir</name> <value>/data/hadoop/history/mapreduce/intermediate</value> </property> <property> <name>mapreduce.jobhistory.done-dir</name> <value>/data/hadoop/history/mapreduce/done</value> </property> </configuration>

这里有个细节:历史服务器的两个目录不能提前手建,NameNode格式化并启动HDFS后,会自动创建。若手动创建在本地文件系统里,历史服务器反而找不到路径。我第一次就犯了这个错,手动建了本地路径,然后历史服务器一直报目录不存在,后来把这两个目录删掉让它自动生成就正常了。

6. 启动全流程,保姆级命令执行手册

6.1 启动顺序为什么不能乱

集群启动的顺序对整个搭建过程至关重要:ZooKeeper必须最先启动,然后JournalNode,接着格式化NameNode,最后才是HDFS和YARN。这个顺序里如果JournalNode没有启动就去初始化NameNode,格式化过程无法写入共享edits log目录,会报连接失败。

下面是三台节点分步执行的具体命令,按顺序执行:

第一步:在三台节点分别启动ZooKeeper

/usr/local/zookeeper/bin/zkServer.sh start

第二步:在三台节点分别启动JournalNode

注意这里不能直接用start-dfs.sh里的自动启动journalnode方式,要手动在每台节点单独执行:

/usr/local/hadoop-3.1.4/sbin/hadoop-daemon.sh start journalnode

启动后验证journalnode进程是否存活:

jps

应该能看到JournalNode进程。如果jps里没有,多半是配置写错或者端口被占,直接去logs目录看hadoop-root-journalnode-*.log日志。

第三步:在node01上格式化NameNode

hdfs namenode -format

看到 “successfully formatted” 就是成功。格式化会清空namenode目录下所有元数据,执行一次就好,除非集群废弃重建,否则不要重复执行——这一点我要特别强调,很多人遇到NameNode启动失败后反复格式化,结果元数据错乱,整个集群只能推倒重来。

第四步:初始化ZKFC

在node01上执行:

hdfs zkfc -formatZK

这个命令会在ZooKeeper中创建HA相关的路径和临时节点。如果没执行这一步,后面即使两个NameNode启动成功,ZKFC也无法参与leader选举,Active/Standby状态永远起不来。

第五步:把node01格式化产生的元数据同步到node02

在node02执行(注意:不能直接scp目录,而是用hdfs命令同步):

hdfs namenode -bootstrapStandby

这个命令会自动从node01拷贝fsimage和edits log。没有这一步,node02启动后会认为自己的元数据不完整,报错“There appears to be a gap”。

第六步:在node01上启动HDFS

/usr/local/hadoop-3.1.4/sbin/start-dfs.sh

脚本会读取 slaves(或workers)文件,把DataNode启动到对应节点,并把ZKFC自动带起来。启动完成后,在node01和node02分别执行jps查看,应该能看到NameNode、ZKFC进程,node03上看到DataNode。

第七步:在node01上启动YARN

/usr/local/hadoop-3.1.4/sbin/start-yarn.sh

脚本会把ResourceManager和NodeManager启起来。注意这个脚本默认只在执行命令的机器上启动RM,所以node02上的RM需要手动启动:

/usr/local/hadoop-3.1.4/sbin/yarn-daemon.sh start resourcemanager

第八步:启动历史服务器

在node01执行:

/usr/local/hadoop-3.1.4/sbin/mr-jobhistory-daemon.sh start historyserver

6.2 启动后必须做的状态检查

启动不是跑完命令就完事了,验证状态比启动本身更重要。检查分三层:

第一层,进程层面:

jps
  • node01:NameNode、ResourceManager、ZKFC、JournalNode、QuorumPeerMain
  • node02:NameNode、ResourceManager、ZKFC、JournalNode、QuorumPeerMain
  • node03:DataNode、NodeManager、JournalNode、QuorumPeerMain

第二层,高可用状态层面:

hdfs haadmin -getAllServiceState

正常输出应该是:

node01:8020 active node02:8020 standby

如果两边都是standby,说明ZKFC没有正常工作或者没有连上ZK;如果两边都是active,说明fencing机制没生效,这属于脑裂风险,必须马上排查。

第三层,ResourceManager状态:

yarn rmadmin -getAllServiceState

正常输出:

node01:8030 active node02:8030 standby

6.3 界面验证

  • NameNode Web界面:分别访问 http://node01:9870 和 http://node02:9870,active节点的页面顶部会显示“Status: Active”,standby节点显示“Status: Standby”
  • YARN Web界面:访问 http://node01:8088 和 http://node02:8088,同样能区分active/standby
  • 历史服务器:访问 http://node01:19888/jobhistory

如果访问不了,第一反应看防火墙,第二看进程是否存活,第三看端口监听情况:netstat -anp | grep 9870。

7. 高可用切换实战验证,必须亲手测一测

7.1 用kill命令暴力测试NameNode自动切换

配置再好,不实际测一次永远不知道会不会切换。测试方法很简单:找到活跃NameNode的进程号,直接kill掉。让它“死”得干脆一点:

在node01上执行:

kill -9 $(cat /tmp/hadoop-root-namenode.pid)

然后观察两个现象:

  • node02上的NameNode是否从standby变成了active
  • node01上是否自动重启了新的NameNode进程(如果开启了自动重启配置,或者手动拉起)

确认切换状态:

hdfs haadmin -getAllServiceState

正常情况下会看到node02变成了active,node01(如果已重启)变成standby。整个过程大概几十秒,取决于ZK的会话超时时间。

这里要说一个细节:为什么旧的active被kill后,standby能自动上位、而不需要人工干预?因为ZKFC在ZooKeeper中注册了临时节点,active的ZKFC一断,临时节点消失,standby的ZKFC通过watcher机制感知到了变化,随即发起选举和状态变更。

7.2 手动切换回去,别在测试时把顺序搞乱

验证完自动切换到node02后,可以再把node01拉起来,然后手动切回:

hdfs haadmin -transitionToActive nn1

或者直接等node01的ZKFC自动通过选举把自己变成active。如果你想彻底验证自动切换,可以把node01启动后,观察一段时间。按照源码逻辑,节点重新加入后,是否重新成为active取决于ZK中的leader是否已经存在——如果node02已经是active,node01只会保持standby。

7.3 ResourceManager切换测试

ResourceManager的切换方式和NameNode不同。它依赖ZKRMStateStore,切换时可以手动:

yarn rmadmin -transitionToActive rm1 yarn rmadmin -getAllServiceState

在自动故障转移的场景下,如果kill掉active RM进程,standby RM会在几十秒内变成active。一个需要注意的指标:RM恢复时会加载ZK里保存的application状态,所以会有一定的恢复时间,这期间集群可能暂时不接收新任务,但一般不会超过1分钟。

7.4 验证DataNode没有心跳中断

切换完成后,在HDFS页面上看看live nodes数量是否正常,用命令验证:

hdfs dfsadmin -report

如果DataNode全部在线,说明整个集群在NameNode切换过程中没有感知到服务中断,DataNode会通过重新连接新的Active NameNode完成心跳续传。

8. 实战中踩过的坑,每个都值得记下来

8.1 NameNode格式化失败,或者格式化后启动报错

格式化失败最常见的原因是目录权限或已经存在旧元数据。如果以前搭过集群,再次搭建前务必清理干净:

rm -rf /data/hadoop/hdfs/name rm -rf /data/hadoop/hdfs/data

还有一个隐藏坑:格式化时机器名能解析到localhost,而配置里用的是node01,导致格式化出来的元数据bind到了错误机器上。解决方法是检查 /etc/hosts 和 /etc/sysconfig/network 里的主机名配置,确保一致。

8.2 JournalNode端口8485被占用

如果你在多台机器上反复重启JournalNode,有时候会提示端口占用。原因是客户端的journalnode进程没被杀干净。杀干净再重启:

kill -9 $(jps | grep JournalNode | awk '{print $1}')

还有一种情况是防火墙策略。虽然前面建议关防火墙,但如果你用的是云主机,云安全组策略是独立于防火墙的,需要在安全组里放行8485、2181、2888、3888、8020、9870、8088等端口。

8.3 ZKFC启动了但两个NameNode都是standby

我遇到过最诡异的场景是:两个NameNode都活着、ZK集群正常、ZKFC也启动了,但查询状态全是standby。排查过程让我发现了一个容易忽略的点:ZKFC启动时会连接ZooKeeper,并创建锁节点/hadoop-ha/mycluster/ActiveBreadCrumb,如果这个锁节点由于之前手动切换或格式化没清理而被残留了,新的active无法抢到锁,就一直保持standby状态。

修复方式:

hdfs zkfc -formatZK

这会清空HA相关的ZK节点,然后重启ZKFC:

/usr/local/hadoop-3.1.4/sbin/hadoop-daemon.sh stop zkfc /usr/local/hadoop-3.1.4/sbin/hadoop-daemon.sh start zkfc

8.4 ResourceManager切换后YARN web页面显示不正常

有时RM切换了,8088界面还是打不开或报错,大概率是当前机器上RM进程没起来。YARN脚本没有自动启所有RM,需要手动在另一台执行yarn-daemon.sh start resourcemanager。

另外注意RM的web界面地址如果用了逻辑地址(如rm1),浏览器里访问node01:8088跳转到node02:8088的情况是正常的——因为它开启了HTTP重定向。有些浏览器对重定向敏感,换个无痕窗口试试。

8.5 集群时间不一致导致ZK频繁断连

ZooKeeper的实现对会话超时比较敏感,如果节点间时间差太多,整个集群偶尔出现session expired,表现就是ZKFC连接不稳定、NameNode切换频繁。解决方法就是前面提到的ntpdate定时同步,把三台机器时间对齐。这个问题在测试环境里最容易忽略,因为不是配置错误、不会报明确错误,但会各种偶发问题,排查起来极费时间。

8.6 历史服务器日志看不到,HDFS路径不对

历史服务器跑起来后,MapReduce作业日志要查看,需要在yarn-site.xml中配置yarn.log.server.url指向历史服务器。如果配置缺失或者写错,页面跳转不到日志。另外mapreduce.jobhistory.intermediate-done-dir和done-dir这两个目录必须存在,如果之前手动建了本地路径,HDFS里不存在对应目录,也会失败——最好删掉它们让系统自动创建。

9. 经验总结与最后的实用建议

整个集群到目前为止,你已经从一个没有Hadoop的环境搭起了一个具备NameNode高可用、ResourceManager高可用、历史服务器的完整平台。这台集群虽然只有三台机器,但架构和生产环境基本一致——可以提交MapReduce作业、查看调度界面、观察自动切换、验证日志聚合。

我个人的经验是,HA集群搭建成功只是第一步,后续维护才真正体现水平。日常使用中建议关注几个点:一是定期查看ZooKeeper的选举状态,确认三台都在线;二是监控JournalNode的磁盘空间,edits log日积月累可能会占用不少空间;三是做任何NameNode变更操作前先备份hdfs-site.xml和core-site.xml,改坏了至少能回滚。

再分享一个很多教程不写的小技巧:NameNode在active状态下,如果直接重启机器,再次启动时大概率会先进入standby状态然后等ZK选举决定是否转active,这本来是正常逻辑。但如果你希望某台机器稳定作为首选的active节点,可以通过调整ZKFC的选举策略或者手动执行transitionToActive来实现,测试环境中手动指定反而减少不确定性。

还有,HA集群里HDFS的写操作性能会比单NameNode模式稍低,因为每次写操作都要多写一份journal节点,磁盘开销翻倍。如果你也遇到集群跑起来后写入速度明显变慢,不用怀疑是自己配置错了,这是HA的正常代价。生产环境通常会给JournalNode用独立的高性能磁盘来缓解。

希望这套保姆级教程能帮你少走弯路。如果你按着步骤操作却卡在某个环节,建议先按我整理的问题排查表对照检查,80%的问题都写在里面了。搭建过程中如果发现其他奇葩问题,欢迎随时交流,毕竟大数据这条路,踩过的坑才是真正属于自己的经验。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进