1. 为什么还要回头啃 Hadoop 环境搭建这块硬骨头
大数据入门这条路上,有个特别有意思的现象:很多人能跟你聊明白 MapReduce 的 Shuffle 过程,能画得出 HDFS 的架构图,但真让他从零把一套本地开发环境跑起来,立马卡壳。我见过太多这样的情况——笔记记了一大本,真到动手的时候,连 NameNode 都起不来。
这次要聊的,就是知识点回顾加 Hadoop 开发环境搭建,再配合 Eclipse 集成 Hadoop 插件这一整套流程。说白了,这是把前面学过的 HDFS、MapReduce、YARN 这些零散概念,通过一个能跑起来的本地环境串成一条线。你光看视频、光看书,那些知识点是散的;只有当你自己配好环境,写一个 WordCount 跑通,看到控制台输出结果的那一刻,这些概念才算真正落地。
这篇文章适合谁看?如果你是刚学完 Hadoop 基础理论、准备动手做实验的学生,或者是从其他语言转过来、第一次接触大数据生态的开发者,再或者你之前搭过但总是出各种莫名其妙的错,那这篇内容就是给你准备的。我会把整个搭建过程拆开揉碎,包括每一步为什么这么做、参数怎么定、遇到报错怎么排查,全部讲清楚。
需要提前说明的是,Hadoop 环境搭建这件事,坑非常多,而且很多坑跟你的系统环境、JDK 版本、网络配置强相关。我下面给出的方案是基于常见实践总结出来的,你实际操作时如果遇到不一致的地方,先别慌,大概率是环境差异,按我给的排查思路走就行。
2. 动手之前先把知识点串一遍
2.1 Hadoop 三大核心组件到底各自管什么
在动手配环境之前,我觉得有必要把 Hadoop 的核心组件再捋一遍。不是为了背书,而是因为你后面配的每一个配置文件,都跟这些组件的职责直接相关。你搞不清楚谁管谁,配置文件里那些参数你就是在瞎填。
HDFS,分布式文件系统,负责存数据。它的核心角色有两个:NameNode 和 DataNode。NameNode 管元数据,也就是"哪个文件被切成了哪些块、这些块分别在哪台机器上";DataNode 管实际的数据块存储。你后面配core-site.xml和hdfs-site.xml,本质上就是在告诉 NameNode 和 DataNode 它们该在哪、该怎么工作。
MapReduce,分布式计算框架,负责算数据。它把一个大任务拆成 Map 阶段和 Reduce 阶段,Map 负责分散处理,Reduce 负责汇总。你写 WordCount 的时候,Map 就是把每行文本拆成单词并计数,Reduce 就是把相同单词的计数合并。
YARN,资源调度框架,负责管资源。它决定哪个任务能用多少 CPU、多少内存。ResourceManager 是全局管家,NodeManager 是每台机器上的小管家。你配yarn-site.xml就是在告诉这两个角色怎么协作。
这三个组件的关系,你可以这么理解:HDFS 是仓库,MapReduce 是流水线,YARN 是调度中心。仓库负责存货,流水线负责加工,调度中心负责分配人力和设备。三者配合,才能完成一个完整的大数据处理任务。
2.2 为什么本地开发环境跟生产集群配置差别这么大
这里有个很多人容易混淆的点:你在本地搭的环境,跟真实生产集群的配置,完全是两码事。本地环境追求的是"能跑起来、方便调试",生产集群追求的是"高可用、高性能、容错"。
本地环境通常用伪分布式模式,也就是所有角色都跑在一台机器上。NameNode、DataNode、ResourceManager、NodeManager 全在你本机。这样做的好处是资源占用少、启动快、调试方便。坏处是你体会不到真正的分布式特性,比如数据副本、节点故障转移这些。
生产集群则是完全分布式,几十上百台机器各司其职。配置上要考虑副本因子、机架感知、高可用 NameNode、联邦等高级特性。这些在本地环境里基本用不上,配了反而增加复杂度。
所以你在本地搭环境的时候,不要照搬生产集群的配置模板。那些模板里很多参数在单机上根本跑不通,或者跑起来也是浪费资源。我下面给的配置就是针对本地伪分布式场景优化过的,你直接抄作业就行。
2.3 Eclipse 集成 Hadoop 插件解决了什么痛点
以前写 MapReduce 程序,流程是这样的:本地写好代码,打包成 jar,上传到集群,用hadoop jar命令提交,然后去日志里看结果。这个流程调试起来极其痛苦,改一行代码就要重新打包上传,效率极低。
Eclipse 集成 Hadoop 插件之后,你可以直接在 IDE 里右键运行 MapReduce 程序,就像跑一个普通的 Java 类一样。插件会自动帮你处理依赖、提交任务、拉取结果。更关键的是,你可以直接在 IDE 里打断点调试,看每个变量的值,这在排查逻辑错误的时候简直是救命稻草。
不过要注意,这个插件主要解决的是开发调试效率问题,不是功能问题。你最终提交到生产集群的任务,还是得打包成 jar 用命令行提交。插件只是让你在开发阶段少受点罪。
3. 环境搭建的完整实操流程
3.1 基础环境准备与版本选型
先说版本选型,这是第一个坑。Hadoop 生态的版本兼容性出了名的差,你随便选版本,大概率会遇到各种诡异的报错。
我的建议是:JDK 用 1.8,别用更高版本。Hadoop 2.x 和 3.x 对 JDK 1.8 的支持最稳定,JDK 11 以上会有各种模块化相关的报错,新手没必要去趟这个雷。Hadoop 版本选3.3.x系列,这个版本相对成熟,社区资料多,遇到问题好搜。Eclipse 用2020-06 之后的版本,太老的版本插件装不上。
操作系统方面,Windows 和 Linux 都可以,但 Linux(尤其是 Ubuntu 或 CentOS)踩的坑会少很多。Windows 上跑 Hadoop 需要额外装 winutils 和 hadoop.dll,还得配环境变量,麻烦得很。如果你有 Linux 虚拟机或者 WSL,强烈建议在 Linux 上搭。
环境变量这块,需要配三个:
# 在 ~/.bashrc 或 /etc/profile 里添加 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export HADOOP_HOME=/opt/hadoop-3.3.6 export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin配完之后记得source ~/.bashrc让配置生效,然后用java -version和hadoop version验证一下。如果hadoop version报错说找不到命令,八成是 PATH 没配对,或者 HADOOP_HOME 路径写错了。
提示:JAVA_HOME 的路径一定要写对,而且不要带 bin 目录。很多人写成
/usr/lib/jvm/java-8-openjdk-amd64/bin,这是错的,会导致 Hadoop 启动时报 "JAVA_HOME is not set" 的错误。
3.2 核心配置文件的逐项拆解
Hadoop 的配置文件都在$HADOOP_HOME/etc/hadoop/目录下,你需要改的主要是四个文件:core-site.xml、hdfs-site.xml、mapred-site.xml、yarn-site.xml。我一个一个说。
core-site.xml是全局配置,最关键的参数是fs.defaultFS,它指定了 HDFS 的访问地址。伪分布式环境下,你配成hdfs://localhost:9000就行。这个端口 9000 是 NameNode 的 RPC 端口,你可以改成别的,但改了之后所有引用这个地址的地方都要跟着改,所以建议就用默认的。
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/opt/hadoop-3.3.6/tmp</value> </property> </configuration>hadoop.tmp.dir这个参数很多人会忽略,但它很重要。它指定了 Hadoop 运行时产生的临时文件存放位置。如果不配,默认会放在/tmp下,而/tmp在很多系统上重启就会被清空,导致你的 NameNode 元数据丢失,下次启动就报错。所以一定要配一个固定的、不会被自动清理的目录。
hdfs-site.xml是 HDFS 专属配置。伪分布式下最关键的是dfs.replication,副本因子。生产环境一般是 3,但你就一台机器,配 3 也没意义,配成 1 就行。
<configuration> <property> <name>dfs.replication</name> <value>1</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/opt/hadoop-3.3.6/data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/opt/hadoop-3.3.6/data/datanode</value> </property> </configuration>dfs.namenode.name.dir和dfs.datanode.data.dir分别指定 NameNode 和 DataNode 的数据存储目录。这两个目录一定要提前创建好,而且要有写权限。我见过有人配了路径但没建目录,启动时报 "Permission denied",排查半天。
mapred-site.xml是 MapReduce 配置。关键参数是mapreduce.framework.name,指定 MapReduce 跑在什么框架上。伪分布式下配成yarn,表示用 YARN 来调度。
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>yarn-site.xml是 YARN 配置。最关键的是yarn.nodemanager.aux-services,指定 NodeManager 的辅助服务。这个必须配成mapreduce_shuffle,否则 MapReduce 任务跑不起来。
<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.env-whitelist</name> <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value> </property> </configuration>yarn.nodemanager.env-whitelist这个参数在 Hadoop 3.x 里特别重要。如果不配,NodeManager 启动容器时可能找不到 JAVA_HOME,导致任务失败。这个坑我踩过,报错信息很隐晦,排查了很久才发现是这个参数的问题。
3.3 格式化与启动的完整步骤
配置文件改完之后,第一步是格式化 NameNode。这个操作只在第一次搭建时做一次,之后绝对不能再做,否则你的元数据会被清空,之前存的数据全丢。
hdfs namenode -format格式化成功的话,你会看到 "Storage directory ... has been successfully formatted" 这样的输出。如果报错,大概率是目录权限问题或者路径不存在。
格式化之后,启动 HDFS:
start-dfs.sh然后启动 YARN:
start-yarn.sh启动完成后,用jps命令检查进程。正常应该看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 这五个进程。如果少了哪个,去对应的日志目录里看错误信息。日志默认在$HADOOP_HOME/logs/下。
注意:如果你在 Windows 上搭,
start-dfs.sh这些脚本是跑不了的,得用hdfs --daemon start namenode这种方式逐个启动,或者用start-all.cmd。这也是我建议用 Linux 的原因之一。
启动成功后,可以打开浏览器访问 NameNode 的 Web UI,默认地址是http://localhost:9870(Hadoop 3.x 的端口,2.x 是 50070)。能看到页面说明 HDFS 起来了。YARN 的 Web UI 默认是http://localhost:8088。
3.4 Eclipse 集成 Hadoop 插件的详细配置
Eclipse 集成 Hadoop 插件这块,是很多人卡住的地方。插件本身不复杂,但版本匹配和配置细节容易出问题。
首先,你需要一个跟 Hadoop 版本匹配的插件 jar 包。Hadoop 3.3.x 对应的插件一般是hadoop-eclipse-plugin-3.3.x.jar。这个 jar 包不是 Hadoop 自带的,需要你自己编译或者找现成的。编译方法这里不展开,网上有现成的编译好的包可以用。
拿到 jar 包之后,把它放到 Eclipse 安装目录的dropins文件夹下,然后重启 Eclipse。重启后,在Window -> Perspective -> Open Perspective -> Other里应该能看到 "Map/Reduce" 这个选项。选中它,Eclipse 会切换到 MapReduce 开发视图。
接下来配置 Hadoop 连接。在 Eclipse 底部的 "Map/Reduce Locations" 面板里右键,选 "New Hadoop Location"。弹出的对话框里填几个关键信息:
- Location name:随便起,比如 "local-hadoop"
- Map/Reduce Master 的 Host 和 Port:填
localhost和8088(YARN 的端口) - DFS Master 的 Host 和 Port:填
localhost和9000(NameNode 的 RPC 端口)
填完之后点 Finish。如果配置正确,左侧的 "Project Explorer" 里会出现一个 "DFS Locations" 的节点,展开能看到 HDFS 里的目录结构。如果展开报错,检查一下端口和 Hadoop 是否正常启动。
这里有个细节:Eclipse 插件连接 HDFS 用的是 RPC 端口 9000,不是 Web UI 的 9870。很多人填错端口,导致连不上。另外,如果你的 Hadoop 配了权限控制,可能还需要配置用户名。在 "Advanced parameters" 里找到hadoop.job.ugi,填上你的用户名。
4. 跑通第一个 MapReduce 程序验证环境
4.1 WordCount 代码的编写与关键点
环境配好了,得跑个程序验证一下。WordCount 是 MapReduce 的 "Hello World",逻辑简单但涵盖了完整的 MapReduce 流程。
在 Eclipse 里新建一个 Java 项目,把 Hadoop 相关的 jar 包加到 Build Path 里。需要的 jar 包在$HADOOP_HOME/share/hadoop/下的各个子目录里,主要是common、hdfs、mapreduce、yarn这几个目录下的 jar。嫌麻烦的话,可以把所有 jar 都加进去。
代码结构分三部分:Mapper、Reducer、Driver。
Mapper 负责把输入的每一行拆成单词,每个单词输出<word, 1>。核心代码:
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); public void map(Object key, Text value, Context context) throws IOException, InterruptedException { StringTokenizer itr = new StringTokenizer(value.toString()); while (itr.hasMoreTokens()) { word.set(itr.nextToken()); context.write(word, one); } } }Reducer 负责把相同单词的计数累加。核心代码:
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); } result.set(sum); context.write(key, result); } }Driver 负责配置任务参数并提交。关键点是设置输入输出路径、指定 Mapper 和 Reducer 类。
public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); job.setCombinerClass(IntSumReducer.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); }这里有个细节:setCombinerClass设成了 Reducer 类。Combiner 是在 Map 端做一次预聚合,减少网络传输。WordCount 这个场景下,Combiner 的逻辑跟 Reducer 完全一样,所以可以直接复用。但不是所有场景都能这么干,比如求平均值就不能用 Combiner,因为平均值不满足结合律。
4.2 在 Eclipse 中直接运行与调试
代码写好后,在 Eclipse 里右键 Driver 类,选 "Run As -> Java Application"。这时候会报错,因为没传输入输出路径参数。
你需要配置 Run Configuration。在 "Arguments" 标签页里,Program arguments 填上输入路径和输出路径。输入路径可以是本地文件系统路径,也可以是 HDFS 路径。如果填 HDFS 路径,格式是hdfs://localhost:9000/input。
输出路径必须是一个不存在的目录,否则 Hadoop 会报 "Output directory already exists" 的错误。这是 Hadoop 的保护机制,防止你误覆盖之前的结果。
运行的时候,控制台会打印一堆日志,包括任务提交、Map 进度、Reduce 进度。如果看到 "Job job_local... running" 和最后的 "completed successfully",说明跑通了。
跑通之后,去输出目录看结果。如果输出到 HDFS,可以用hdfs dfs -cat /output/part-r-00000查看。结果文件里每一行是 "单词 计数" 的格式。
调试的时候,你可以直接在 Mapper 或 Reducer 的代码里打断点。Eclipse 会停在断点处,你可以查看当前处理的 key、value,以及 context 的状态。这个能力在排查逻辑错误时非常有用,比看日志高效多了。
4.3 常见报错与排查思路
跑 WordCount 的过程中,最常见的报错有这么几个:
报错一:java.net.ConnectException: Call From ... to localhost:9000 failed
这个一般是 NameNode 没启动,或者端口配错了。先用jps确认 NameNode 进程在不在,再用netstat -tlnp | grep 9000确认端口有没有监听。如果进程在但端口没监听,可能是配置文件里fs.defaultFS写错了。
报错二:org.apache.hadoop.security.AccessControlException: Permission denied
权限问题。Hadoop 默认会检查文件权限,如果你的用户名跟 HDFS 里的目录所有者不一致,就会报这个错。解决办法是在core-site.xml里加一个配置,把权限检查关掉:
<property> <name>hadoop.security.authorization</name> <value>false</value> </property>或者用hdfs dfs -chmod -R 777 /把目录权限放开。但后者在生产环境是禁忌,本地开发无所谓。
报错三:Container exited with a non-zero exit code 1
这个报错信息很笼统,真正的原因要去 NodeManager 的日志里找。常见原因是内存不够,或者 JAVA_HOME 没配好。如果是内存问题,可以在yarn-site.xml里调大yarn.nodemanager.resource.memory-mb的值。
报错四:ClassNotFoundException
依赖 jar 包没加全。检查一下 Build Path 里是不是漏了某个 jar。最稳妥的办法是把$HADOOP_HOME/share/hadoop/下所有子目录的 jar 都加进去。
5. 实操心得与避坑指南
5.1 那些文档里不会写的经验教训
搭 Hadoop 环境这件事,文档里写的都是理想情况,实际操作中你会遇到各种文档里没提的问题。我分享几个我踩过的坑。
第一个坑:主机名解析问题。Hadoop 启动时会用主机名去解析 IP,如果你的/etc/hosts里没有配主机名到 127.0.0.1 的映射,启动会非常慢,甚至超时失败。解决办法是在/etc/hosts里加一行127.0.0.1 your-hostname。这个坑很隐蔽,因为报错信息不会直接说主机名解析失败,而是各种超时。
第二个坑:多次格式化 NameNode。前面说过,格式化只能做一次。但有时候你改了配置,启动报错,网上搜到的解决方案让你重新格式化,你一格式化,之前的数据全没了。正确的做法是:如果只是改配置,不需要重新格式化;如果确实要重新格式化,先把dfs.namenode.name.dir和dfs.datanode.data.dir指向的目录清空,再格式化。
第三个坑:Eclipse 插件的端口配置。前面提过,DFS Master 的端口是 9000,不是 9870。但很多人会搞混,因为 9870 是 Web UI 端口,浏览器能访问,就以为插件也填这个。实际上插件走的是 RPC 协议,用的是 9000。
第四个坑:Windows 下的 winutils。如果你非要在 Windows 上搭,必须下载对应 Hadoop 版本的 winutils.exe 和 hadoop.dll,放到$HADOOP_HOME/bin下,并且把$HADOOP_HOME/bin加到 PATH 里。否则会报 "Could not locate winutils.exe" 的错误。这个文件 Hadoop 官方不提供,得自己找。
5.2 性能调优的几个实用参数
本地开发环境虽然不追求高性能,但有些参数调一下,能明显提升体验。
Map 和 Reduce 的内存配置。默认情况下,每个 Map 和 Reduce 任务分配的内存比较小,跑大一点的数据集容易 OOM。可以在mapred-site.xml里调:
<property> <name>mapreduce.map.memory.mb</name> <value>1024</value> </property> <property> <name>mapreduce.reduce.memory.mb</name> <value>2048</value> </property>YARN 的容器内存上限。如果 NodeManager 分配的内存不够,任务会一直处于 ACCEPTED 状态,跑不起来。在yarn-site.xml里调:
<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>8192</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>4096</value> </property>这两个参数的含义是:NodeManager 总共能用 8G 内存,单个容器最多能申请 4G。根据你机器的实际内存调整,别配得比物理内存还大。
JVM 重用。默认情况下,每个 Map 任务都会启动一个新的 JVM,任务多了之后,JVM 启动开销很可观。开启 JVM 重用可以缓解这个问题:
<property> <name>mapreduce.job.jvm.numtasks</name> <value>10</value> </property>这个配置表示一个 JVM 最多跑 10 个 Map 任务再销毁。本地开发环境任务少,效果不明显,但养成习惯是好的。
5.3 环境搭建完成后的验证清单
环境搭完之后,别急着往下学,先做一遍完整验证,确保每个组件都正常。
| 验证项 | 操作命令 | 预期结果 |
|---|---|---|
| HDFS 进程 | jps | 看到 NameNode、DataNode、SecondaryNameNode |
| YARN 进程 | jps | 看到 ResourceManager、NodeManager |
| HDFS 读写 | hdfs dfs -mkdir /test然后hdfs dfs -ls / | 能看到 /test 目录 |
| 文件上传下载 | hdfs dfs -put local.txt /test/然后hdfs dfs -get /test/local.txt | 文件能正常上传下载 |
| Web UI | 浏览器访问localhost:9870和localhost:8088 | 页面正常显示 |
| Eclipse 连接 | 展开 DFS Locations | 能看到 HDFS 目录结构 |
| WordCount | 右键运行 Driver | 控制台输出 "completed successfully" |
这个清单全部通过,说明你的环境是健康的。任何一项不通过,就针对那一项去排查,别跳过。我见过有人 HDFS 都没起来就去跑 MapReduce,然后报一堆错,排查半天发现是基础环境的问题。
6. 从环境搭建延伸到后续学习路径
环境搭好只是第一步,接下来你要做的是在这个环境上不断练习。我的建议是,先把 HDFS 的常用命令全部过一遍,put、get、ls、mkdir、rm、cat、chmod、chown这些,每个都实际操作一下。然后写几个不同场景的 MapReduce 程序,比如去重、排序、求 Top N,体会一下 MapReduce 编程的套路。
再往后,你可以尝试把环境从伪分布式升级到完全分布式,用两三台虚拟机搭一个真正的集群。这个过程会让你对 Hadoop 的架构理解更深,也会遇到更多有意思的问题,比如网络配置、SSH 免密、时间同步这些。
Eclipse 插件这块,等你熟悉了之后,可以试试 IntelliJ IDEA 的 Hadoop 插件,或者直接用 Maven 管理依赖,用命令行提交任务。不同的工具各有优劣,多试试找到最适合自己的。
最后说一个我个人的体会:Hadoop 环境搭建这件事,第一次搭可能花一整天,各种报错各种查。但搭好之后,一定要把整个流程记下来,包括配置文件的内容、启动命令、遇到的问题和解决办法。下次再搭,或者帮别人排查问题的时候,这份记录就是你的救命稻草。我现在还保留着当年第一次搭 Hadoop 的笔记,虽然现在看起来很多地方很幼稚,但那份笔记帮我省了无数次重复踩坑的时间。