最近接了个活,需要在一台开发机上快速验证一套 MapReduce 业务逻辑,生产集群动不了,公司也没有现成的多机测试环境。我当时的决定很干脆:搭一个 Hadoop 伪分布式测试环境。别小看这个“伪”字,它意味着 NameNode、DataNode、ResourceManager、NodeManager 这些角色全部跑在同一台机器上,官方叫 Pseudo-Distributed Mode,介于本地模式和全分布式模式之间。
说实话,网上关于 Hadoop 伪分布式的教程已经多到溢出了,但我翻了一遍发现几个通病:要么版本太老,端口还停留在 50070、9001 这种远古阶段;要么只给命令不给原理,照着抄一遍然后迷茫;要么上来就让你用 root、全默认配置,结果任务一跑就内存爆掉,DataNode 起不来还找不到原因。这篇文章我不会只贴命令,而是把每个关键配置、每个启动步骤背后的逻辑讲清楚,顺便把我踩过的坑都标出来。核心内容以 Hadoop 3.3.6 为基准,版本差异我会单独说明。
适合谁看?刚开始接触 Hadoop 的初学者、正在做课程设计需要在单机环境跑通完整流程的学生、以及临时需要一套可复现测试环境来验证业务代码的开发者。看完之后,你应该能从空目录开始,一步步搭出可用环境,跑通 WordCount,并且知道出了问题去哪看、怎么看。
1. 伪分布式到底在“测”什么:先搞清定位再动手
1.1 三种运行模式的关系,别把伪分布式当“简化版”
Hadoop 官方定义了三种运行模式,很多人把它们理解成“低级、中级、高级”的递进关系,这个理解方向没问题,但容易忽略本质区别。
本地模式(Local Mode)是 Hadoop 安装好之后默认的模式,它不启动任何守护进程,不需要配置 HDFS 和 YARN。当你直接运行hadoop jar xxx.jar input output时,MapReduce 会在本地 JVM 里以单进程方式执行,输入输出走本地文件系统。这种模式的好处是零配置、跑得快,适合验证算法逻辑本身,但缺点很明显:完全没有分布式概念,HDFS 读写、YARN 调度、shuffle 机制这些都体会不到。
伪分布式模式(Pseudo-Distributed Mode)则完全不同。它把每个守护进程作为独立 Java 进程启动,进程之间通过端口通信,HDFS 和 YARN 都是真实工作的。所谓“伪”,只是物理上所有进程挤在同一台机器上。通信机制、配置项、协议栈、日志格式,和全分布式完全一致。拿排戏来类比:本地模式是在脑海里对台词,伪分布式是全体演员挤在一个房间里排练走位,全分布式才是正式剧场里各就各位。
全分布式(Fully Distributed Mode)就是在多台机器上分别部署角色,一般至少三台起步,涉及机架感知、跨节点网络传输、多副本分布等更复杂的机制。
伪分布式真正的价值在于:它是从“单机跑个 jar”到“多机集群调度”之间那个必经的过渡层。你在这套环境里养成的配置习惯、排错思路,迁移到真实集群时基本都能沿用。
1.2 伪分布式能测什么,测不了什么
搞清楚边界的最大好处,是避免你把测试结论误用。伪分布式能覆盖的范围包括:
- HDFS 的完整读写链路:客户端通过 fs.defaultFS 与 NameNode 交互,写数据时流式写入 DataNode,读数据时按块读取。
- MapReduce 任务的完整生命周期:Job 提交、ApplicationMaster 启动、Map 阶段、Shuffle、Reduce 阶段、结果写回。
- YARN 的资源调度逻辑:Container 的申请、分配和释放。
- 配置文件和启动脚本的正确性:你改的任何参数,在这套环境里的生效方式和真集群一致。
- 业务代码的粗粒度验证:数据量不大时,跑通 MapReduce 任务逻辑没有问题。
测不了或者测不准的也很明确:
- 多节点数据分布和机架感知:单机上没有网络拓扑可言。
- 副本容错:伪分布式里副本数可以设成 3,但三份副本都在同一块磁盘上,数据节点挂掉的场景无法真实复现。
- 高并发与资源隔离压力:NodeManager 在单机上的资源上限就在那儿,压测结论没有参考性。
- HA 高可用:那需要多台 NameNode 加 ZooKeeper 组成,伪分布式根本不涉及。
我的建议是:功能验证、机制学习、课程设计、代码冒烟测试,伪分布式完全够用。但如果任务是评估生产容量或验证 HA 切换策略,那就老老实实搭真正的集群,或者直接用云厂商的托管集群,别在这个环境上浪费时间。
1.3 版本选型的底线建议
我见过不少教程还在用 Hadoop 2.7.x,然后配套 JDK 7,这种组合在今天不仅维护困难,而且和主流生态脱节。在 3.x 时代,推荐选 3.3.x 分支(比如 3.3.6),原因很直接:3.x 默认端口变了(NameNode Web UI 是 9870,不再是 50070)、支持了 EC 纠删码、YARN 的 Timeline Service 也更完善。跟着新版本学习,至少不会在起步阶段就接触已经废弃的配置项。
对应的 JDK 用 Oracle JDK 8 或 OpenJDK 8 是最稳的,Hadoop 3.x 也支持 JDK 11。但我不建议为了追求“新”去用 JDK 17 以上版本,Hadoop 部分模块在 Java 17 下会碰到模块化相关的权限问题,属于给自己找不痛快。
硬件底线方面,内存是最关键的。伪分布式至少 4G 内存才算舒服,2G 也不是不能跑,但必须在 YARN 配置里把容器内存调得很小,否则 WordCount 都会 OOM。磁盘预留 10 到 20G 足够,毕竟测试数据量不会太大。操作系统建议直接用 CentOS 7、Rocky Linux、Ubuntu 20.04 这类 Linux 发行版,Windows 原生跑伪分布式的坑属于“可以跑但没必要”,后面我会单独讲 Windows 开发机怎么和伪分布式配合。
2. 环境准备里的隐形坑:JDK、SSH 与运行用户
2.1 JDK 版本不是越高越好,装了还要指向对
Hadoop 本身是用 Java 写的,所以 JDK 是它的底座。大多数教程会让你“安装 JDK、配置 JAVA_HOME”,然后就没有然后了,但实际执行时最容易栽在两个地方:一是 JDK 装错了目录,二是在 hadoop-env.sh 里没写清楚 JAVA_HOME,导致脚本用系统默认 Java 时出现诡异错误。
安装步骤不复杂,CentOS 系可以直接:
yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel注意必须装-devel版本,否则缺少javac等编译工具,后续打包代码或跑部分示例会报错。装完后找到路径:
update-alternatives --config java把返回的路径记下来,通常是/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.x86_64。然后编辑hadoop-env.sh,显式设置:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.x86_64为什么非要显式写一次?因为 Hadoop 的启动脚本在读取 JAVA_HOME 时,有自己的解析逻辑,它和系统 PATH 里的 java 不一定一致。如果系统默认的java指向的是 JRE,而不是 JDK 根目录,脚本在一些需要搜 rt.jar、tools.jar 的场景下会直接失败。与其排查这种低级问题,不如一开始就把环境变量写死。
最后执行hadoop version验证。如果能看到 Hadoop 3.3.6 以及 Java 版本信息,说明环境变量 OK。
2.2 SSH 免密登录:只有一台机器也要配
这是伪分布式里最高频的初学踩坑点。很多人在配置 HDFS 时,一步到位的命令是start-dfs.sh,然后脚本就卡住或者报 SSH 连接错误。原因在于:start-dfs.sh 脚本会通过 SSH 协议“登录”到core-site.xml里配置的节点,然后把守护进程远程启动起来。即使节点就是 localhost,脚本也照样走一遍 SSH 流程。
这就好比你发快递给自己,快递员也要先接单、取件、再配送,流程不会因为你收件人和寄件人相同就省略。
配置免密的完整流程:
# 切换到要运行 Hadoop 的用户,比如 hadoop su - hadoop ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # 验证 ssh localhost-P ''表示密钥不带密码短语,这是脚本自动化登录的前提。如果生成密钥时手贱输入了密码短语,后面 ssh 登录依然要交互输入,一样会卡住。
如果ssh localhost提示 Connection refused,先启动 sshd 服务;如果提示 Host key verification failed,删掉~/.ssh/known_hosts里对应的旧记录,或者在生成密钥前先执行一下ssh-keyscan -H localhost >> ~/.ssh/known_hosts。这些细节不解决,后面的所有启动都会连环失败。
2.3 独立用户跑测试:不是洁癖,是止损
我知道很多同学图省事,直接 root 一把梭。伪分布式测试环境里,短期看 root 确实能绕开一部分命令权限问题,但代价是你后续排查问题时,日志里会有大量 “Permission denied” 和诡异的目录归属错误,你根本分不清是配置问题还是权限问题。
我习惯的做法是新建独立用户:
useradd -m hadoop passwd hadoop然后把 Hadoop 安装目录的属主改给这个用户:
chown -R hadoop:hadoop /usr/local/hadoop以后所有操作都切到hadoop用户下执行。这样做的好处有三个:一是日志目录的所有权限清晰可控;二是 /tmp 下由 Hadoop 自动生成的临时文件不会和 root 的其他文件混在一起;三是将来这个环境被销毁时,删除/home/hadoop和/usr/local/hadoop就是全部清理,不会有系统文件被我误删。
3. 从零手写四个 XML:一行行拆解配置意图
3.1 下载与目录规划
下载 Hadoop 可以从 Apache 官方镜像站找一个离你最近的源,选择二进制压缩包hadoop-3.3.6.tar.gz。版本号以你实际拉到的为准,3.3.x 之间小版本差异不影响本文中的配置。
解压安装:
tar -zxvf hadoop-3.3.6.tar.gz -C /usr/local/ ln -s /usr/local/hadoop-3.3.6 /usr/local/hadoop用软链名的好处是:将来升级 Hadoop 版本时,只要切换软链接,不用改一堆配置文件里的绝对路径。虽然测试环境很少考虑升级,但这个习惯在真实运维里非常实用。
然后规划数据目录:
mkdir -p /usr/local/hadoop/tmp/name mkdir -p /usr/local/hadoop/tmp/data chown -R hadoop:hadoop /usr/local/hadoop把 NameNode 元数据和 DataNode 数据单独放在tmp/name和tmp/data下,是有意为之的。之所以不放在 Hadoop 安装目录里,是因为测试环境经常需要整体重置集群,只要把 tmp 目录删掉再重建,就能做到“干净复位”。如果把数据目录混在安装目录中,重置时很容易误删可执行文件。
配置环境变量,编辑~/.bashrc:
export HADOOP_HOME=/usr/local/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.x86_64注意 PATH 里要同时包含 bin 和 sbin。bin下是hdfs、yarn、hadoop这类客户端命令,sbin下是start-dfs.sh、stop-yarn.sh这类启停脚本。只配一个的话,你会发现某个命令找不到。
3.2 core-site.xml:集群的“总入口”
core-site.xml是 Hadoop 核心配置,里面最关键的属性是fs.defaultFS。它决定了 Hadoop 客户端默认连接的 HDFS 地址,可以理解成整个集群的根入口。
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration>9000 是 NameNode 的 RPC 端口。客户端执行hdfs dfs -ls /时,会通过这个端口向 NameNode 发起元数据请求。Hadoop 2.x 和 3.x 的 RPC 端口默认都是 9000,这点没有变化。
为什么这个配置这么重要?因为如果你不配fs.defaultFS,Hadoop 默认走本地文件系统,hdfs dfs命令会尝试访问一个本地文件系统,很多新手装了 Hadoop 之后发现所有hdfs命令报错说“找不到文件”,原因就是这里没有指向 HDFS。配置完成后,hdfs命令才真正成为“HDFS 客户端”。
3.3 hdfs-site.xml:单副本测试的取舍
hdfs-site.xml里重点配置三件事:副本数、NameNode 元数据目录、DataNode 数据目录。
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/usr/local/hadoop/tmp/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/usr/local/hadoop/tmp/data</value> </property> </configuration>为什么副本数配 1?伪分布式只有一台机器,配 3 的结果就是在同一台机器的不同目录里塞三份相同的数据,既没有容错意义,又白白浪费磁盘。更麻烦的是,有些 Hadoop 版本在单机多副本场景下会抛出“Not replicated yet”的警告,反而干扰学习。测试环境里,副本数=1 是行业默认操作。
为什么显式指定 name.dir 和 data.dir?Hadoop 默认的元数据路径是/tmp/hadoop-${user.name}。这个路径有隐患:系统定时清理 /tmp 时,你的集群元数据可能突然蒸发,下次重启直接进入救火状态。显式指定目录,既是好习惯,也是给后续排查 clusterID 不匹配问题留出开关,具体我在第 6 章讲。
3.4 mapred-site.xml 与 yarn-site.xml:计算框架如何挂到资源调度上
mapred-site.xml里最关键的配置是计算框架名称:
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>如果不配这个属性,MapReduce 默认以 local 模式运行,任务不会提交给 YARN,而是直接在本地跑。表面上 WordCount 一样出结果,但你打开 YARN 的 8088 页面时,会发现上面空空的,根本没有任务记录。很多人以为“任务跑通了就是对的”,其实跳过了整个资源调度环节。把 framework 名字配成 yarn,是让 MapReduce 任务真正走进 YARN 的关键。
然后是yarn-site.xml:
<configuration> <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>aux-services 是 NodeManager 提供给 MapReduce 框架的辅助服务,其中mapreduce_shuffle负责 Map 端输出数据的 Shuffle 和排序。如果不配置,任务提交后会在 ApplicationMaster 启动阶段直接失败,日志里出现找不到 ShuffleHandler 之类的报错。
如果机器内存偏小,我强烈建议同时加上资源限制参数:
<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>2048</value> </property> <property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>256</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>2048</value> </property>然后在 mapred-site.xml 里同步限制容器内存:
<property> <name>yarn.app.mapreduce.am.resource.mb</name> <value>512</value> </property> <property> <name>mapreduce.map.memory.mb</name> <value>512</value> </property> <property> <name>mapreduce.reduce.memory.mb</name> <value>512</value> </property>这套参数在 2G 内存的机器上尤其重要。默认配置下,YARN 会给容器申请 1G 以上的内存,资源不足时 Container 会被直接 kill,任务报错 OOM。调小之后虽然速度慢一点,但至少能稳定跑完测试。
3.5 环境变量与 hadoop-env.sh 的细节
hadoop-env.sh位于$HADOOP_HOME/etc/hadoop/,脚本里有很多环境变量预设,但真正必须动的是 JAVA_HOME。即使你已经在系统层面配置了 java,也建议在这里写一遍:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.x86_64 export HADOOP_HOME=/usr/local/hadoop另外,Hadoop 3.x 的运行脚本默认把 PID 写到/tmp,这在重启时偶尔会因为 PID 文件过期导致误判进程已存在。如果你不想遇到这种问题,可以在 hadoop-env.sh 里统一设置:
export HADOOP_PID_DIR=/opt/hadoop/pids mkdir -p $HADOOP_PID_DIR这一步非必须,但我在多次测试中吃过 PID 文件残留的亏,写在这里给你避雷。
4. 格式化、启动与健康检查:稍不注意就留隐患
4.1 格式化 NameNode:只能做一次的动作
首次启动 HDFS 之前,必须格式化 NameNode:
hdfs namenode -format格式化的本质是初始化 NameNode 的系统目录,生成current/VERSION文件,里面包含一个全局唯一的clusterID。之后 DataNode 启动时会读取自己数据目录里的 clusterID,并跟 NameNode 的 clusterID 比对,不一致就拒绝注册。
所以这里有一条铁律:不要反复执行格式化命令。每一次 format 都会生成新的 clusterID,但已经生成了数据的 DataNode 目录里的 clusterID 并不会自动跟着变,结果就是 NameNode 正常起来了,DataNode 却反复报IncompatibleClusterIDException,整个集群看起来半死不活。
如果确实需要重置集群,正确做法是:先停掉所有进程,然后删除tmp/name和tmp/data目录,再重新执行格式化。这样 NameNode 和 DataNode 拿到的是同一套新 ID,才能愉快地合作。
format 命令执行成功时,屏幕上会出现 “Storage directory /usr/local/hadoop/tmp/name has been successfully formatted” 这样的提示。没看到这句话,说明格式化失败,先解决报错再继续,不要稀里糊涂往下跑。
4.2 首次启动的顺序与日志观察法
格式化完成后,按照固定顺序启动:
start-dfs.sh start-yarn.sh先启 HDFS,再启 YARN,这是因为 YARN 上的计算任务依赖 HDFS 提供数据。虽然也有start-all.sh一把梭的命令,但我个人习惯是分开启动。分开的意义在于:两个框架各自有一份日志,分开启动能让我明确知道当前是哪个框架出了问题,而不是面对一份混合日志里找线索。
启动脚本执行完后,第一件事永远是看进程:
jps正常情况下应该看到五个进程:
- NameNode
- DataNode
- SecondaryNameNode
- ResourceManager
- NodeManager
少了哪一个,就去$HADOOP_HOME/logs/下找对应的日志。日志文件的命名规则类似hadoop-hadoop-namenode-<hostname>.log,用tail -n 200直接看末尾几百行。日志是你判断问题的唯一权威依据,比拍脑袋猜配置靠谱一百倍。
4.3 三分钟健康检查:jps、Web UI、文件系统
很多新手在确认进程都起来之后,就急着跑任务。我建议你花三分钟做一套健康检查,把环境的地基打牢。
第一步,已经做过:jps 确认进程齐全。
第二步,打开 Web UI 验证页面服务:
- NameNode UI:
http://localhost:9870 - ResourceManager UI:
http://localhost:8088
注意 Hadoop 3.x 的 NameNode HTTP 端口已经从 50070 改为 9870。如果你用 2.x 的习惯访问 50070,看到的只能是连接失败,然后陷入“我哪配错了”的自我怀疑。
第三步,操作文件系统验证客户端链路:
hdfs dfs -ls / hdfs dfs -mkdir -p /test能正常返回列表、创建目录,说明 HDFS 客户端与 NameNode 之间的 RPC 通信通畅,整个 HDFS 链路已经可用。如果这三步都过,基础环境就稳了,接下来跑任务只是时间问题。
4.4 端口清单与“看哪个日志”的判断
Hadoop 3.x 常用端口如下:
| 端口 | 进程 | 作用 |
|---|---|---|
| 9870 | NameNode | Web UI |
| 9000 | NameNode | RPC,客户端读写入口 |
| 9864 | DataNode | HTTP UI / 数据传输 |
| 9866 | DataNode | RPC |
| 8088 | ResourceManager | YARN Web UI |
| 8032 | ResourceManager | RPC,ApplicationMaster 调度 |
| 8042 | NodeManager | 日志与 UI |
| 19888 | JobHistory | MapReduce 历史任务 UI |
排查时记住这个顺序:先确认对应进程在不在,再确认端口通不通,最后看日志。具体来说:
- 客户端连不上 HDFS,先检查 9000 端口是否监听。
- HDFS 网页打不开,检查 9870。
- 任务提交后一直卡着不动,去 8088 看是不是 Application 一直处于 PENDING。
- 任务运行中某个 Task 反复失败,去 NodeManager 的日志目录抓详情。
这套排查链路建立起来之后,你会发现绝大多数问题在 10 分钟内就能定位,而不是在配置文件和网上搜索里无限循环。
5. 跑通一个完整的 MapReduce 测试:从 WordCount 到排错
5.1 用 hdfs dfs 完成基本文件验证
进入测试环节,先在本地准备一个小文件,然后传到 HDFS 上:
mkdir -p ~/hadoop-test echo "hello hadoop hello scala hadoop hdfs" > ~/hadoop-test/test.txt hdfs dfs -mkdir -p /user/hadoop/input hdfs dfs -put ~/hadoop-test/test.txt /user/hadoop/input/ hdfs dfs -cat /user/hadoop/input/test.txt这里提醒一个 HDFS 和 Linux 文件系统的差异:HDFS 没有 “cd 到某个目录” 的概念,你用什么路径访问,就必须写全路径。如果你习惯了 Linux 终端的管理方式,一上来执行hdfs dfs -ls input大概率会报错说找不到input。
另外,如果你用 hadoop 用户执行命令,报权限不足,多半是因为 HDFS 根目录里没有以这个用户名命名的用户目录。手动创建一下就行:
hdfs dfs -mkdir -p /user/hadoop hdfs dfs -chown -R hadoop:hadoop /user/hadoop5.2 WordCount 全流程执行记录
文件上传完成后,运行 Hadoop 自带的 WordCount 示例:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar \ wordcount /user/hadoop/input /user/hadoop/output执行过程中,你会看到一行行 job 进度输出,Map 和 Reduce 的百分比不断滚动。这里有一个容易踩的坑:输出目录/user/hadoop/output在任务执行前必须不存在。MapReduce 框架不覆盖旧输出,而是直接拒绝。如果你跑第二次,记得换一个输出目录,或者先执行:
hdfs dfs -rm -r /user/hadoop/output任务跑完后读取结果:
hdfs dfs -cat /user/hadoop/output/part-r-00000看到词频统计结果,比如hadoop 2、hdfs 1,说明整条链路已经全部打通:客户端提交 Job → YARN 分配容器 → Map 读数据 → Shuffle 排序 → Reduce 聚合 → 写回 HDFS。这一步走通之后,你可以在伪分布式上测试任何自己的 MapReduce 代码。
如果你是测试自己的业务 jar,命令格式可以换成:
hadoop jar /path/to/your-job.jar com.example.MainClass /input /output在测试阶段,把代码打成带依赖的 jar 包提交,是伪分布式环境里最直接的验证方式。
5.3 测试中最容易遇到的三种报错
第一个是FileAlreadyExistsException。报错信息里会明确说输出目录已存在。处理方式就是换目录或者删旧目录,原因前面说过了,框架设计如此,不算 bug。
第二个是Container killed on request或running beyond virtual memory limits。这个一看就是内存或虚拟内存超限,在小内存伪分布式里尤其常见。处理分两步:先调小 YARN 容器内存参数(参考 3.4 节的配置),如果还不行,就在 yarn-site.xml 里关闭虚拟内存检查:
<property> <name>yarn.nodemanager.vmem-check-enabled</name> <value>false</value> </property>第三步重启 YARN 让配置生效。注意改完 yarn-site.xml 之后,必须执行stop-yarn.sh和start-yarn.sh,只重启单个节点管理进程不吃配置。
第三种是ConnectException或call from localhost to localhost:9000 failed on connection exception。这类报错的范围很广,但排查思路不变:先jps看 NameNode 进程在不在,再netstat -an | grep 9000看端口听没听,最后检查客户端的core-site.xml有没有被正确加载。客户端问题通常是环境变量不对,服务端问题通常是数据目录权限不对,对症下药就好。
还有一种看着像权限问题实则也是权限问题的:org.apache.hadoop.security.AccessControlException: Permission denied。本质是当前 Linux 用户对 HDFS 上的目标目录没有写权限,按 5.1 里说的把用户目录建好、权限给足即可。
6. 测试收尾与进阶:停机顺序、数据残留与 Docker 化
6.1 停止顺序和数据目录清理
测试结束后,停止集群的顺序和启动相反:
stop-yarn.sh stop-dfs.sh为什么先停 YARN?因为 YARN 上可能还有 Application 正在跑,如果你先把 HDFS 停了,正在执行的 MapReduce 任务瞬间失去了文件系统依赖,会抛出一堆莫名奇妙的 IOException,日志特别难看。反过来,先停 YARN,Application 会优雅结束,HDFS 再从容退出,一切都是干净的。
如果你想彻底重置环境,把测试期间产生的一切痕迹清掉,执行:
rm -rf /usr/local/hadoop/tmp/name /usr/local/hadoop/tmp/data mkdir -p /usr/local/hadoop/tmp/name /usr/local/hadoop/tmp/data hdfs namenode -format注意:清理之前,先确认你已经拿到了所有需要的日志和测试结果。我见过有人图省事直接删掉临时目录,然后在下次启动时报错时才想起来还没下载某个关键日志,不得不重新造数据,浪费半天时间。
6.2 频繁格式化的后果:clusterID 不匹配
伪分布式里最经典、最典型的问题就是:NameNode 起来了,DataNode 却一直起不来,日志里出现IncompatibleClusterIDException或者类似 "storage directory with clusterID XXXX is incompatible with clusterID YYYY" 的提示。
根因就是我在 4.1 节说的:每次hdfs namenode -format都会生成一个新的 clusterID,而 DataNode 数据目录里的 clusterID 停留在第一次格式化时的值。两边对不上,DataNode 拒绝注册。
遇到这个问题的处理方案有两种。第一种是彻底重置:
rm -rf /usr/local/hadoop/tmp/name /usr/local/hadoop/tmp/data hdfs namenode -format start-dfs.sh用删目录重新 format 的方式,让两边都拿到新 ID,最省心。第二种是手工改 DataNode 的 VERSION 文件。先查看 NameNode 的 clusterID:
cat /usr/local/hadoop/tmp/name/current/VERSION再修改 DataNode 数据目录里current/VERSION中的 clusterID,改成 NameNode 的值,然后重启 DataNode。这种方法适用于你不想丢掉已有 HDFS 数据的场景,测试环境里一般用不上,但理解原理很重要。
6.3 Windows 下开发环境联调的思路
很多人在 Windows 上用 IDEA 写 MapReduce 代码,然后想让它直接连上一台 Linux 虚拟机里的伪分布式集群。这个场景其实不需要在 Windows 上完整安装 Hadoop,否则会遇到 winutils.exe 缺失、native 库不兼容、路径分隔符混乱等一堆破事。
更务实的做法是:在 Windows 的 Java 工程里引入hadoop-client依赖,然后在代码里把 HDFS 地址指到虚拟机的 IP 和端口,也就是把fs.defaultFS的 localhost 换成虚拟机的 IP。这样本地代码通过客户端协议连接远程集群,不用跑任何本地守护进程。
需要额外注意的事:Windows 主机访问 Linux 虚拟机里的 NameNode 时,防火墙必须放行 9000、9870、8088 等端口。我见过大量“代码没问题但连不上”的场景,最后都是防火墙和安全组挡路。
如果只是验证业务逻辑,这个方案比在 Windows 上硬装 Hadoop 舒服得多,也能保留 IDEA 的断点调试能力。
6.4 快速用 Docker 跑伪分布式的思路
如果说还有一种更快的起环境方式,那就是 Docker。社区里维护了不少单节点 Hadoop 镜像,启动一个容器进去,JDK、SSH、Hadoop 配置都已经提前弄好了。你直接执行hdfs dfs和hadoop jar就能测试。
相关热搜词里有“hadoop的docker镜像”,这确实是一条捷径,但我建议的顺序是:先手动搭一次伪分布式,再拥抱镜像。因为镜像把 SSH 免密、文件目录权限、内存资源管理这些细节都隐藏了,你只看到成功的结果,看不到背后的机制。万一容器里任务报错,你对日志目录、配置来源完全没有概念,排查起来基本抓瞎。
反过来,等你手动搭过一遍,再用 Docker 镜像,就会清晰很多:哦,原来镜像里的 NameNode 端口映射到了宿主机某个端口,原来这个目录挂载出来是拿来持久化它的数据目录的,原来容器重启后 clusterID 保留在这个 volume 里。这些认知,只有亲手搭过一遍才长得出来。
最后说点实际的感受。我见过不少同学搭伪分布式,包括我自己第一次,也是栽在 SSH 免密和反复格式化这两个问题上。这两个问题的本质,都是对“脚本会通过 SSH 启动本地进程”和“format 会生成新的 clusterID”这两个底层机制没有概念。把这两件事想清楚,剩下的步骤其实非常固定:准备环境、改四个 xml、格式化、启动、看 jps、跑 jar、看日志。等你从头到尾完整跑通一次 WordCount,再把 DataNode 日志翻上几页,后面接触真正的多机集群时,心里会踏实很多。