☰
Hadoop 3.x完全分布式集群HA高可用架构设计与部署实战
2026/10/19 9:38:17 网站建设 项目流程

1. 集群整体架构设计与选型思路

1.1 为什么非要上完全分布式加HA

单机伪分布式跑个Demo没问题,但一旦数据量上来、任务并发变高,单点故障就是悬在头顶的刀。NameNode挂了整个HDFS读不了也写不了,ResourceManager挂了所有计算任务排队等死。这套方案的核心目标就一个:让任何一个主控节点宕机时,集群能在秒级到分钟级内自动恢复服务。

我选的是Hadoop 3.x系列配合ZooKeeper 3.8.x,原因很直接:Hadoop 3对NameNode HA的支持已经非常成熟,而且YARN的ResourceManager HA配置比2.x简洁不少。ZooKeeper在这里扮演两个角色——NameNode HA的ZKFC选举协调器,以及YARN ResourceManager的Active-Standby状态存储。一套ZK集群同时服务两个HA体系,资源利用率高,运维也省心。

节点规划上,我用了5台机器:3台跑ZK和JournalNode,2台跑NameNode和ResourceManager。为什么是5台而不是3台?因为ZK集群要求奇数节点,3台刚好满足容错1台的要求;而NameNode和ResourceManager各需要2台做HA对。如果预算紧张,3台也能凑合——把NameNode、ResourceManager、ZK、JournalNode全混布,但生产环境不建议这么干,资源争抢和故障域重叠会让你排查问题排到怀疑人生。

1.2 核心组件角色分配与端口规划

先把角色分配表列清楚,后面所有配置都围绕这张表展开:

节点主机名ZK角色NameNode角色JournalNodeResourceManagerDataNode/NodeManager
node01FollowerActive NN是Active RM是
node02FollowerStandby NN是Standby RM是
node03Leader否是否是
node04Follower否否否是
node05Follower否否否是

端口方面,NameNode的RPC端口用8020,Web UI用9870;ResourceManager的RPC用8032,Web UI用8088;JournalNode的RPC用8485;ZK客户端端口2181。这些端口在防火墙策略里必须提前放行,否则ZKFC选举时会因为连不上JournalNode而反复重试,日志里全是Connection refused,新手很容易卡在这一步。

注意:所有节点的hostname必须提前在/etc/hosts里配好,并且保证互相能ping通。我见过有人用IP配HA,结果ZKFC的自动故障转移死活不生效——因为Hadoop的HA配置里如果写IP,ZKFC在切换时无法正确解析目标节点的主机名,导致fencing失败。

1.3 为什么选QJM共享存储而不是NFS

NameNode HA的共享编辑日志方案有两种:QJM和NFS。我选QJM的原因有三:第一,QJM不需要额外搭建NFS服务,少一个故障点;第二,QJM的JournalNode本身就是分布式设计,容忍少数节点宕机;第三,QJM的写入延迟比NFS低,因为它是并行写入多个JournalNode,只要多数节点确认就返回成功。

QJM的容错公式是:允许宕机的JournalNode数量 = (JournalNode总数 - 1) / 2。我配了3个JournalNode,所以允许挂1台。如果配5个,允许挂2台,但写入延迟会略微增加,因为需要等待更多节点确认。生产环境3个足够,除非你的机房跨机架部署,那建议5个。

2. 基础环境准备与系统调优

2.1 操作系统层面的必做项

所有节点统一用CentOS 7.9或Ubuntu 20.04,别混用。我踩过的坑:CentOS和Ubuntu的默认文件句柄数、内核参数、甚至JDK的默认字符集都不一样,混用会导致DataNode之间传输数据时出现莫名其妙的编码错误。

关闭防火墙和SELinux是第一步,但生产环境不能直接关防火墙,得按端口放行。测试环境为了省事可以临时关闭:

systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

配置hosts和hostname,每台机器执行:

hostnamectl set-hostname node01 cat >> /etc/hosts << EOF 192.168.1.101 node01 192.168.1.102 node02 192.168.1.103 node03 192.168.1.104 node04 192.168.1.105 node05 EOF

创建统一用户和目录,我习惯用hadoop用户,UID和GID在所有节点保持一致:

groupadd -g 1001 hadoop useradd -u 1001 -g hadoop -m hadoop echo "hadoop:你的密码" | chpasswd

配置SSH免密登录,在node01上生成密钥对,然后分发到所有节点:

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

