第一次搭 Hadoop 完全分布式集群,我照着网上的教程敲了一整天,NameNode 起不来、DataNode 连不上,最后发现是 /etc/hosts 里多了一个空格。这种“配置全对但就是跑不起来”的体验,估计每个搭过集群的人都经历过。这篇文章把我从零到一把三节点完全分布式集群跑通的完整过程,包括踩过的坑、报错信息背后的原理、每一步为什么要这么配,一次性写清楚。不管你是课程设计要交差,还是想在本地搞一套测试环境做实验,按这个流程走完,能少走不少弯路。
1. 先把架构讲明白:完全分布式和那些“起不来”的根子
1.1 三大组件在集群里到底怎么协作
在展开配置之前,先说清楚完全分布式集群里到底有哪些角色。Hadoop 的底层存储是 HDFS,计算框架是 MapReduce,资源调度靠 YARN。这三部分的进程各有分工:HDFS 里有 NameNode 和 DataNode,YARN 里有 ResourceManager 和 NodeManager,另外还有 SecondaryNameNode 这个辅助角色。
NameNode 负责整个文件系统的元数据,可以理解成图书馆的总目录;DataNode 负责真正存放数据块,相当于书架上的实体书。客户端要读一个文件,先去找 NameNode 问“这个文件在哪些节点上”,然后拿着地址去对应 DataNode 拉数据。这也是为什么 NameNode 挂了整个集群就不可用——它是 HDFS 的单点。
ResourceManager 是 YARN 的大脑,负责接收客户端提交的计算任务并分配资源;NodeManager 是每台机器上的执行者,负责启动和监控容器。SecondaryNameNode 这个名字很有迷惑性,它并不是 NameNode 的热备节点,它的职责是定期合并 NameNode 的编辑日志(edits)和镜像文件(fsimage),为的是让 NameNode 重启时恢复得快一点,防止日志无限膨胀。这个区别也是面试里经常被问到的点。
1.2 配置全对还是失败,问题多半出在这四个前置条件
我先按三节点集群来规划:node01 作为主节点,跑 NameNode、ResourceManager、SecondaryNameNode;node02 和 node03 作为工作节点,跑 DataNode 和 NodeManager。如果机器不够,也可以让 node01 同时当 DataNode 和 NodeManager,但那样主节点负载会高一些,测试环境无所谓,生产环境不建议这么干。
除了角色规划,还有四个前置条件是最容易被忽略的:
- 内存。Hadoop 的进程本来就是 JVM 进程,光一个 NameNode 就默认占用 1G 左右的堆内存,加上 ResourceManager、NodeManager,每台机器至少要有 2G 内存,否则后面跑 MapReduce 任务时很容易 OOM。
- 时钟同步。集群内节点时间差太大会导致心跳异常甚至认证失败,本地测试环境如果时间差不大还好,但最好统一一下时间,否则后面排查问题时很难想到这一层。
- 网络。保证各节点 IP 固定,
/etc/hosts内容一致,防火墙要放行集群通信端口,或者直接关掉防火墙。 - SSH 免密登录。
start-dfs.sh和start-yarn.sh启动时会通过 SSH 远程到所有节点拉起进程,没有免密只能手动一台台启动,集群脚本就完全失去了意义。
这四个条件里,我见过太多人挂在第 3 个和第 4 个上:hosts 文件写错、SSH 免密配不完全,结果启动脚本报错的时候,日志里根本看不到明确信息。先把这个基础打好,后面配置文件的坑反而都是明面上的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三台机器的准备:版本选型与基础环境
2.1 JDK 和 Hadoop 版本怎么搭配最稳
版本选择是一个经常被忽略但决定成败的环节。我建议用 Hadoop 3.3.6(或 3.3.x 的最新小版本),搭配 OpenJDK 8(1.8),这是目前最稳妥的组合。Hadoop 3.3 虽然也支持 JDK 11,但很多教程和文档还是按 JDK 8 写的,遇到问题好查资料,而且大部分线上生产环境的主流选择也是这个组合。
不建议追新去装 Hadoop 4.x,也不建议再用 Hadoop 2.x。前者太新踩坑没人能帮你;后者虽然还有老教程在讲,但端口、命令细节都和新版本不一样,学完反而容易把新版本搞混。操作系统方面,CentOS 7.9 或者 Ubuntu 20.04/22.04 都可以,核心步骤几乎一样,只是包管理器不同。如果你手头连三台虚拟机都凑不齐,也可以用 Docker 起三个容器来模拟多节点,思路是相通的,后面我会提到一些和容器环境相关的差异点。
2.2 JDK 安装与 JAVA_HOME 的坑
JDK 安装我习惯直接用解压版,不用 yum 自带的 openjdk,因为那个版本有时候会缺少一些管理工具。以 root 用户为例,把 jdk-8u202-linux-x64.tar.gz 解压到 /usr/local/java,然后编辑 /etc/profile:
bash复制export JAVA_HOME=/usr/local/java/jdk1.8.0_202
export PATH=$JAVA_HOME/bin:$PATH
执行 source /etc/profile 后,用 java -version 验证。这里有个必须记住的点:Hadoop 的启动脚本是通过 $JAVA_HOME 找 Java 的,而且它在 hadoop-env.sh 里读取的 JAVA_HOME 是硬编码的绝对路径,不读 /etc/profile 里的环境变量。很多人在命令行 java 能用,但启动 Hadoop 时却报找不到 JAVA_HOME,就是因为没在 hadoop-env.sh 里显式配置。这个坑我在第一次搭建时踩过,后面会专门讲。
2.3 主机名、hosts 映射与 SSH 免密
在每台机器上修改主机名,然后统一 /etc/hosts。如果三台机器的 hosts 内容不一致,后面会非常折腾。我习惯把三行映射都写在每台机器的 hosts 里:
code复制192.168.10.11 node01
192.168.10.12 node02
192.168.10.13 node03
SSH 免密配置也很简单,每台机器都执行:
bash复制ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
ssh-copy-id node01
ssh-copy-id node02
ssh-copy-id node03
注意最后一步要把自己和其他节点都加一遍,因为 start-dfs.sh 会先 SSH 到本机再跳到其他节点。测试的时候不要只测从 node01 到 node02,还要试 ssh node01 本机登录。如果这一步没配好,后面启动脚本会卡在“需要输入密码”上,看起来就像卡死了一样。如果遇到 SSH 连接很慢或失败,检查 ~/.ssh 目录权限,正常应该是 700,authorized_keys 是 600。
3. 配置文件逐项拆解:每一行都要知道为什么
3.1 core-site.xml 与 hdfs-site.xml
下载 Hadoop 并在 node01 上解压到 /usr/local/hadoop。先加环境变量:
bash复制export HADOOP_HOME=/usr/local/hadoop
export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH
配置文件都在 $HADOOP_HOME/etc/hadoop 下。第一个是 core-site.xml,核心是 fs.defaultFS 和 hadoop.tmp.dir:
xml复制<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://node01:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/usr/local/hadoop/tmp</value>
</property>
</configuration>
fs.defaultFS 指定了客户端访问 HDFS 的入口,相当于告诉所有组件“文件系统的主节点在哪”。hadoop.tmp.dir 是 Hadoop 临时数据的根目录,默认在 /tmp 下,系统一重启就可能被清掉,NameNode 的元数据如果存在这里,后果可想而知。所以无论如何都要把它改到数据盘或固定目录。
接下来是 hdfs-site.xml,这里最核心的是 name 目录和 data 目录的显式配置:
xml复制<property>
<name>dfs.namenode.name.dir</name>
<value>/usr/local/hadoop/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/usr/local/hadoop/dfs/data</value>
</property>
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>node01:9868</value>
</property>
有几个细节需要展开说。dfs.replication 表示每个数据块的副本数,三节点都跑 DataNode 的话可以设 3;如果其中一台不跑 DataNode,就要设成 2,否则副本永远写不满,会一直报 Under replicated 警告。dfs.namenode.name.dir 和 dfs.datanode.data.dir 不写的话会默认放到 hadoop.tmp.dir 下面,但显式写出来更直观,也更方便后续单独处理磁盘。
dfs.namenode.secondary.http-address 在 Hadoop 3.x 里建议显式配置,默认值虽然存在,但多节点环境下最好指定到主节点,这样 SecondaryNameNode 会固定跑在 node01 上,日志和排查都方便。这个配置漏了,SecondaryNameNode 可能无法正常工作,但很多人一开始根本看不出来,直到 NameNode 日志越来越庞大才意识到问题。
3.2 yarn-site.xml、mapred-site.xml 与 workers
yarn-site.xml 是另一个重灾区。最关键的两个属性:
xml复制<property>
<name>yarn.resourcemanager.hostname</name>
<value>node01</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
yarn.resourcemanager.hostname 告诉所有 NodeManager 去哪里找 ResourceManager。aux-services 里必须配 mapreduce_shuffle,这是 MapReduce 在 YARN 上跑任务时进行数据洗牌(shuffle)所依赖的辅助服务,漏配的话任务能提交但会一直卡在 RUNNING 状态。
对于小内存的测试集群,还要调低资源阈值,否则 NodeManager 的资源会被吃满。我一般会在三台 2G 内存的虚拟机上设置:
xml复制<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>1024</value>
</property>
<property>
<name>yarn.scheduler.minimum-allocation-mb</name>
<value>256</value>
</property>
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>1024</value>
</property>
mapred-site.xml 在 Hadoop 3.x 里默认是模板,需要自己从 mapred-site.xml.template 复制一份出来。核心配置就两行:
xml复制<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>node01:10020</value>
</property>
mapreduce.framework.name 必须是 yarn,否则任务会在本地跑,根本不会提交到集群。jobhistory 配在 node01 用于查看历史任务,配不配不影响核心启动,但配了方便后期排查问题。
最后是 workers 文件(Hadoop 3.x 以前叫 slaves)。在这个文件里每行写一个主机名,列出所有要运行 DataNode 和 NodeManager 的节点:
code复制node01
node02
node03
如果想让 node01 只做主节点,这里就只写 node02 和 node03;如果想让主节点也承担存储和计算任务,就把 node01 也写进去。注意这个文件是所有节点共用的,start-dfs.sh 会根据它来决定在哪里启动 DataNode。
3.3 hadoop-env.sh 里被忽略的坑
在所有配置里,hadoop-env.sh 很容易被忽略,但它往往是启动失败的直接原因。首要的事是把 JAVA_HOME 改成绝对路径:
bash复制export JAVA_HOME=/usr/local/java/jdk1.8.0_202
export HADOOP_HEAPSIZE=1024
HADOOP_HEAPSIZE 控制 Hadoop 各进程的默认堆大小,测试环境设 1024 就够了,设太大反而会拖垮小内存机器。如果不设,某些场景下默认堆配置会吃掉大量内存,尤其是 NameNode,很容易把整台虚拟机的内存打满,然后所有进程都会被 OOM Killer 杀掉。
改完这些配置后,最省事的做法是把整个 /usr/local/hadoop 目录打包复制到其他节点,然后保持目录结构和用户一致。我习惯在配置完 node01 后直接执行:
bash复制scp -r /usr/local/hadoop node02:/usr/local/
scp -r /usr/local/hadoop node03:/usr/local/
顺便把 /etc/profile 也同步过去,保证每台机器的环境变量一样。不要在一台机器上改完配置,再到另一台上逐个敲一遍,既容易漏又浪费时间。
4. 从格式化到跑通 WordCount:完整的启动链路
4.1 格式化 NameNode 的时机与误区
配置完成后,第一件大事是格式化 NameNode。这里最容易出问题的其实不是命令本身,而是时机和反复执行。格式化命令:
bash复制cd /usr/local/hadoop
bin/hdfs namenode -format
格式化会做两件事:生成 NameNode 的元数据目录和集群 ID(clusterID),并写入 VERSION 文件。如果格式化成功,日志里能看到 successfully formatted 字样。这里必须强调:这个命令只能在集群第一次启动前执行一次。之后如果因为启动失败想重新格式化,必须先把所有节点的 name 目录和 data 目录清空,否则 DataNode 和 NameNode 的 clusterID 对不上,就会出现 DataNode 起不来或掉线的经典故障。
还有一个小坑:执行格式化时,如果之前启动过集群,NameNode 进程还活着,格式化会直接失败,提示 already running。所以先保证没有任何 Hadoop 进程再执行。
4.2 启动顺序与验证清单
格式化完成后,第一次启动集群:
bash复制start-dfs.sh
start-yarn.sh
start-dfs.sh 会依次拉起 NameNode、DataNode 和 SecondaryNameNode;start-yarn.sh 会拉起 ResourceManager 和 NodeManager。启动过程中终端会输出 SSH 连接日志,如果哪台机器连不上,这里就会有明显提示。
启动后先别急着提交任务,按顺序验证:
- 在 node01 执行
jps,确认存在 NameNode、SecondaryNameNode、ResourceManager。node02/node03 上执行jps,确认存在 DataNode 和 NodeManager。如果哪个进程缺失,去看对应日志,NameNode 日志一般在$HADOOP_HOME/logs下,文件名类似hadoop-root-namenode-node01.log。 - 用
hdfs dfsadmin -report查看存活节点数,确认所有 DataNode 都注册进来。 - 浏览器访问
http://node01:9870,看到 NameNode 页面说明 HDFS 工作正常。访问http://node01:8088,看到 ResourceManager 页面说明 YARN 工作正常。 - 跑一个最基础的 MapReduce 样例程序验证端到端。先创建测试输入目录:
bash复制hdfs dfs -mkdir -p /test/input
hdfs dfs -put $HADOOP_HOME/NOTICE.txt /test/input
hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /test/input /test/output
这里唯一要注意的是输出目录 /test/output 必须不存在,因为 MapReduce 不允许覆盖已有输出目录。如果第一次跑完想看第二次,先删掉输出目录或换个新名字。跑完后用 hdfs dfs -cat /test/output/* 查看统计结果。
4.3 Web UI 白屏和页面打不开的排查思路
页面访问不通是很多人第一步就卡住的地方。先用 curl http://node01:9870 检查本机是否返回 HTML,再用 ping node01 检查 hosts 解析是否正常。如果 node01
