Hadoop开发环境搭建实战:Eclipse集成与WordCount调试
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 namefs.defaultFS/name valuehdfs://localhost:9000/value /property property namehadoop.tmp.dir/name value/opt/hadoop-3.3.6/tmp/value /property /configurationhadoop.tmp.dir这个参数很多人会忽略但它很重要。它指定了 Hadoop 运行时产生的临时文件存放位置。如果不配默认会放在/tmp下而/tmp在很多系统上重启就会被清空导致你的 NameNode 元数据丢失下次启动就报错。所以一定要配一个固定的、不会被自动清理的目录。hdfs-site.xml是 HDFS 专属配置。伪分布式下最关键的是dfs.replication副本因子。生产环境一般是 3但你就一台机器配 3 也没意义配成 1 就行。configuration property namedfs.replication/name value1/value /property property namedfs.namenode.name.dir/name value/opt/hadoop-3.3.6/data/namenode/value /property property namedfs.datanode.data.dir/name value/opt/hadoop-3.3.6/data/datanode/value /property /configurationdfs.namenode.name.dir和dfs.datanode.data.dir分别指定 NameNode 和 DataNode 的数据存储目录。这两个目录一定要提前创建好而且要有写权限。我见过有人配了路径但没建目录启动时报 Permission denied排查半天。mapred-site.xml是 MapReduce 配置。关键参数是mapreduce.framework.name指定 MapReduce 跑在什么框架上。伪分布式下配成yarn表示用 YARN 来调度。configuration property namemapreduce.framework.name/name valueyarn/value /property /configurationyarn-site.xml是 YARN 配置。最关键的是yarn.nodemanager.aux-services指定 NodeManager 的辅助服务。这个必须配成mapreduce_shuffle否则 MapReduce 任务跑不起来。configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.env-whitelist/name valueJAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME/value /property /configurationyarn.nodemanager.env-whitelist这个参数在 Hadoop 3.x 里特别重要。如果不配NodeManager 启动容器时可能找不到 JAVA_HOME导致任务失败。这个坑我踩过报错信息很隐晦排查了很久才发现是这个参数的问题。3.3 格式化与启动的完整步骤配置文件改完之后第一步是格式化 NameNode。这个操作只在第一次搭建时做一次之后绝对不能再做否则你的元数据会被清空之前存的数据全丢。hdfs namenode -format格式化成功的话你会看到 Storage directory ... has been successfully formatted 这样的输出。如果报错大概率是目录权限问题或者路径不存在。格式化之后启动 HDFSstart-dfs.sh然后启动 YARNstart-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:9870Hadoop 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-hadoopMap/Reduce Master 的 Host 和 Port填localhost和8088YARN 的端口DFS Master 的 Host 和 Port填localhost和9000NameNode 的 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 MapperObject, 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 ReducerText, IntWritable, Text, IntWritable { private IntWritable result new IntWritable(); public void reduce(Text key, IterableIntWritable 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 namehadoop.security.authorization/name valuefalse/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 namemapreduce.map.memory.mb/name value1024/value /property property namemapreduce.reduce.memory.mb/name value2048/value /propertyYARN 的容器内存上限。如果 NodeManager 分配的内存不够任务会一直处于 ACCEPTED 状态跑不起来。在yarn-site.xml里调property nameyarn.nodemanager.resource.memory-mb/name value8192/value /property property nameyarn.scheduler.maximum-allocation-mb/name value4096/value /property这两个参数的含义是NodeManager 总共能用 8G 内存单个容器最多能申请 4G。根据你机器的实际内存调整别配得比物理内存还大。JVM 重用。默认情况下每个 Map 任务都会启动一个新的 JVM任务多了之后JVM 启动开销很可观。开启 JVM 重用可以缓解这个问题property namemapreduce.job.jvm.numtasks/name value10/value /property这个配置表示一个 JVM 最多跑 10 个 Map 任务再销毁。本地开发环境任务少效果不明显但养成习惯是好的。5.3 环境搭建完成后的验证清单环境搭完之后别急着往下学先做一遍完整验证确保每个组件都正常。验证项操作命令预期结果HDFS 进程jps看到 NameNode、DataNode、SecondaryNameNodeYARN 进程jps看到 ResourceManager、NodeManagerHDFS 读写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 的笔记虽然现在看起来很多地方很幼稚但那份笔记帮我省了无数次重复踩坑的时间。