实操心得:ssh-copy-id之后一定要手动ssh一遍每台机器,第一次连接会提示确认指纹,如果跳过这步,后面启动集群时脚本会卡在yes/no交互上,自动化脚本直接失败。

2.2 JDK安装与JAVA_HOME的坑

Hadoop 3.x要求JDK 8或JDK 11。我推荐JDK 8,因为生态兼容性最好。安装路径统一为/usr/local/jdk1.8,所有节点保持一致:

tar -zxvf jdk-8u351-linux-x64.tar.gz -C /usr/local/ mv /usr/local/jdk1.8.0_351 /usr/local/jdk1.8 cat >> /etc/profile << EOF export JAVA_HOME=/usr/local/jdk1.8 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar EOF source /etc/profile

关键坑点:Hadoop的hadoop-env.sh里必须显式指定JAVA_HOME,不能依赖系统环境变量。因为Hadoop启动脚本在某些情况下不会加载/etc/profile,导致找不到Java:

echo 'export JAVA_HOME=/usr/local/jdk1.8' >> $HADOOP_HOME/etc/hadoop/hadoop-env.sh

2.3 ZooKeeper集群部署

ZK集群是整套HA体系的基石,必须先于Hadoop启动。在三台ZK节点上分别操作:

tar -zxvf apache-zookeeper-3.8.3-bin.tar.gz -C /opt/ mv /opt/apache-zookeeper-3.8.3-bin /opt/zookeeper mkdir -p /data/zookeeper/data mkdir -p /data/zookeeper/logs

创建myid文件,node01写1,node02写2,node03写3:

echo "1" > /data/zookeeper/data/myid # node01执行 echo "2" > /data/zookeeper/data/myid # node02执行 echo "3" > /data/zookeeper/data/myid # node03执行

配置zoo.cfg:

tickTime=2000 initLimit=10 syncLimit=5 dataDir=/data/zookeeper/data dataLogDir=/data/zookeeper/logs clientPort=2181 maxClientCnxns=60 autopurge.snapRetainCount=3 autopurge.purgeInterval=1 server.1=node01:2888:3888 server.2=node02:2888:3888 server.3=node03:2888:3888

启动ZK集群:

/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/bin/zkServer.sh status

三台都启动后,应该看到一台Leader两台Follower。如果全是Looking状态,检查myid文件内容和zoo.cfg里的server编号是否对应,以及防火墙是否放行了2888和3888端口。

注意:ZK的dataDir目录权限必须是hadoop用户可写,否则启动时报Permission denied。我习惯用chown -R hadoop:hadoop /data/zookeeper统一授权。

3. Hadoop完全分布式核心配置

3.1 core-site.xml的HA关键参数

core-site.xml是所有Hadoop组件的公共配置,HA相关的核心参数都在这里:

<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> <property> <name>ha.zookeeper.session-timeout.ms</name> <value>10000</value> </property> </configuration>

fs.defaultFS写的是mycluster而不是具体主机名,这是HA的核心——客户端通过逻辑名称访问,由ZKFC决定当前Active是谁。mycluster这个名字可以自定义,但必须和hdfs-site.xml里的dfs.nameservices保持一致。

ha.zookeeper.session-timeout.ms默认是5000毫秒,我调到10000。原因:如果ZK集群网络抖动超过5秒,ZKFC会认为Active NameNode失联并触发切换,但实际可能只是短暂网络问题。调到10秒能减少误切换,代价是真实故障时切换慢5秒。这个取舍看你的业务容忍度。

3.2 hdfs-site.xml的NameNode HA配置

这是整个方案最核心的配置文件,我逐段解释:

<configuration> <!-- 命名服务逻辑名称 --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!-- 两个NameNode的ID --> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <!-- nn1的RPC和Web地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>node01:8020</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>node01:9870</value> </property> <!-- nn2的RPC和Web地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>node02:8020</value> </property> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>node02:9870</value> </property> <!-- JournalNode共享编辑日志地址 --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://node01:8485;node02:8485;node03:8485/mycluster</value> </property> <!-- JournalNode本地数据目录 --> <property> <name>dfs.journalnode.edits.dir</name> <value>/data/hadoop/journalnode</value> </property> <!-- 自动故障转移开关 --> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property> <!-- 故障转移代理类 --> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <!-- fencing隔离方法 --> <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> <property> <name>dfs.ha.fencing.ssh.connect-timeout</name> <value>30000</value> </property> <!-- 副本数 --> <property> <name>dfs.replication</name> <value>3</value> </property> <!-- NameNode数据目录 --> <property> <name>dfs.namenode.name.dir</name> <value>/data/hadoop/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/data/hadoop/datanode</value> </property> </configuration>

