1. 为什么完全分布式才是Hadoop集群的真正起点
伪分布式跑通WordCount那一刻确实挺爽,但那个体验跟真实生产环境之间隔了大概十条街。伪分布式把所有角色——NameNode、DataNode、ResourceManager、NodeManager——全塞在一台机器上,进程之间抢内存、抢磁盘IO,连数据块的副本机制都跑不出真实效果。你永远不知道网络抖动时心跳会不会丢,也看不到某个DataNode挂掉之后集群怎么自动恢复。
完全分布式就不一样了。它把各个角色拆到不同物理节点或虚拟机上,数据真正跨网络传输,副本真正落在不同机器上,任何一个节点宕机你都能观察到集群的自我修复过程。说白了,完全分布式才是你理解HDFS读写流程、YARN资源调度、MapReduce任务分发的最佳实验场。
这篇内容适合谁?如果你已经跟着前两篇搞定了CentOS7的基础环境配置、SSH免密、JDK安装,现在准备把Hadoop真正“铺开”到多台机器上,那接下来的每一步都是你需要的。我会从集群规划开始,一路讲到格式化、启动、验证,最后把我踩过的坑和排查思路全盘托出。整个方案基于Hadoop 3.x稳定版,三节点起步,配置可直接抄作业。
先明确一个核心原则:完全分布式的搭建过程,本质上是在解决“分布式一致性”问题。所有配置文件、时间同步、SSH互信、目录权限,都是在保证多个节点能协同工作而不打架。理解了这个,后面每一步你都知道为什么要这么做。
2. 集群规划与基础环境准备
2.1 节点角色分配与硬件考量
三节点是最小可行规模,再少就体现不出分布式的意义了。我建议的规划是这样的:
| 主机名 | IP地址 | 角色分配 | 内存建议 | 磁盘建议 |
|---|---|---|---|---|
| master | 192.168.1.100 | NameNode、ResourceManager、SecondaryNameNode | 4GB+ | 50GB+ |
| slave1 | 192.168.1.101 | DataNode、NodeManager | 2GB+ | 50GB+ |
| slave2 | 192.168.1.102 | DataNode、NodeManager | 2GB+ | 50GB+ |
为什么把SecondaryNameNode放在master上?很多教程把它单独放一台机器,理由是“减轻NameNode压力”。但实际测试下来,SecondaryNameNode的合并操作对内存和CPU的消耗远没有想象中那么大,在小规模集群里合设完全没问题。当然,如果你有第四台机器,单独放也无妨。
内存分配上有个经验公式:NameNode堆内存 ≈ 集群总文件数 × 150字节。假设你计划存100万个文件,那NameNode大概需要150MB堆内存,加上系统开销,给2GB足够了。DataNode的内存主要消耗在块扫描和传输上,2GB起步没问题。这些数字不是拍脑袋来的,是社区大量实践总结出来的经验值。
2.2 主机名与IP映射配置
三台机器都要做同样的操作。先设置主机名:
# 在master上执行 hostnamectl set-hostname master # 在slave1上执行 hostnamectl set-hostname slave1 # 在slave2上执行 hostnamectl set-hostname slave2然后编辑每台机器的/etc/hosts文件,加入以下内容:
192.168.1.100 master 192.168.1.101 slave1 192.168.1.102 slave2注意:主机名千万不要用下划线,Hadoop对主机名的解析规则比较严格,下划线会导致UnknownHostException。用短横线或纯字母数字组合最稳妥。
改完hosts之后,用ping master和ping slave1验证一下,确保三台机器之间能通过主机名互相通信。这一步看起来简单,但后面所有配置文件里写的都是主机名而不是IP,如果解析不了,集群根本起不来。
2.3 SSH免密登录配置
Hadoop的启动脚本依赖SSH在节点间执行命令,所以必须配置免密。在master上生成密钥对:
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa然后把公钥分发到所有节点,包括master自己:
ssh-copy-id master ssh-copy-id slave1 ssh-copy-id slave2验证方式很简单,在master上执行ssh slave1,如果不需要密码直接进去了,说明配置成功。这里有个细节:第一次SSH连接会提示确认主机指纹,这个提示会阻塞自动化脚本。解决办法是在~/.ssh/config里加上:
Host * StrictHostKeyChecking no UserKnownHostsFile /dev/null这样首次连接就不会卡住了。不过要注意,这只是实验环境的便利配置,生产环境还是要保留指纹校验的。
2.4 时间同步与防火墙处理
分布式系统对时间敏感,各节点时间差太大会导致心跳超时、日志时间错乱。CentOS7自带chronyd服务,配置一下:
# 所有节点执行 systemctl start chronyd systemctl enable chronyd如果内网没有NTP服务器,可以让slave节点同步master的时间。在slave上编辑/etc/chrony.conf,把server指向master的IP。
防火墙方面,实验环境建议直接关闭,避免端口不通导致各种诡异问题:
systemctl stop firewalld systemctl disable firewalld setenforce 0生产环境当然要按需开放端口,但那是另一个话题了。实验阶段先把防火墙关掉,能省掉至少一半的排查时间。
3. Hadoop核心配置文件逐项拆解
3.1 环境变量与JDK路径确认
先把Hadoop压缩包解压到统一目录,我习惯放在/opt/module/下:
tar -zxvf hadoop-3.3.4.tar.gz -C /opt/module/ mv /opt/module/hadoop-3.3.4 /opt/module/hadoop然后配置环境变量,编辑/etc/profile.d/my_env.sh:
export HADOOP_HOME=/opt/module/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME=/opt/module/jdksource一下让配置生效。这里有个容易忽略的点:Hadoop的启动脚本会读取JAVA_HOME,但如果你只在当前shell里export了,用sudo或su切换用户后可能读不到。所以一定要写进profile文件,而不是临时export。
3.2 core-site.xml:NameNode地址与临时目录
这个文件定义集群的全局参数,最核心的两个配置:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://master:9820</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/module/hadoop/data/tmp</value> </property> </configuration>fs.defaultFS告诉客户端NameNode在哪里,端口9820是Hadoop 3.x的默认RPC端口,2.x是9000,别搞混了。hadoop.tmp.dir是Hadoop的临时目录,NameNode和DataNode的元数据默认都放在这里。这个目录千万不要用默认的/tmp,因为系统重启会清空/tmp,到时候集群数据全丢,哭都来不及。
3.3 hdfs-site.xml:副本数与数据目录
<configuration> <property> <name>dfs.replication</name> <value>3</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/module/hadoop/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/opt/module/hadoop/data/datanode</value> </property> <property> <name>dfs.namenode.secondary.http-address</name> <value>master:9868</value> </property> </configuration>副本数设为3是标准做法:一份在本地节点,一份在同机架的另一节点,一份在跨机架的节点。三节点集群刚好每个节点一份。dfs.namenode.name.dir和dfs.datanode.data.dir分开配置,方便后续单独挂载不同磁盘做IO隔离。
3.4 yarn-site.xml:资源调度关键参数
<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.hostname</name> <value>master</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>2048</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>2048</value> </property> </configuration>yarn.nodemanager.resource.memory-mb表示每个NodeManager能支配的物理内存,默认是8GB,但你的虚拟机可能没那么多,所以要显式设置。yarn.scheduler.maximum-allocation-mb是单个容器能申请的最大内存,不能超过NodeManager的总量。这两个参数如果配反了,提交任务时会直接报“Container killed”错误。
3.5 mapred-site.xml与workers文件
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <property> <name>yarn.app.mapreduce.am.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value> </property> <property> <name>mapreduce.map.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value> </property> <property> <name>mapreduce.reduce.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop</value> </property> </configuration>后面三个环境变量配置是Hadoop 3.x的新要求,不配的话MapReduce任务会找不到Hadoop类路径。workers文件(2.x叫slaves)里写上所有DataNode的主机名:
slave1 slave2master本身不作为DataNode,所以不写进去。这个文件决定了start-dfs.sh会在哪些机器上启动DataNode进程。
3.6 配置文件分发与同步
所有配置文件改完后,用scp分发到两个slave节点:
scp -r /opt/module/hadoop slave1:/opt/module/ scp -r /opt/module/hadoop slave2:/opt/module/ scp /etc/profile.d/my_env.sh slave1:/etc/profile.d/ scp /etc/profile.d/my_env.sh slave2:/etc/profile.d/分发完记得在每台机器上source /etc/profile.d/my_env.sh。我见过有人只分发Hadoop目录忘了分发环境变量文件,结果slave上启动脚本找不到JAVA_HOME,报错信息还特别隐晦,排查半天。
4. 集群格式化与启动全流程实操
4.1 NameNode格式化:只做一次的操作
在master上执行:
hdfs namenode -format看到“Storage directory ... has been successfully formatted”就说明成功了。这个命令在整个集群生命周期里只能执行一次,除非你打算清空所有数据重新来过。重复格式化会导致NameNode和DataNode的clusterID不一致,DataNode会拒绝启动。
格式化之后,dfs.namenode.name.dir指定的目录下会生成current文件夹,里面有fsimage和edits文件。你可以进去看看,VERSION文件里记录了clusterID和namespaceID,这两个ID是集群的身份标识。
4.2 启动HDFS与YARN的正确顺序
启动HDFS:
start-dfs.sh这个脚本会读取workers文件,通过SSH到各节点启动DataNode,同时在master上启动NameNode和SecondaryNameNode。启动完成后用jps检查进程:
- master上应该有:NameNode、SecondaryNameNode
- slave1和slave2上应该有:DataNode
启动YARN:
start-yarn.shmaster上会出现ResourceManager,slave上出现NodeManager。注意YARN的启动脚本默认在master上执行,ResourceManager就启动在当前节点。
实操心得:如果某个进程没起来,第一件事是去看对应节点上的日志,路径在
$HADOOP_HOME/logs/下。日志文件名包含进程名和启动时间,比如hadoop-root-datanode-slave1.log。日志里的报错信息通常很直白,比瞎猜快得多。
4.3 Web UI验证与进程检查
HDFS的Web界面在http://master:9870,YARN的在http://master:8088。打开HDFS界面,重点看几个地方:
- Overview区域显示集群容量、已用空间、DataNode数量
- Datanodes标签页列出所有DataNode及其状态
- 如果DataNode数量少于预期,说明有节点没启动成功
YARN界面主要看Nodes标签页,确认所有NodeManager都注册上了。还有Scheduler页面能看到队列资源使用情况。
命令行验证也必不可少:
hdfs dfsadmin -report这个命令会输出每个DataNode的详细信息,包括容量、剩余空间、最后心跳时间。如果某个DataNode的“Last contact”超过30秒,说明心跳有问题。
4.4 运行第一个分布式MapReduce任务
跑一个WordCount验证整个链路:
# 创建输入目录 hdfs dfs -mkdir -p /input/wordcount # 上传测试文件 echo "hello hadoop hello yarn hello hdfs" > test.txt hdfs dfs -put test.txt /input/wordcount/ # 提交任务 hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.4.jar wordcount /input/wordcount /output/wordcount # 查看结果 hdfs dfs -cat /output/wordcount/part-r-00000如果输出里hello出现3次、hadoop和yarn和hdfs各1次,说明HDFS读写、YARN调度、MapReduce计算全链路都通了。这时候你可以去YARN的Web界面看看任务执行历史,能看到Map和Reduce任务分别分配到了哪些节点上,这就是完全分布式的直观体现。
5. 常见故障排查与避坑指南
5.1 DataNode启动失败排查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| DataNode进程不存在 | 目录权限不对 | 检查data目录属主 | chown -R root:root |
| DataNode启动后立刻退出 | clusterID不一致 | 对比NameNode和DataNode的VERSION文件 | 删除DataNode数据目录重新格式化 |
| Web UI显示DataNode数量为0 | 网络不通或端口占用 | telnet master 9820 | 检查防火墙和端口 |
| 日志报UnknownHostException | hosts文件没配 | ping主机名 | 补全/etc/hosts |
5.2 端口占用与进程残留处理
Hadoop的进程有时候会“假死”——jps能看到进程,但Web UI打不开。这种情况多半是端口被占用了。用netstat -tlnp | grep 端口号查一下,如果发现是之前的Hadoop进程没退干净,直接kill掉再重启。
还有一种情况是多次执行start-dfs.sh导致启动了多个同名进程。这时候jps会显示多个NameNode,但只有一个能绑定端口,其他的都在空转。解决办法是先用stop-dfs.sh停掉所有,确认没有残留进程后再启动。
5.3 内存不足与容器被杀
提交MapReduce任务时最常见的错误就是“Container is running beyond physical memory limits”。这是因为YARN默认给每个容器分配的内存超过了NodeManager的实际能力。回顾一下前面yarn-site.xml里的配置:
yarn.nodemanager.resource.memory-mb是NodeManager能用的总内存yarn.scheduler.maximum-allocation-mb是单个容器的上限
如果虚拟机只给了2GB内存,NodeManager最多也就用1.5GB左右,但MapReduce任务默认申请1GB的AM容器加1GB的Map容器,加起来就超了。解决办法是把这两个值调小,比如都设成1024,同时把mapreduce.map.memory.mb和mapreduce.reduce.memory.mb也相应调小。
5.4 安全模式与数据块副本异常
有时候集群启动后HDFS处于安全模式(Safe Mode),无法写入数据。这是NameNode在等待DataNode上报块信息,通常等30秒会自动退出。如果一直不退,用hdfs dfsadmin -safemode get查看状态,用hdfs dfsadmin -safemode leave手动退出。
副本异常表现为某些块的副本数少于设定值。用hdfs fsck /path -files -blocks可以查看具体哪些块有问题。如果是DataNode临时下线导致的,等节点恢复后HDFS会自动复制副本补齐。如果某个DataNode彻底坏了,需要手动从集群中移除并重新平衡数据。
5.5 独家避坑经验三则
第一则:格式化之前先备份VERSION文件。如果你不小心格式化了NameNode,但DataNode数据还在,理论上可以通过手动修改clusterID来恢复。但前提是你记得原来的clusterID。所以格式化之前,把dfs.namenode.name.dir/current/VERSION文件复制一份存好,关键时刻能救命。
第二则:workers文件里不要写localhost。有些教程图省事写localhost,结果所有DataNode都起在同一台机器上,完全分布式变成了伪分布式。一定要写真实的主机名。
第三则:启动脚本的执行用户要一致。如果你用root格式化NameNode,又用其他用户启动DataNode,会因为目录权限问题导致启动失败。实验环境统一用root最省事,生产环境则要规划好专用用户和组。
6. 集群日常运维与扩展思路
6.1 动态添加新DataNode节点
集群跑起来之后想加节点,步骤比初次搭建简单得多。在新节点上配好hosts、SSH免密、JDK和Hadoop环境,把配置文件从master同步过去,然后直接执行:
hdfs --daemon start datanode yarn --daemon start nodemanager新节点会自动向NameNode注册,在Web UI上就能看到。数据平衡是自动进行的,NameNode会逐步把部分块的副本迁移到新节点上。如果数据量大,可以用hdfs balancer命令手动触发平衡,并调整带宽参数控制迁移速度。
6.2 日志管理与监控要点
Hadoop的日志默认按天滚动,保留数量有限。实验环境够用,但如果你要跑长时间任务,建议调整log4j.properties里的滚动策略。关键日志文件包括:
- NameNode日志:记录所有元数据操作,排查文件系统问题必看
- DataNode日志:记录块读写和心跳,排查数据问题必看
- ResourceManager日志:记录应用调度,排查任务提交问题必看
监控方面,除了Web UI,还可以用jps配合定时脚本做进程存活检测。更专业的做法是接入Prometheus和Grafana,但那是另一个话题了。
6.3 从实验集群到生产思维的转变
实验集群搭起来只是第一步。真正要往生产环境靠,还需要考虑:高可用(HA)配置,消除NameNode单点故障;联邦(Federation)机制,支持多个NameNode横向扩展;机架感知,让副本分布更合理;安全认证,接入Kerberos做身份验证。
这些高级特性每一个都值得单独写一篇。但如果你连完全分布式的基础集群都没亲手搭过,直接上HA只会让你更迷糊。先把三节点集群跑稳,把每个配置文件的作用搞清楚,把常见故障排查一遍,后面再学HA就是水到渠成的事。
我个人在实际操作中的体会是,搭建过程中遇到的每一个报错都是理解Hadoop内部机制的机会。比如DataNode启动失败让你去研究clusterID,内存不足让你去理解YARN的资源模型,安全模式让你去了解NameNode的启动流程。这些知识光看文档记不住,只有亲手踩过坑才能真正掌握。最后再分享一个小技巧:把搭建过程录屏或者写成脚本,下次重建环境时直接复用,能省下大量重复劳动的时间。