第一次搭Hadoop完全分布式,我在同一个坑里踩了整整两天:node01 的 jps 一切正常,node02 和 node03 的 DataNode 就是起不来。后来翻日志才发现,问题是出在我担心启动失败,连续执行了两次 hdfs namenode -format。这种“格式化一时爽,排查火葬场”的经历,在零基础阶段真的太典型了。
今天我用一边搭建一边解释原理的方式,把 Hadoop 完全分布式的整个过程重新梳理一遍。文章不讲高深理论,只讲你从零开始需要做什么、每步为什么要这样做,以及在哪个环节最容易翻车。适合第一次接触分布式、照着伪分布式教程成功后想进一步搭建多节点集群的读者。
1. 搭建之前,先搞清楚完全分布式和伪分布式差在哪
1.1 从一个小卖部模型说起
很多人一开始会困惑:我在一台电脑上明明也能跑 Hadoop,为什么要折腾多台机器?
单机模式(Local Mode)里,Hadoop 连 HDFS 都没启动,输入输出都在本地文件系统上,MapReduce 也只在单个 JVM 里跑,它存在的意义主要是本地调试。伪分布式(Pseudo-Distributed Mode)虽然启动了 NameNode、DataNode 这些进程,但所有进程都在同一台机器上,就像一个公司所有员工都挤在一间屋子里上班,能模拟出“岗位分工”,却完全没有体现分布式真正的容错和扩展能力。
完全分布式是把不同角色真正拆到不同机器上。用一个更生活化的类比:NameNode 是公司的财务和档案室,负责记“每份文件放在哪个仓库的哪个货架”;DataNode 是真正的仓库管理员,实际保管文件内容;ResourceManager 是项目调度中心,安排计算任务去哪些仓库存取数据。如果财务、档案室、仓库全在一间屋子里,小规模可以运转,但数据量一大,这间屋子早晚被塞满,一个人也忙不过来。
1.2 完全分布式里到底有哪些角色
零基础阶段,你不需要把 Hadoop 每个组件都吃透,但至少要知道这几个进程各管什么:
| 进程名 | 所属组件 | 核心职责 | 最容易理解的一句话 |
|---|---|---|---|
| NameNode | HDFS | 管理文件系统的元数据 | 只记账本,不存实际数据块 |
| DataNode | HDFS | 实际存储数据块 | 真正管货架的仓库员 |
| SecondaryNameNode | HDFS | 定期合并元数据日志 | 给 NameNode 做个“定期的账目整理”,不是热备 |
| ResourceManager | YARN | 接收作业、分配资源 | 项目总调度,决定任务给谁跑 |
| NodeManager | YARN | 在单机上启动任务容器 | 每个节点上的“包工头” |
看到“完全分布式”,你要在脑子里建立的第一个判断是:NameNode 这台机器不能被称为“存储所有数据的服务器”,它存的是目录结构、文件与数据块的映射关系这类元数据,真正的文件内容被打散成一块一块(默认 128MB 一个块),存放在多台 DataNode 上。很多零基础的人搭完集群后,去 DataNode 目录里看不到那种“我一眼能看懂的文件”,这是正常的,因为块文件由框架管理。
1.3 文件读写流程:把“为什么分角色”讲透
读一个文件时,客户端会先访问 NameNode,问“我想读 /data/a.txt,它的数据块在哪些机器上”,NameNode 查询元数据后返回块位置列表,例如“block 1 在 node02 和 node03,block 2 在 node01 和 node02”。然后客户端直接去对应的 DataNode 读取内容,不再经过 NameNode。这种设计让 NameNode 只承担非常轻量的路径索引工作,真正的数据传输压力分散到各个 DataNode 上。
写文件时也类似:客户端先问 NameNode“我要创建一个文件,应该把第一个数据块写到哪两台机器”,NameNode 基于机架感知甚至磁盘空间情况,给出一份 DataNode 写入列表。客户端随后把数据块依次传输给第一个 DataNode,再由它复制给下一个 DataNode,形成流水线复制。这就是 HDFS 默认三副本策略背后的分布式传输逻辑。
零基础理解完这个小节,就可以开始准备机器了。因为后面所有配置文件里填的主机名、端口,本质上都是为了让这些角色在正确的机器上运行起来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三台机器从零准备:硬件、系统、网络里的大坑
2.1 到底需要几台机器,配置怎么定
完全分布式最少需要两台机器,因为要保证元数据节点和数据节点在不同机器上;但学习阶段我不建议只准备两台。原因有两个:第一,HDFS 的默认副本数是 3,如果只有 2 个数据节点,第三个副本永远没有地方可放,集群状态会一直告警;第二,很多 HDFS 操作只有配合多个节点才能观察到数据块的复制和分散效果。
最舒服的配置是三台虚拟机,一台作为主节点(NameNode + ResourceManager),另外两台作为从节点。如果你电脑内存只有 16GB,我给出一份实测比较稳的配置:
| 角色 | 主机名 | 推荐内存 | 推荐磁盘 | 主要运行进程 |
|---|---|---|---|---|
| 主节点 | node01 | 3GB 以上,最好 4GB | 50GB | NameNode、ResourceManager |
| 从节点 | node02 | 2GB 以上 | 50GB | DataNode、NodeManager、SecondaryNameNode |
| 从节点 | node03 | 2GB 以上 | 50GB | DataNode、NodeManager |
三台虚拟机可以都跑在一台物理电脑上,不建议一上来就买三台云服务器,因为按量计费可能让你在实验过程中一直焦虑。用 VMware Workstation 或 VirtualBox 都可以,创建第一台虚拟机后,另外两台用“克隆”方式生成,能省去重复安装系统的大量时间。
2.2 克隆后的虚拟机最容易犯的错:主机名和 IP 没改
克隆虚拟机最大的优势是快,但有个连锁坑:克隆出来的机器会沿用源虚拟机的 MAC 地址、hostname 和网卡配置。如果你在 VMware 里使用“完整克隆”,创建向导通常会问你是否重新初始化 MAC 地址,一定要选“是”。
创建完三台虚拟机后,先分别执行 hostnamectl set-hostname 把主机名改成 node01、node02、node03,然后固定 IP。固定 IP 建议用 NAT 模式,因为这样虚拟机之间通过虚拟交换机可以互通,同时也能上网下载依赖包。以 CentOS 7.9 或后续兼容版本为例,改网卡配置的典型操作是编辑 /etc/sysconfig/network-scripts/ifcfg-ens33,把 BOOTPROTO="static" 并追加 IP 信息。如果用的是 Ubuntu Server 或带 Netplan 的版本,路径和格式会不一样,核心思路是同样的:每台机器给一个固定内网 IP。
我常用的 IP 规划是这样的:
- node01:192.168.10.101
- node02:192.168.10.102
- node03:192.168.10.103
改了网卡后记得重启网络服务,或者直接重启虚拟机,再执行 ip addr 确认 IP 生效。如果发现三台机器互相 ping 不通,先检查 VMware 的虚拟网络编辑器里 NAT 网段是否和你的静态 IP 在同一网段。零基础阶段最容易出现的问题是:宿主机无线网卡也占用了一个 192.168.10.x 网段,导致虚拟网络和物理网络冲突,这时换一个不常用的网段比如 192.168.56.x 就能解决。
2.3 防火墙、SELinux、系统时钟:三个看不见的绊脚石
为什么很多教程都让你关闭防火墙?因为 Hadoop 各进程之间会动态使用大量端口,DataNode 和 NameNode 之间的心跳、客户端与 DataNode 之间的数据传输端口都是由框架随机分配的。对学习环境来说,最省心的做法是直接关闭防火墙;如果公司或生产环境不允许关防火墙,则需要精确放行 NameNode、DataNode、ResourceManager、NodeManager 之间的通信端口,这要复杂得多。
在 CentOS 系系统上执行:
bash复制systemctl stop firewalld
systemctl disable firewalld
还要检查 SELinux 状态。执行 getenforce,如果返回 Enforcing,需要临时和永久关闭,否则节点间通信可能被拦截。
另一个容易被忽略的是节点时间同步。HDFS 内部对时间敏感,节点间时间差过大会导致一些校验失败,表现还很奇怪——比如某个节点总能启动但无法正常参与数据块复制。最简单的做法是在三台机器上都配置系统时间同步,用 NTP 服务或 chrony 均可,学习阶段至少保证三台机器时间和宿主机时间大差不差。
3. JDK、hosts 解析、SSH 免密登录,一个都不能少
3.1 JDK:版本选择真的不用纠结
Hadoop 本身是 Java 写的,所有节点都必须先有 JDK。虽然新版本 Hadoop 3.3 开始支持更高版本的 Java,但业界最主流的选择依旧是 JDK 8。原因在于,Hadoop 自己的生态组件像 Hive、Spark、HBase,很多老版本只对 JDK 8 做过充分测试。学习阶段用 JDK 8 能避开大量“版本支持矩阵”带来的问题。
下载 jdk-8u202-linux-x64.tar.gz 后,放在 node01 的 /usr/local/src 目录,解压到 /usr/local/jdk1.8.0_202:
bash复制cd /usr/local/src
tar -zxf jdk-8u202-linux-x64.tar.gz -C /usr/local/
然后编辑 /etc/profile,在文件末尾加入:
bash复制export JAVA_HOME=/usr/local/jdk1.8.0_202
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
执行 source /etc/profile 后验证:
bash复制java -version
三台机器都要安装 JDK,且路径最好保持一致,因为后面 Hadoop 脚本会通过 JAVA_HOME 去定位 Java。如果你一开始把 JDK 安装路径写成了 /opt/jdk,那所有节点的路径都要一致,否则某个节点的进程会直接报“找不到 Java”。
3.2 /etc/hosts:先于一切的域名解析
在 Hadoop 配置文件里,你写的不是 IP 而是主机名,例如 hdfs://node01:8020。那么所有节点就必须能把 node01、node02、node03 解析到对应 IP,否则启动时根本连不上。修改三台机器的 /etc/hosts,追加如下内容:
bash复制192.168.10.101 node01
192.168.10.102 node02
192.168.10.103 node03
这里有个经验之谈:完全分布式搭建中,/etc/hosts 的优先级比我一开始想象得高。很多人的集群启动失败,报错信息里写着 “UnknownHostException: node02”,结果排查到最后发现只是 hosts 文件没写全。另一个值得注意的地方是,不要把 node01 解析成一个公网域名或某个特殊内网地址,保持和实际 IP 一一对应。
3.3 SSH 免密登录:不是给你省事,是给脚本用的
Hadoop 的 start-dfs.sh 脚本会在主节点上启动 NameNode,然后通过 SSH 登录到 workers 文件里列出的每一台机器,去那边启动 DataNode。如果你没有配置免密登录,脚本会停下来向你询问密码,分布式环境里基本没法用。
SSH 免密的原理其实很简单,使用非对称加密:node01 生成一对密钥,私钥留在 node01,公钥分发到所有需要被登录的机器上。当 node01 发起 SSH 连接时,目标机器用公钥验证 node01 是否持有对应的私钥,验证通过就放行,全程不需要输密码。
在主节点 node01 上执行:
bash复制ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
连续回车会在 ~/.ssh 目录下生成 id_rsa 和 id_rsa.pub。然后逐个分发公钥:
bash复制ssh-copy-id node01
ssh-copy-id node02
ssh-copy-id node03
注意,node01 自己也要做一次免密,因为 start-dfs.sh 不仅要登录到其他节点,也会通过 SSH 在本地执行某些命令。分发完成后,验证免密效果:
bash复制ssh node02 hostname
如果不需要输密码直接返回 node02,说明配置成功。我遇到过一种情况:用 ssh-copy-id root@node02 能够登录,但 ssh node02 仍然要输密码,原因是没有把 node01 自己的公钥加入到 node01 的 authorized_keys 里,仔细检查每个文件的权限也很重要,.ssh 目录权限必须是 700,authorized_keys 必须是 600,权限过宽会被 SSH 拒绝加载公钥。
4. 五个配置文件逐字段拆解:从零到能启动的全部修改
4.1 先统一软件路径,再谈配置
下载 Hadoop 二进制包(选择 hadoop-3.3.x.tar.gz 这类发行版)上传到 node01 的 /usr/local/src,解压后放到统一目录:
bash复制tar -zxf hadoop-3.3.6.tar.gz -C /usr/local/
mv /usr/local/hadoop-3.3.6 /usr/local/hadoop
所有节点软件路径保持一致非常重要。Hadoop 脚本里经常会出现相对路径和基于 HADOOP_HOME 的路径拼接,如果 node01 装在 /usr/local/hadoop,node02 却装在 /opt/hadoop,你在主节点执行 start-dfs.sh 后会发现远端进程要么找不到配置,要么找出一个奇怪的目录。
配置文件的逻辑其实可以串成一条线:Hadoop 启动时,首先去 $HADOOP_HOME/etc/hadoop 目录读取 XML 文件,core-site.xml 告诉它“NameNode 在哪、临时文件放哪”,hdfs-site.xml 告诉它“副本数多少、元数据放哪”,yarn-site.xml 告诉它“资源调度器在哪、NodeManager 怎么配合”,mapred-site.xml 告诉它“MapReduce 任务跑在 YARN 上”。在 node01 修改完全部配置后,用 scp 同步到另外两台即可:
bash复制scp -r /usr/local/hadoop/etc/hadoop/* node02:/usr/local/hadoop/etc/hadoop/
scp -r /usr/local/hadoop/etc/hadoop/* node03:/usr/local/hadoop/etc/hadoop/
4.2 hadoop-env.sh:先让 Hadoop 找到 Java
在 $HADOOP_HOME/etc/hadoop/hadoop-env.sh 文件里,能找到 JAVA_HOME 相关配置。直接修改为:
bash复制export JAVA_HOME=/usr/local/jdk1.8.0_202
有些版本的 Hadoop 会默认写 export JAVA_HOME=${JAVA_HOME},这表示它会直接读取系统环境变量。理论上如果系统环境变量已经配好,不修改也能跑。但我在实际搭建中仍然建议显式写死 JAVA_HOME,因为你无法确定所有节点是不是都在同一个用户下配置过 /etc/profile,显式写死能减少节点间不一致。
4.3 core-site.xml:整个集群的“中心地址”
core-site.xml 决定了客户端和 DataNode 如何找到 NameNode。修改 $HADOOP_HOME/etc/hadoop/core-site.xml:
xml复制<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://node01:8020</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/usr/local/hadoop/tmp</value>
</property>
</configuration>
fs.defaultFS 是全集群的默认文件系统地址。以后你在任何节点执行 hdfs dfs -ls /,客户端都会先去问 node01 的 NameNode 要文件列表。
hadoop.tmp.dir 一定要自定义,千万不能依赖 Hadoop 默认的 /tmp 目录。原因是 Linux 系统重启后可能自动清理 /tmp,一旦 NameNode 的元数据存在 /tmp 下,重启机器后整个元数据会丢失,表现得像集群突然“失忆”。我把这个目录放在 Hadoop 安装目录内部,看起来简单,但能避免系统清理问题。
4.4 hdfs-site.xml:副本数、元数据路径、辅助节点
下面是一份可以照抄的 hdfs-site.xml:
xml复制<configuration>
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/usr/local/hadoop/data/namenode</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/usr/local/hadoop/data/datanode</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>node02:9868</value>
</property>
</configuration>
这几个字段分别代表:
dfs.replication:每个数据块保留几个副本。三台 DataNode 都启动时设为 3 没问题。如果你只准备了两台机器,要改成 2,否则第三个副本会一直缺失,Web UI 上能看到警告。dfs.namenode.name.dir:NameNode 的元数据保存目录,对应主节点本地磁盘。这个目录一定不要放在 /tmp。dfs.datanode.data.dir:DataNode 实际存放数据块的目录,每个从节点各存一份配置文件,但目录内容是各自独立的。dfs.namenode.secondary.http-address:指定 SecondaryNameNode 在哪个节点运行、监听什么端口。在完全分布式里,SecondaryNameNode 应该和 NameNode 分开,所以我习惯把它放在 node02 上。
关于 SecondaryNameNode,几乎所有初学者都会误解它是“备用的 NameNode”,这是个要命的误区。它不会在 NameNode 挂掉后自动接管,只是定期从 NameNode 拉取元数据镜像和操作日志,合并后写回,让 NameNode 重启时恢复更快,本质上是个“检查点辅助进程”。
4.5 yarn-site.xml 与 mapred-site.xml:接力棒不能丢
修改 yarn-site.xml:
xml复制<configuration>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>node01</value>
</property>
<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>
</configuration>
yarn.resourcemanager.hostname 告诉所有 NodeManager,资源调度中心在 node01。如果你填的是 localhost,每个节点都会认为自己是调度中心,那 YARN 集群就散架了。yarn.nodemanager.aux-services 在早期版本的 Hadoop 2.x 中不是必填项,但在 Hadoop 3.x 中如果漏配,MapReduce 任务即使能被接收,也会一直停留在 ACCEPTED 状态,容器始终无法启动,后面测试 WordCount 时会非常难排查。
mapred-site.xml 则负责告诉 MapReduce 框架,要把计算任务提交到 YARN 上执行。新建 $HADOOP_HOME/etc/hadoop/mapred-site.xml:
xml复制<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
4.6 workers 文件:决定 DataNode 在哪台机器启动
在 Hadoop 3.x 中,决定“哪些节点作为 DataNode 和 NodeManager 启动”的文件叫 workers,位于 $HADOOP_HOME/etc/hadoop/workers;如果你参考的是 Hadoop 2.x 的教程,这个文件名可能是 slaves。文件名差异就是旧教程导致踩坑的高发点。
编辑 workers 文件,把三台机器都写进去:
bash复制node01
node02
node03
有些架构会把主节点 node01 从 workers 里摘出去,让 DataNode 只运行在纯从节点上。但零基础完全分布式阶段,我建议三台机器都参加工作,这样副本数为 3 时每个数据块都能均匀落在三台节点上,Web UI 上显示三个 Live Nodes,排查问题时更容易直观判断是否成功。
把配置文件同步到 node02 和 node03 后,不要急着格式化启动。先花两分钟在三台机器上分别执行 hadoop version,确认 Hadoop 能正常读取到 Java 环境,然后检查一下各节点 $HADOOP_HOME/etc/hadoop/ 目录下的文件内容和 node01 是否完全一致。如果你有一个字段忘了同步,节点间配置不一致的表现会非常混乱。
5. 格式化、启动与验证:最容易踩坑的三十分钟
5.1 格式化之前,先问自己是不是第一次
在主节点 node01 上执行:
bash复制hdfs namenode -format
格式化的本质是初始化 NameNode 的元数据存储目录,生成 fsimage 和集群唯一标识 clusterID。DataNode 首次启动时会从 NameNode 拿到同一个 clusterID,之后双方都按这个 ID 识别彼此。
最让人头疼的问题是:“我已经成功启动过一次集群,但后来重启失败了,我能不能重新格式化?”
不能。如果你重复格式化,NameNode 会生成新的 clusterID,旧 DataNode 上已经保存的 clusterID 和新的不一致,DataNode 启动时会被 NameNode 拒绝,表现就是“NameNode 活着,但 DataNode 全部掉了”。我第一天的故障就是这么来的。如果一定要重置集群学习环境,正确做法是清理所有节点上 dfs.namenode.name.dir 和 dfs.datanode.data.dir 目录下的数据文件,再重新格式化:
bash复制# 在主节点 node01 上
rm -rf /usr/local/hadoop/data/namenode/*
# 在所有节点上
rm -rf /usr/local/hadoop/data/datanode/*
# 保证三台机器的 /usr/local/hadoop/tmp 里没有残留
# 最后只在 node01 上重新格式化
hdfs namenode -format
格式化时会有两次以上需要输入 Y 确认,一般手动输入即可。不要用管道 echo Y | hdfs namenode -format 那种写法,因为生产环境中误操作的概率会大大增加。
5.2 启动顺序:先 HDFS,再 YARN
在 node01 上执行:
bash复制cd /usr/local/hadoop
sbin/start-dfs.sh
sbin/start-yarn.sh
start-dfs.sh 会启动 NameNode、所有 DataNode,以及 SecondaryNameNode。启动过程中日志会滚动显示通过 SSH 连接到哪些机器。如果看到 Permission denied (publickey),说明免密配置有问题,回第 3 节检查。
启动后,逐一在节点上执行 jps,看 Java 进程是否和预期一致:
| 节点 | 预期进程 |
|---|---|
| node01 | NameNode、ResourceManager、DataNode、NodeManager |
| node02 | DataNode、NodeManager、SecondaryNameNode |
| node03 | DataNode、NodeManager |
提醒一下,jps 不是某个 Hadoop 进程,它是 JDK 自带的小工具,专门查看当前机器上有哪些 Java 进程。如果 node02 的 jps 里出现了 NameNode,说明配置中 fs.defaultFS 或者 worker 文件路径有误,NameNode 在错误的节点上被拉起了,需要立即查看日志并修正。
5.3 用浏览器验证:不要只看 jps
jps 只是第一步,我建议再打开浏览器验证集群状态。在宿主机浏览器访问:
- NameNode Web UI:
http://192.168.10.101:9870 - YARN Web UI:
http://192.168.10.101:8088
如果访问不了,先确认宿主机和虚拟机网络能互通,在宿主机命令行执行 ping 192.168.10.101。如果 ping 不通,多半是 VMware 网络模式或防火墙问题,先解决网络,再谈集群。
NameNode 页面里的 Datanodes 标签页能看到 Live Nodes 数量,正常应为 3 个,每个节点都有独立的 IP 和 Rack 信息。YARN 页面里的 Active Nodes 同样应是 3。很多教程验证到启动成功就结束了,但 Web UI 这一步能帮你提前发现“DataNode 进程起来了但没注册成功”“内存不足导致节点反复离线”这类隐藏问题。
5.4 停止与重启的正确姿势
停止时不要一个个手动 kill 进程,而是:
bash复制cd /usr/local/hadoop
sbin/stop-yarn.sh
sbin/stop-dfs.sh
停止顺序和启动顺序反过来。Hadoop 的 NameNode 启动时不会自动格式化,只要你不乱删 dfs.namenode.name.dir,每次停止再重新启动都是安全的。我发现不少初学者在学习过程中喜欢直接关闭虚拟机快照,下次打开后集群进程不见了,又去重新格式化,结果陷入格式化循环。正确做法是:保存一个“格式化完成且成功启动”的虚拟机快照,下次实验前从快照启动,集群进程用脚本正常启动。
6. 让数据真正流转起来:HDFS 上传与 WordCount 测试
6.1 在 HDFS 里建目录、上传文件
启动成功只代表进程都活着,真正能说明集群可用的是文件上传和计算任务跑通。先建好目录并上传一个测试文件:
bash复制hdfs dfs -mkdir -p /user/root/input
echo "hello hadoop world" > /tmp/test.txt
hdfs dfs -put /tmp/test.txt /user/root/input/
hdfs dfs -ls /user/root/input/
执行 hdfs dfs -put 后,数据会按配置写入多个 DataNode。你可以去 node02 和 node03 的 /usr/local/hadoop/data/datanode 目录下找到名为 blk_ 开头的一系列文件,这些是真实的数据块文件和校验文件。如果一个文件同时出现在多台 DataNode 上,说明副本机制在工作。
6.2 跑官方自带 WordCount 例子
Hadoop 发行版自带一个示例 JAR,包含了 WordCount、PI 等经典程序。进入 $HADOOP_HOME/share/hadoop/mapreduce 目录,找到名字形如 hadoop-mapreduce-examples-3.3.6.jar 的文件,然后执行:
bash复制cd /usr/local/hadoop
hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /user/root/input /user/root/output
执行完先看屏幕日志尾部有没有 SUCCEEDED。接着查看结果:
bash复制hdfs dfs -cat /user/root/output/part-r-00000
如果输入文件里那句 “hello hadoop world” 被正确统计成单词计数,就说明 HDFS 和 YARN 都已经真正工作了。
这里有三个容易卡住的点。
第一,输出目录 /user/root/output 不能提前存在。MapReduce 框架在做任务提交时会创建输出目录,如果目录已存在,会直接报 FileAlreadyExistsException。第二次重跑时把 output 改成别的名字,或先删掉旧目录。
第二,如果任务一直停留在 ACCEPTED 状态,优先检查 yarn.nodemanager.aux-services 是否正确配置,并且用 yarn node -list 查看 NodeManager 是否全部注册。
第三,如果 NodeManager 注册了但任务仍失败,需要去 $HADOOP_HOME/logs/userlogs 目录查找对应 application 下的日志,Container 真正的报错信息通常在 stdout/stderr 文件里,而不是在控制台日志里。
6.3 我想验证文件是不是真的分散存储了
想知道某个文件占用了几个数据块、每个块存在哪些节点,可以执行:
bash复制hdfs fsck /user/root/input/test.txt -files -blocks -locations
输出会列出文件的 block 列表,以及每个 block 的存放节点。看到同一个 block 出现在三台不同的 DataNode 上,你对“分布式存储”会有远比任何概念解释都深刻的理解。学习阶段想强制制造“数据被切块”的效果,可以把默认块大小调小,但我不想推荐修改块大小,因为 HDFS 默认 128MB 是有性能和元数据开销考量的,学习环境的文件太小,块就是一个,观察效果有限。
6.4 MapReduce 跑在 YARN 上的完整链路
通过 WordCount 跑通后,你可以回看整个作业的执行过程:客户端把 jar、输入路径、输出路径提交给 ResourceManager;ResourceManager 选择一个 NodeManager 启动 ApplicationMaster;ApplicationMaster 再向 ResourceManager 申请容器资源,并把 Map 和 Reduce 任务分发到不同节点的 NodeManager 上执行。这时你打开 YARN UI 的 Applications 页面,能看到一个完整的 Application 历史记录,点击进去能看到每个 Task 的日志入口。
对零基础来说,这一条链路的价值在于,它让你明白 Hadoop 不是只有 HDFS,YARN 才是真正干活的调度器。之前配置的每一个字段,最终都落实到了这条链路的某个环节上。
7. 启动失败排查链路与几个高频问题实录
7.1 不要凭感觉猜,先按日志定证据
没有人能保证搭建过程一次成功。我把遇到故障时的排查顺序总结成一条固定链路,你可以直接照着做:
- 先确认执行启动命令的节点是 node01,而不是在 node02 上随意执行了
start-dfs.sh。 - 在启动命令输出中,观察 SSH 登录到哪些节点。如果某个节点没有出现,去对应节点手动执行
jps,确认进程有没有被拉起。 - 打开
$HADOOP_HOME/logs目录,查看与失败进程对应的日志文件。例如 NameNode 有问题看hadoop-root-namenode-node01.log,DataNode 有问题看hadoop-root-datanode-node02.log。 - 在 Web UI 上对比 Active/Live 节点数量,确认是进程没起来,还是起来后没注册成功。
日志文件名的规律很好记:hadoop-用户名-进程名-主机名.log。看到进程名和主机名对不上,基本能快速判断错误是出在配置还是环境。
7.2 案例一:DataNode 全部掉线,原因是 clusterID 不一致
如果你遇到 NameNode 正常,但所有 DataNode 启动后又在 Web UI 上消失,大概率是重复格式化导致 clusterID 不匹配。NameNode 在初次格式化时会给集群生成一个 clusterID,DataNode 第一次启动后也会保存这个 ID。再次格式化会重新生成 ID,旧 DataNode 里的 ID 对不上,于是注册失败。
查看 DataNode 日志,通常会出现类似 Incompatible clusterIDs 的报错。解决方式我前面已经写过:清空所有节点 dfs.namenode.name.dir 和 dfs.datanode.data.dir 下的内容,重新格式化。但这个操作会导致已有 HDFS 数据全部丢失,所以只要是能跑的集群,绝不要在正常运行期间随便格式化。
7.3 案例二:jar does not exist or is not a normal file
这个报错出现频率非常高,尤其是照着老教程敲命令时。它不是你 Hadoop 没装好,而是执行 hadoop jar 时后面跟的 JAR 包路径不存在。可能原因有三种:
- 教程里的 Hadoop 版本是 2.x,但本地装的是 3.x,JAR 包名里版本号对不上;
- 目录路径写错,把
share/hadoop/mapreduce写成了share/hadoop/mapred; - 下载的是源码包,不是二进制发行包,share 目录下根本没有 examples JAR。
解决方法是先进入目录,用 ls -l 查看实际文件名,再复制完整路径。不要猜路径,不要照抄别人命令行里的版本号,直接看本地文件才是最快的。
7.4 其他两类高频坑:权限与剩余空间
HDFS 里的权限模型默认和 Linux 很像。你在 root 用户下上传的文件,换成普通用户执行 hdfs dfs -ls 时可能看不到或无权访问。学习阶段统一使用 root 或统一创建一个 hadoop 用户执行所有命令,不要混用用户,否则你会陷入大量 Permission denied 的困惑。
另一个容易被忽略的问题是磁盘空间。DataNode 的数据目录所在磁盘如果满了,节点会被标记为不健康,Web UI 上能看到。可以在每台节点上执行 df -h 检查磁盘使用率,学习环境中预留 10GB 到 20GB 空闲空间给 HDFS 的临时数据和日志比较稳妥。
7.5 完全分布式搭建好之后,别急着直接上生产架构
写到这,你已经拥有一个能启动、能上传文件、能跑 MapReduce 的完全分布式集群。但从“成功搭建”到“真正能应对故障”,中间还差几段路。
副本数调整实验。尝试把 dfs.replication 改成 2,重启后观察 DataNode 会不会自动删除多余副本;改成 4,观察不会立即报错,但 Web UI 会一直提示副本不足。这能帮你理解 HDFS 的副本管理机制是“尽力达到目标”。
数据节点扩容实验。在 VMware 里再克隆一台 node