fencing机制必须配。sshfence的作用是:当ZKFC决定切换时,先SSH到旧的Active节点执行kill命令,确保旧的NameNode进程彻底死掉,防止脑裂。如果不配fencing,旧Active可能还在写JournalNode,新Active也写,编辑日志就乱了。

实操心得:sshfence要求从Active节点能免密SSH到Standby节点,反过来也要。我建议所有节点之间两两免密,省得后面出问题。另外dfs.ha.fencing.ssh.connect-timeout设30秒,太短了SSH还没连上就超时,切换失败。

3.3 yarn-site.xml的ResourceManager HA配置

YARN的HA配置比HDFS简单,因为ResourceManager没有共享编辑日志,状态直接存在ZK里:

<configuration> <!-- ResourceManager HA开关 --> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property> <!-- RM集群ID --> <property> <name>yarn.resourcemanager.cluster-id</name> <value>yarn-cluster</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> <property> <name>yarn.resourcemanager.webapp.address.rm1</name> <value>node01:8088</value> </property> <!-- rm2地址 --> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>node02</value> </property> <property> <name>yarn.resourcemanager.webapp.address.rm2</name> <value>node02:8088</value> </property> <!-- ZK状态存储 --> <property> <name>yarn.resourcemanager.zk-address</name> <value>node01:2181,node02:2181,node03:2181</value> </property> <property> <name>yarn.resourcemanager.recovery.enabled</name> <value>true</value> </property> <property> <name>yarn.resourcemanager.store.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value> </property> <!-- NodeManager辅助服务 --> <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.nodemanager.remote-app-log-dir</name> <value>/tmp/logs</value> </property> </configuration>

yarn.resourcemanager.recovery.enabled必须为true,否则RM重启后所有运行中的Application状态丢失,正在跑的任务全部失败。ZKRMStateStore把Application状态、Token、队列信息全存ZK里,切换后新Active RM能从ZK恢复状态。

3.4 workers文件与分发配置

workers文件列出所有DataNode和NodeManager节点:

cat > $HADOOP_HOME/etc/hadoop/workers << EOF node01 node02 node03 node04 node05 EOF

然后把配置好的Hadoop目录分发到所有节点:

for i in node02 node03 node04 node05; do scp -r $HADOOP_HOME $i:$HADOOP_HOME scp /etc/profile $i:/etc/profile done

注意:分发后每台机器都要source /etc/profile,否则hadoop命令找不到。我习惯在分发脚本最后加一句ssh $i "source /etc/profile"。

4. 集群启动流程与HA验证

4.1 首次启动的严格顺序

启动顺序错了会导致各种诡异问题,我总结的标准流程是:

第一步:启动ZK集群(三台ZK节点)

/opt/zookeeper/bin/zkServer.sh start

确认一台Leader两台Follower后再进行下一步。

第二步:启动JournalNode(三台JournalNode节点)

hdfs --daemon start journalnode

第三步:格式化NameNode(仅在node01执行)

hdfs namenode -format

第四步:启动node01的NameNode

hdfs --daemon start namenode

第五步:同步node02的NameNode元数据

hdfs namenode -bootstrapStandby

这一步从node01的Active NameNode拉取最新的fsimage和编辑日志,确保两个NameNode元数据一致。

第六步:格式化ZKFC(仅在node01执行)

hdfs zkfc -formatZK

这一步在ZK里创建HA相关的znode,ZKFC启动后会往这些znode写状态。

第七步:启动整个HDFS集群

start-dfs.sh

这个脚本会自动在所有节点启动DataNode,在node01和node02启动NameNode和ZKFC。

第八步:启动YARN

start-yarn.sh

在node01启动ResourceManager,其他节点启动NodeManager。然后手动在node02启动Standby ResourceManager:

yarn --daemon start resourcemanager

4.2 HA自动切换验证方法

验证NameNode HA是否生效,最直接的方法是kill掉Active NameNode进程,看Standby是否自动接管:

# 查看当前Active hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2 # 手动kill Active kill -9 $(jps | grep NameNode | awk '{print $1}') # 等待10秒后查看状态 hdfs haadmin -getServiceState nn1 hdfs haadmin -getServiceState nn2

如果配置正确,原来的Standby会变成Active,原来的Active变成Standby(进程已死)。ZKFC的日志在$HADOOP_HOME/logs/hadoop-hadoop-zkfc-*.log,切换过程的每一步都有记录。

