简介:一份大数据平台编程实践类实验报告,面向正在学习 Hadoop 与 MapReduce 的本科生或入门开发者。内容以 wordcount 单词统计程序为主线,完整记录从 Hadoop 虚拟机安装、环境配置、Eclipse 插件加载到 MapReduce 项目创建的实操过程,并附可直接运行的 Java 源码,适合对照练习、完成课程实验或作为排错参考。资源包共 1 个文件,为 doc 格式文档,大小约 758KB,内容包含实验目的、环境说明、步骤截图位置与关键代码注释,便于按步骤复现。已有 1726 人浏览学习,热度较高。通过本报告可重点掌握 Hadoop 启动命令与端口验证、Eclipse 与 Hadoop 连接配置、Mapper/Reducer 类编写及 job 提交逻辑,对理解 MapReduce 编程框架和后续大数据开发有直接帮助。
1. 你的第一个分布式程序:为什么偏偏是 WordCount
如果你搜过“大数据实验报告”,八成绕不开这个标题:Hadoop 编程实现 WordCount 单词统计程序附源码。Hadoop 的 WordCount 之于大数据开发,就像 C 语言的 Hello World 之于编程入门——它把分布式计算最核心的 MapReduce 思想压进了几十行代码里:数据怎么切、怎么并行、怎么洗牌(Shuffle)、怎么汇总。你把这个程序完整跑通一遍,Hadoop 集群的文件读写、任务调度、资源分配、日志排查就全打通了。做实验、交课程报告、准备大数据面试,它都是成本最低的试金石。这篇笔记不绕弯,直接从环境准备讲到源码逐段拆解,再到提交运行的完整命令和调参,最后把我在真机上翻过的车、玄学报错全列给你。适合两类人:刚搭好 Hadoop 环境想跑通第一个程序的学生,和要带新人入门、想在自己的测试集群里复现完整流程的工程师。
2. 跑通前的准备:Java 版本、HDFS 目录和一条命令验证环境
很多人把 WordCount 跑不通的锅甩给代码,其实八成是环境没对齐。写 WordCount 前先花十分钟把环境三件套确认好,后面能少踩一半坑。
2.1 环境核对:Java 版本和 Hadoop 版本怎么配对
Hadoop 是用 Java 写的,但 Java 版本不是越新越好。我见过有人装 Hadoop 3.3.x 却配了 Java 17,结果 NameNode 直接起不来,日志里报UnsupportedClassVersionError。常见做法是看 Hadoop 发行版的编译目标:Hadoop 2.x 用 Java 7/8,Hadoop 3.x 用 Java 8/11,别一上来就上 17。
确认版本的方式很简单,两条命令:
java -version hadoop version第一行输出里看1.8.0_xxx这种格式是 Java 8,11.0.x是 Java 11。第二行输出里看Hadoop 3.2.x或Hadoop 2.10.x。我一般会提前在/etc/profile里把JAVA_HOME写死成具体路径,而不是只写export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java)))),因为which java有时候指向的是/usr/bin/java的系统默认版本,和你装的高版本对不上。
提示:如果
hadoop version报错说找不到 Java,先echo $JAVA_HOME,空的就去/etc/profile末尾加export JAVA_HOME=/usr/local/jdk1.8.0_xxx和export PATH=$PATH:$JAVA_HOME/bin,然后source /etc/profile。
2.2 准备输入文件:上传前的文件格式与编码坑
WordCount 的输入是文本文件,但这里有一个常被忽略的问题:编码。Hadoop 默认按 UTF-8 读文件,如果你的实验数据是 Windows 记事本存的 GBK 编码,Mapper 读出来的中文字符会变成乱码,统计出来的词频全是问号。最省事的方案是直接在 Linux 上用echo或vim生成 UTF-8 文件。
我一般这样准备测试数据:
# 创建一个测试目录,写入三行英文句子 mkdir -p /home/hadoop/wordcount_input echo "hello world hadoop hello" > /home/hadoop/wordcount_input/input1.txt echo "hadoop mapreduce hello hive" >> /home/hadoop/wordcount_input/input1.txt echo "spark flink hadoop spark" > /home/hadoop/wordcount_input/input2.txt # 查看文件编码,确认是 UTF-8 file /home/hadoop/wordcount_input/*.txtfile命令输出里如果显示ASCII text或UTF-8 Unicode text都没问题,如果显示ISO-8859或Non-ISO extended-ASCII,说明编码不对,需要重新转。转换用iconv -f GBK -t UTF-8 input1.txt > input1_utf8.txt就行。这里多文件输入不是必须的,但建议至少准备两个文件,因为后面验证分布式计算时,能直观看到 Map 任务被拆分到不同节点上跑。
2.3 伪分布式 vs 完全分布式:实验报告最该用哪种
本地上跑 WordCount 有三种模式:本地模式、伪分布式、完全分布式。实验报告里最常见的写法是伪分布式——因为它只需要一台机器,却能完整走一遍 HDFS 读写和 YARN 调度流程,报告里可以写出的架构图和数据流向,比本地模式(直接跑 Java 进程)看起来完整得多。
伪分布式启动后,用一条命令确认核心进程都在:
jps正常情况下你应该看到五个进程:NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager。少任何一个都说明配置有问题,最常见的是SecondaryNameNode缺失——很多教程只让你改core-site.xml和hdfs-site.xml,忘了在hdfs-site.xml里配dfs.namenode.secondary.http-address。如果ResourceManager没起来,检查yarn-site.xml里是否漏了yarn.nodemanager.aux-services为mapreduce_shuffle。
注意:做完这一步后,把
jps的输出截图放进实验报告,这是最能证明环境搭建成功的证据,比贴一长串配置文件有用得多。
3. 源码拆解:Map、Reduce、Driver 三段式到底在做什么
WordCount 的源码结构固定是三段:Mapper 类、Reducer 类、主类(Driver)。不同教程的代码长得不太一样,但骨架完全一致。我先把完整代码贴出来,然后逐段解释每个方法、每个泛型、每个输出的含义。
import java.io.IOException; import java.util.StringTokenizer; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.Mapper; import org.apache.hadoop.mapreduce.Reducer; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCount { // Mapper 阶段:把每行文本拆成单词,输出 <单词, 1> public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text word = new Text(); @Override 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); } } } // Reduce 阶段:把相同单词的计数累加 public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); @Override 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 主类:定义 Job 并提交 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); } }3.1 Mapper:为什么输出键值对必须是 Text 和 IntWritable
先看 Mapper 的泛型签名:Mapper<Object, Text, Text, IntWritable>。四个参数分别对应输入键、输入值、输出键、输出值。输入键是Object而不是LongWritable,很多人不理解,其实是因为这里根本用不到行号——Hadoop 默认把文件每一行的偏移量作为输入键,但 WordCount 只关心行内容,所以键类型可以放宽。
输出键是Text,输出值是IntWritable,注意这里不能用 Java 自带的String和Integer。原因是 Hadoop 设计了一套自己的可序列化类型Writable,它们经过优化,能在网络传输和磁盘读写时比 Java 原生类型更快、更节省空间。IntWritable代表一个可序列化的整数,它的.get()方法取出 int 值,.set()方法写入 int 值。
map()方法的核心逻辑就三行:用StringTokenizer把一行文本按空白字符(空格、制表符、换行)切碎,每次取一个nextToken()作为单词,通过context.write(word, one)输出一对<单词, 1>。StringTokenizer是 Java 的老类,默认分隔符是空格和制表符,如果你要统计的文章里带逗号、句号,这里就得手动加new StringTokenizer(value.toString(), " \t,.!?")把标点也当作分隔符,否则"hello,"和"hello"会被算成两个不同单词。
3.2 Reducer:values 为什么是 Iterable 而不是 List
Reducer 的泛型签名是Reducer<Text, IntWritable, Text, IntWritable>,对应输入键、输入值、输出键、输出值。输入是 Mapper 输出经过 Shuffle 排序后的结果:所有相同单词的 value 被聚合到一起,比如 5 个("hadoop", 1)会被合并成("hadoop", [1, 1, 1, 1, 1])。
注意reduce()方法的第二个参数是Iterable<IntWritable> values,不是List。这里有个性能设计:Hadoop 可能把上百万个值聚到一个 key 上,如果全放进 List 会撑爆内存,所以它设计成迭代器,让你一次只处理一个值,边遍历边累加。
这里的累加逻辑是实验报告里最容易写错的地方:sum += val.get()不能写成sum += val,因为val是IntWritable对象不是 int 基础类型。写完后用result.set(sum)把 int 结果包装回IntWritable,再context.write(key, result)输出。
3.3 Driver 主类:五个必须写对的配置项
Driver 主类的main()方法里,有五个配置项决定作业能不能跑成,一个都别省。
job.setJarByClass(WordCount.class)告诉 Hadoop 去哪个 jar 里找 Mapper 和 Reducer 类。如果不写这行,伪分布式模式下任务提交到 YARN 时会报ClassNotFoundException,因为客户端类路径和 NodeManager 的类路径不是一回事。job.setMapperClass和job.setReducerClass是两行独立的调用,不能写成链式调用。job.setCombinerClass(IntSumReducer.class)是可选优化,把 Mapper 输出在本地先聚合一遍再传出去,后面第 6 章细讲。
job.setOutputKeyClass(Text.class)和job.setOutputValueClass(IntWritable.class)这两行的作用是告诉 Hadoop 最终的输出类型,用于序列化。即使 Mapper 和 Reducer 的输出类型一致也得写,这是安全边界——很多人的代码在本地跑没问题,提交到集群就报类型错误,就是漏了这两行。输入输出路径从args[0]和args[1]读,这意味着你运行时要传两个参数:输入目录和输出目录。
4. 编译打包提交:从 javac 到 yarn 的完整命令链
源码写完了,下一步是把.java文件编译成.class再打成 jar 包。这里有个经典问题:javac默认找不到 Hadoop 的类库,因为 Hadoop 的 jar 不在 JDK 的 classpath 里。
4.1 编译命令:HADOOP_CLASSPATH 的获取与使用
Hadoop 官方提供了一条命令来生成完整的 classpath 字符串,不用你自己一个个 jar 去拼:
export HADOOP_CLASSPATH=$(hadoop classpath) javac -classpath $HADOOP_CLASSPATH -d wordcount_classes WordCount.java jar -cvf wordcount.jar -C wordcount_classes .这段命令做了三件事:第一行把hadoop classpath的输出赋值给环境变量,这个输出是一长串以冒号分隔的 jar 路径,包含了 Hadoop 全部依赖;第二行-d wordcount_classes指定编译输出的 class 文件目录,所有.class文件会按包结构放进去;第三行-cvf把 class 目录打成 jar,-C wordcount_classes先切换进去再打包,确保 jar 内部路径不含多余的顶层目录。
编译后检查一下 jar 内容,确认主类存在:
jar tf wordcount.jar | grep WordCount.class这一步我吃过亏:用 IDE 的图形化打包功能导出的 jar 常常不带主类入口,提交到 YARN 后在 ApplicationMaster 里报ClassNotFoundException: WordCount。用命令行jar打出来的包最稳妥,它会保留所有 class 文件的完整路径,配合setJarByClass正好互补。
提示:如果你在 IDEA 里开发,记得在项目结构里把 Hadoop 依赖设为
provided作用域,这样打包时不会把 Hadoop 全家桶塞进 jar——我之前打出来的 jar 有 80MB,里面全是没用的依赖,影响任务启动速度。
4.2 上传输入文件到 HDFS:三条命令的边界
本地文件不能直接作为 MapReduce 的输入,必须先上传到 HDFS。整个过程严格按顺序执行:
# 1. 在 HDFS 上创建输入目录 hdfs dfs -mkdir -p /user/hadoop/wordcount_input # 2. 把本地文件上传 hdfs dfs -put /home/hadoop/wordcount_input/*.txt /user/hadoop/wordcount_input/ # 3. 验证文件到位 hdfs dfs -ls /user/hadoop/wordcount_input/第 1 条命令如果报mkdir: Cannot create directory,说明当前用户没有 HDFS 根目录写权限。常见解决方式是给当前用户创建 home 目录:hdfs dfs -mkdir -p /user/$(whoami),然后hdfs dfs -chown $(whoami):$(whoami) /user/$(whoami)。第 2 条-put支持通配符和目录,它会保留文件名,HDFS 上看到的是input1.txt和input2.txt两个文件。第 3 条-ls除了确认文件存在,还要看Replication列——伪分布式下副本数是 1,如果显示 3 而集群只有一个 DataNode,后续读取时会有大量超时重试。
输入文件的数量决定了 Map 任务的数量:默认情况下每个文件至少一个 Map,如果文件超过 128MB(或你配置的dfs.blocksize),每个块单独一个 Map。所以输入目录里有多少个文件,直接看得到任务调度时的并行度。
4.3 提交任务与查看结果:waitForCompletion 的语义陷阱
编译、上传都完成后,提交任务:
hadoop jar wordcount.jar WordCount /user/hadoop/wordcount_input /user/hadoop/wordcount_output注意hadoop jar后面的第一个参数是 jar 包名,第二个参数是主类全类名,后面才是输入输出路径。这里WordCount就是上面代码里public class WordCount的类名,如果你的代码里类名不一致,这里也会报ClassNotFoundException。
输出目录有一个铁律:必须不存在。如果你第二次运行还指到同一个输出目录,会报FileAlreadyExistsException。代码里的FileOutputFormat.setOutputPath在作业初始化时会检查输出路径,Hadoop 不会自动覆盖,这是刻意的设计——防止误操作覆盖掉之前的计算结果。
任务运行期间,终端会滚动输出进度:map 0% reduce 0%、map 100% reduce 100%。跑完后查看结果:
hdfs dfs -cat /user/hadoop/wordcount_output/part-r-00000正常情况下输出是每行一个单词加一个数字,比如hadoop 4、hello 3。如果看到part-r-00000文件不存在而只有_SUCCESS,说明 Reduce 阶段压根没跑,回去看代码里是不是忘了写setReducerClass。
4.4 从头跑一遍的自动化脚本:实验报告的复现保障
实验报告要求步骤可复现,我建议把整个流程封装成一个脚本,每一步用echo打印当前阶段:
#!/bin/bash # 一键跑通 WordCount:清理旧目录 -> 编译 -> 上传输入 -> 提交任务 -> 输出结果 set -e INPUT_DIR=/user/hadoop/wordcount_input OUTPUT_DIR=/user/hadoop/wordcount_output LOCAL_INPUT=/home/hadoop/wordcount_input hdfs dfs -rm -r -f $OUTPUT_DIR export HADOOP_CLASSPATH=$(hadoop classpath) rm -rf wordcount_classes && mkdir -p wordcount_classes javac -classpath $HADOOP_CLASSPATH -d wordcount_classes WordCount.java jar -cvf wordcount.jar -C wordcount_classes . hdfs dfs -mkdir -p $INPUT_DIR hdfs dfs -put -f $LOCAL_INPUT/*.txt $INPUT_DIR hadoop jar wordcount.jar WordCount $INPUT_DIR $OUTPUT_DIR hdfs dfs -cat $OUTPUT_DIR/part-r-00000脚本里最关键的一行是开头的set -e,它让脚本在任一步骤失败时立即退出,不会带着坏环境继续跑下去。hdfs dfs -rm -r -f $OUTPUT_DIR是解决输出目录已存在的后悔药——重复实验之前先强制删除旧结果。这样你每次改代码后,只要重新执行一次脚本,就能在干净的状态下验证修改效果,不需要手动清理 HDFS。
5. WordCount 必踩的 5 个坑:现象、原因、解决
这一部分是我做实验和带人时收集的真实踩坑记录,按「现象 → 原因 → 解决」写,希望对号入座。
坑 1:java.lang.OutOfMemoryError: Java heap space
现象:Map 阶段跑到 60% 左右,任务失败,错误日志里看到这个 OOM 报错。
原因:Mapper 默认堆内存上限继承自mapred-site.xml里的mapreduce.map.memory.mb,很多发行版默认值是 1024MB,但你的数据量或StringTokenizer处理逻辑把单行文本撑得太大,或者是自由编写的 Mapper 里不小心把整个文件加载进了内存。
解决:调大 Map 容器内存上限,在提交命令时加参数:hadoop jar wordcount.jar WordCount -D mapreduce.map.memory.mb=2048 输入 输出。注意-D参数必须放在jar和主类之后、输入输出路径之前,否则会被识别成普通参数。如果还报错,检查是不是输入文件里有超大单行(比如 500MB 的 JSON),可以先用hdfs dfs -cat查看文件行大小,再决定是改代码还是预处理数据。
坑 2:Container killed on request. Exit code is 143
现象:Reduce 阶段启动后立刻被杀,日志里这个退码。
原因:物理内存超限。YARN 默认对容器有物理内存监控(yarn.nodemanager.vmem-pmem-ratio,默认 2.1 倍),Java 进程的虚拟内存很容易超过虚拟内存上限,NodeManager 直接把容器杀了。这个坑在伪分布式上特别常见,因为你的机器同时跑着 NameNode、DataNode、ResourceManager、NodeManager,系统资源紧张。
解决:两个方向,首选调大虚拟内存比例:在yarn-site.xml里把yarn.nodemanager.vmem-pmem-ratio从 2.1 改成 4.0,重启 NodeManager;次选在提交时手动降低 Reduce 的物理内存需求:-D mapreduce.reduce.memory.mb=1024。改完yarn-site.xml记得重启yarn-daemon.sh stop nodemanager && yarn-daemon.sh start nodemanager,只改配置不重启是不生效的。
坑 3:统计结果里单词数量翻倍或出现空行
现象:part-r-00000输出里同一个单词出现两次,各自带着一个较小的数字,或者是出现\t1这种没有 key 的行。
原因:输入文件里混入了\r\n(Windows 换行符)。Linux 的StringTokenizer把\r当作一个普通字符,"hello\r"和"hello"被切成了不同 token;把\r单独一行时,它被统计成了一个「单词」。
解决:转换输入文件格式,或者上传前预处理:sed -i 's/\r$//' /home/hadoop/wordcount_input/*.txt。这条命令把每行末尾的\r删掉。以后从 Windows 传文件到 Linux,先跑这一句再上传,养成习惯就少踩一次坑。
坑 4:输出目录的_SUCCESS文件存在但 result 文件不完整
现象:任务显示completed successfully,但part-r-00000里只有少量单词,明显不完整。
原因:最常见的是输入路径写错了。FileInputFormat.addInputPath传入的是一个目录,Hadoop 会递归读取目录下所有part-*文件,但如果你的输入目录里同时有上次任务的输出文件(比如把输出目录误设成了输入目录的子目录),Map 会把中间结果也读进来当输入。
解决:输入输出目录必须是独立、互不嵌套的两个目录。我的固定约定:输入目录叫xxx_input,输出目录叫xxx_output,并且提交前先hdfs dfs -rm -r -f $OUTPUT_DIR清场。另一个检查点是看任务日志里的Map-Reduce Framework计数器的Map input records,如果这个值远小于你文件行数,说明输入路径读漏了文件。
坑 5:Error: java.lang.ClassNotFoundException: org.apache.hadoop.mapreduce.Job
现象:javac编译通过,但hadoop jar提交时报找不到Job类。
原因:编译时用的 classpath 和运行时用的 classpath 不一致。一种情况是你手动拼了 classpath 漏了几个 jar(比如漏了hadoop-mapreduce-client-core.jar),另一种是 Hadoop 安装目录下有多个相同 jar 的版本,你编译时引了某一个,运行时 YARN 加载了另一个。
解决:不要手拼 classpath,统一用我第 4 章给你的命令——export HADOOP_CLASSPATH=$(hadoop classpath)生成编译 classpath,然后打 jar 包时用jar -cvf只打自己的 class 文件,不带任何 Hadoop 依赖。如果还是报错,检查hadoop classpath的输出里是否包含 mapreduce 相关 jar,没有就手动在HADOOP_CLASSPATH末尾补上这个 jar 的绝对路径再编译。
6. 把 WordCount 用出花:Combiner 优化、链式任务与调试技巧
基础程序跑通只是第一步,实验报告想拿高分,或者你真的想在集群上用它处理大文件,这一章才是分水岭。
6.1 Combiner 到底省了什么:一个本地预聚合的数值测算
Combiner 就是本地 Reduce——它会在每个 Map 任务完成之后、结果写盘之前,先把这个 Map 输出的相同 key 合并一遍。拿前面的input1.txt举例,里面hadoop出现了 2 次,不启用 Combiner,Map 会输出两条("hadoop", 1),经由 Shuffle 传输到 Reducer 后再累加;启用 Combiner 后,这个 Map 在本地先把两条合并成("hadoop", 2),网络传输的数据量减半。
这个优化对 WordCount 这种数据密集型任务的效果非常可观:Map 输出中大部分单词的频次都远大于 1,本地聚合可以轻松减少 50% 以上的 Shuffle 数据量。Combiner 的使用有一个前提条件——你的 Reducer 逻辑必须满足交换律和结合律,即不论怎么分组、按什么顺序累加,最终结果一致。WordCount 的累加逻辑恰好满足,所以可以直接把IntSumReducer类同时当作 Combiner 注册:job.setCombinerClass(IntSumReducer.class)。如果你的逻辑是求平均值,Combiner 就不能直接复用 Reducer,因为分组的平均值再求平均不等于全局平均。
验证是否生效的方式是跑完任务后看计数器里的Combine output records。用浏览器打开 YARN ResourceManager 的 Web UI(默认http://localhost:8088),点进你的作业,在 Counters 里搜Combine相关项,如果Combine output records远大于 0,说明 Combiner 生效了。
6.2 两个实验报告加分项:单词排序与词频 Top N
基础 WordCount 的输出是无序的。实验报告如果想展示你对 MapReduce 有更深理解,可以加一个排序步骤——两种常见改法:在 Reduce 阶段用 TreeMap 对局部结果排序,或者在主类里再定义一个排序用的 Job,把 WordCount 的输出作为第二个 Job 的输入。第二种是链式 MapReduce,报告里能画出的流程图更完整:
// 第二个 Job:按词频降序排序 Job sortJob = Job.getInstance(conf, "word count sort"); sortJob.setJarByClass(WordCount.class); sortJob.setMapperClass(SortMapper.class); // 自定义 Mapper sortJob.setReducerClass(SortReducer.class); // 自定义 Reducer sortJob.setOutputKeyClass(IntWritable.class); // 键设为词频,Hadoop 自动按 key 排序 sortJob.setOutputValueClass(Text.class); // 值设为单词 FileInputFormat.addInputPath(sortJob, new Path(args[1])); // 第一个 Job 的输出 FileOutputFormat.setOutputPath(sortJob, new Path(args[2])); // 最终排序结果关键点在setOutputKeyClass(IntWritable.class)——Hadoop 按 key 排序,把词频当 key 就能得到按词频排列的输出。注意默认排序是升序,需要降序就自定义一个IntWritable的 Comparator 传进去。这个技巧在面试里也常被问:Reducer 按什么分组、按什么排序、Shuffle 的细节都在这个点上。
6.3 调试三板斧:counter、日志、小数据集
跑大数据作业最怕黑匣子:看不到中间结果,出了问题不知道是 Map 阶段还是 Reduce 阶段。我的调试习惯是三板斧:
第一,用context.getCounter("MyCounter", "map_records").increment(1)在代码里打点。比如在map()方法里每处理一行就increment(1),任务跑完后在 Web UI 的 Counters 里看到这个值,就能确认 Mapper 确实处理到了多少行数据,不用猜。
第二,查看日志用yarn logs -applicationId application_xxx,它会打印所有失败的容器日志。看到一大段堆栈不要慌,搜第一个Caused by,那才是根因。
第三,小数据集验证。我固定用 100 行以内的文本先跑通全流程,再上全量数据。这不是小题大做——Hadoop 任务一次失败到重跑,耗时通常是分钟级,用 100 行数据把逻辑验证好,上全量时踩的坑会少一半。测试数据里故意放几行空行、重复单词、大小写混合,这些都跑一遍,能暴露很多边界问题。
最后说我自己的习惯:我每次上手一个 Hadoop 项目,第一件事不是看业务逻辑,而是把 WordCount 在这个集群上跑通一遍。它像个探路器,能快速暴露 Java 版本对不对、HDFS 权限通不通、YARN 内存够不够、日志能不能查——这些基础环境问题在 WordCount 身上只要几分钟就能暴露,等写到真实业务代码再去排查,代价就高了。这个习惯让我少熬了很多次夜,希望帮到你。
本文还有配套的精品资源,点击获取