YARN的HA验证类似:

yarn rmadmin -getServiceState rm1 yarn rmadmin -getServiceState rm2

kill掉Active RM后,Standby RM应该在30秒内接管。YARN的切换比HDFS慢,因为RM需要从ZK恢复状态。

4.3 常见故障与排查速查表

现象可能原因排查方法解决方案
ZKFC启动后两个NN都是StandbyZK里没有Active节点查看ZKFC日志检查ha.zookeeper.quorum配置,确认ZK可连
切换时卡在fencingSSH免密未配好手动ssh测试配置所有节点两两免密
JournalNode写入失败磁盘满或权限不对df -h和ls -la清理磁盘,chown hadoop:hadoop
RM切换后任务全部失败recovery未开启查看yarn-site.xml设置recovery.enabled为true
DataNode无法注册workers文件未分发检查各节点workers重新分发并重启
Web UI显示Standby正常现象访问Active的9870用hdfs haadmin查Active

实操心得:ZKFC的日志级别默认是INFO,排查问题时建议临时调到DEBUG。修改log4j.properties里的log4j.logger.org.apache.hadoop.ha=DEBUG,重启ZKFC后日志会详细到每一步选举和fencing操作。

5. 生产环境调优与运维经验

5.1 JVM堆内存与GC调优

NameNode的堆内存默认是1000MB,生产环境根本不够。我管理的集群有5000万文件,NameNode堆设了16GB。计算公式:每100万文件约需1GB堆内存,再留50%余量。

在hadoop-env.sh里调整:

export HDFS_NAMENODE_OPTS="-Xmx16g -Xms16g -XX:+UseG1GC -XX:MaxGCPauseMillis=200" export HDFS_JOURNALNODE_OPTS="-Xmx4g -Xms4g -XX:+UseG1GC" export YARN_RESOURCEMANAGER_OPTS="-Xmx8g -Xms8g -XX:+UseG1GC"

G1GC是Hadoop 3.x的推荐GC,相比CMS能更好地控制停顿时间。MaxGCPauseMillis设200毫秒,NameNode的GC停顿超过这个值,客户端请求就会超时。

5.2 机架感知配置

生产环境必须配机架感知,否则所有副本可能落在同一个机架,机架断电数据全丢。配置方法:

# 在core-site.xml添加 <property> <name>net.topology.script.file.name</name> <value>/etc/hadoop/rack.sh</value> </property>

rack.sh脚本根据IP返回机架名:

#!/bin/bash HOST=$1 case $HOST in 192.168.1.10[1-3]) echo "/rack1" ;; 192.168.1.10[4-5]) echo "/rack2" ;; *) echo "/default-rack" ;; esac

配好后用hdfs dfsadmin -printTopology验证。

5.3 日常巡检清单

我每天早上花10分钟跑一遍这些检查:

  • hdfs haadmin -getServiceState nn1和nn2,确认一主一备
  • yarn rmadmin -getServiceState rm1和rm2,确认一主一备
  • hdfs dfsadmin -report,看DataNode数量和容量
  • yarn node -list,看NodeManager状态
  • zkServer.sh status,确认ZK集群健康
  • 检查NameNode和ResourceManager的GC日志,看Full GC频率

注意:如果发现频繁Full GC,先别急着加堆内存,用jmap dump堆快照分析是不是有大量小文件或者泄漏。我遇到过NameNode堆里堆积了几百万个INode对象,原因是某个任务疯狂创建小文件,清理后GC恢复正常。

5.4 扩容与缩容操作

扩容DataNode很简单:新节点装好Hadoop,配好hosts和SSH,把workers文件加上新节点,然后在新节点执行hdfs --daemon start datanode和yarn --daemon start nodemanager。NameNode会自动识别新节点并开始分配数据块。

缩容要小心:先用hdfs dfsadmin -refreshNodes配合exclude文件把节点标记为Decommission,等数据块全部迁移完(hdfs dfsadmin -report里该节点显示Decommissioned),再停进程。直接kill进程会导致数据块副本不足,触发大量复制流量。

这套集群我跑了两年多,经历过NameNode进程崩溃、ZK集群网络分区、JournalNode磁盘写满等各种故障,HA机制每次都成功接管,业务侧最多感知到几十秒的抖动。核心经验就一条:HA不是配完就完事,定期演练切换、监控ZKFC和JournalNode的健康状态,才能保证真出故障时它真的能顶上。

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

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

立即咨询