Java操作MapReduce实验报告:从WordCount到集群提交的完整指南
简介这份资源是面向高校大数据课程学习者的Hadoop MapReduce实验报告完整版以Java编程实现气象数据分析为场景帮助读者理解Map阶段与Reduce阶段的协作机制并掌握在Hadoop集群上部署运行程序的完整流程。压缩包内共1个doc文档约765KB内容涵盖实验目的、平台配置、内容要求、步骤详解与心得总结可直接作为课程作业参考或实验复盘材料。文档以2020年中国地区气象数据集为案例详细记录了温度正负值处理、异常值“9999”排除、自定义双分区器将1-6月与7-12月结果分文件输出等关键实现细节并附有打包jar、上传HDFS、执行hadoop jar命令及结果验证的完整操作说明。目前已有190人学习适合正在学习Hadoop、需要完成MapReduce上机实验或希望深入理解HDFS与MapReduce协同操作的读者参考。1. 从一份实验报告说起Java 操作 MapReduce 到底在练什么很多人第一次接触 Hadoop 大数据处理技术都是从一份「Java 操作 MapReduce 实验报告完整版」开始的。伪分布式环境刚跑通jps能看到 NameNode、DataNode、ResourceManager心里挺美结果一到自己写 Mapper 和 Reducer就卡在导包、类型不匹配、ClassNotFoundException上。这份实验报告真正要练的不是背 API而是把「分而治之」这个思想用 Java 落成可提交到 YARN 上跑的作业输入怎么切、map 输出什么类型、shuffle 怎么把相同 key 拉到一起、reduce 怎么聚合、最后怎么打包提交。它适合正在做课程设计、HDFS 和 MapReduce 综合实训或者准备大数据岗面试的 Java 工程师——你不需要先成为分布式专家但得能把一个 WordCount 从零写到集群上出结果并且知道每一步为什么这么写。2. 动手之前把 MapReduce 编程模型和运行环境对齐2.1 MapReduce 的五个阶段与 Java 类型约束MapReduce 作业在 Java 里不是「写两个函数」那么简单它有一套强类型约束。核心阶段是五个InputFormat 读切片、Mapper 做映射、Shuffle 排序分组、Reducer 做聚合、OutputFormat 写结果。Hadoop 没有用 Java 原生类型在网络间传数据而是要求所有 key/value 实现Writable接口作为 key 的还要实现WritableComparable因为 shuffle 阶段要排序。这就是为什么你写IntWritable、Text、LongWritable而不是int、String。常见做法是记住这张对应表写代码时直接查Java 原生类型Hadoop 序列化类型典型用途intIntWritable计数、词频longLongWritable偏移量、大计数StringText单词、行文本doubleDoubleWritable数值统计nullNullWritable只关心 key 或占位Mapper 的泛型是KEYIN, VALUEIN, KEYOUT, VALUEOUTReducer 是KEYIN, VALUEIN, KEYOUT, VALUEOUT其中 Reducer 的输入必须和 Mapper 的输出完全一致否则编译期就报错。新手最容易在这里翻车Mapper 输出Text, IntWritableReducer 却写成Text, Text运行时报类型转换异常。2.2 伪分布式环境的最小检查清单在写代码前先把环境确认一遍不然报错会把你带偏。伪分布式搭建完成后用下面几条命令确认服务状态和 HDFS 可用# 查看 Hadoop 相关进程正常应看到 NameNode/DataNode/ResourceManager/NodeManager jps # 查看 HDFS 根目录确认能连上 hdfs dfs -ls / # 建一个实验用的输入目录 hdfs dfs -mkdir -p /user/root/mr-input # 确认 Hadoop 环境变量生效 echo $HADOOP_HOME hadoop version逻辑说明jps是判断伪分布式是否真正起来的快捷方式少进程就先去查日志别急着写代码。hdfs dfs -mkdir建目录时如果报「cannot create directorypermission denied」说明当前用户不是 HDFS 超级用户常见做法是用hdfs dfs -chmod放开权限或者直接用启动 NameNode 的那个用户操作。参数上-p表示递归创建父目录避免中间目录不存在时报错。提示环境变量HADOOP_HOME没配好时hadoop命令会提示找不到但jps仍可能显示进程这两件事要分开排查。2.3 用 Maven 管依赖别手动拷 jar 包实验报告里经常让人手动把 Hadoop 的 jar 包加到 classpath这在 IDEA 里非常容易漏。更稳的做法是用 Maven 声明依赖版本和你集群的 Hadoop 保持一致dependencies !-- Hadoop 客户端版本必须与集群一致否则可能连不上 -- dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version /dependency /dependencies逻辑说明hadoop-client是一个聚合依赖会把 common、hdfs、mapreduce 客户端都拉进来省得你一个个找。参数上version必须和集群hadoop version输出一致差一个小版本都可能出现 RPC 协议不匹配。如果你的实验环境是 2.x就换成对应 2.x 版本不要照抄 3.x。3. 从零写一个能跑的 WordCountMapper、Reducer 与 Driver3.1 Mapper把一行文本拆成单词并打上 1Mapper 的职责很单一读一行拆词输出单词, 1。下面是最小可用版本import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; // 泛型依次是输入 key 偏移量、输入 value 行文本、输出 key 单词、输出 value 计数 public class WordCountMapper extends MapperLongWritable, Text, Text, IntWritable { private final static IntWritable ONE new IntWritable(1); private Text word new Text(); Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 把一行文本按空白切分常见做法是同时处理空格和制表符 String[] tokens value.toString().split(\\s); for (String token : tokens) { if (token.isEmpty()) { continue; // 跳过空串避免产生无意义的 key } word.set(token); context.write(word, ONE); // 输出 单词, 1 } } }逻辑说明map方法每行调用一次key是该行在文件中的字节偏移量通常用不上value是行内容。context.write把结果交给框架不是直接写文件。参数上split(\\s)里的\\s匹配一个或多个空白字符比split( )更稳能处理多个连续空格。ONE定义成静态常量是为了避免每个单词都 new 一个对象减少 GC 压力这是血泪经验。3.2 Reducer把相同单词的计数累加Reducer 拿到的是已经按 key 分好组的单词, [1,1,1...]你只需要遍历累加import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; // 输入类型必须与 Mapper 输出一致Text, IntWritable public class WordCountReducer extends ReducerText, IntWritable, Text, IntWritable { private IntWritable result new IntWritable(); Override protected void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable val : values) { sum val.get(); // get() 取出 IntWritable 里的 int } result.set(sum); context.write(key, result); // 输出 单词, 总次数 } }逻辑说明values是一个可迭代集合框架已经把相同 key 的 value 聚到一起你不需要自己排序或分组。参数上result复用同一个对象每次set新值再写出这是 Hadoop 官方示例的写法能减少对象创建。注意不要在 reduce 里做耗时 IO否则会拖慢整个 shuffle 后的聚合阶段。3.3 Driver把 Mapper、Reducer 和输入输出串起来Driver 是作业的入口负责配置和提交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.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class WordCountDriver { public static void main(String[] args) throws Exception { Configuration conf new Configuration(); Job job Job.getInstance(conf, word count); job.setJarByClass(WordCountDriver.class); // 指定作业 jar 的入口类 job.setMapperClass(WordCountMapper.class); job.setReducerClass(WordCountReducer.class); job.setOutputKeyClass(Text.class); // 最终输出的 key 类型 job.setOutputValueClass(IntWritable.class); // 最终输出的 value 类型 // 输入输出路径从命令行参数取避免硬编码 FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 提交并等待完成true 表示打印进度 System.exit(job.waitForCompletion(true) ? 0 : 1); } }逻辑说明setJarByClass告诉框架去哪个类所在的 jar 里找 Mapper 和 Reducer集群模式下这行不能少否则报ClassNotFoundException。setOutputKeyClass和setOutputValueClass设置的是最终输出类型如果 Mapper 输出类型和最终输出不同还要额外用setMapOutputKeyClass和setMapOutputValueClass指定。参数上args[0]是 HDFS 输入目录args[1]是输出目录输出目录必须不存在否则作业直接失败这是新手最常见的坑之一。3.4 打包、上传、提交三条命令跑通第一个作业代码写完用 Maven 打包然后提交到集群# 打包跳过测试加快速度 mvn clean package -DskipTests # 把本地测试数据传到 HDFS 输入目录 hdfs dfs -put ./input.txt /user/root/mr-input # 提交作业指定主类和输入输出路径 hadoop jar target/wordcount-1.0.jar com.example.WordCountDriver \ /user/root/mr-input /user/root/mr-output # 查看结果 hdfs dfs -cat /user/root/mr-output/part-r-00000逻辑说明hadoop jar会自动把 jar 分发到集群节点com.example.WordCountDriver要换成你自己的全限定类名。参数上输出目录/user/root/mr-output在提交前不能存在重跑前先hdfs dfs -rm -r删掉。part-r-00000是 reduce 任务的输出文件r表示来自 reducer编号对应分区。4. 实验报告里最容易翻车的几个点排查与避坑4.1 现象作业卡在 map 0% reduce 0% 不动原因通常是资源不够或配置指向了本地模式。伪分布式下如果mapreduce.framework.name没设成yarn作业会跑在本地看起来像卡住。解决检查mapred-site.xml里mapreduce.framework.name是否为yarn以及yarn-site.xml里yarn.nodemanager.aux-services是否为mapreduce_shuffle。改完重启 YARN。4.2 现象报java.lang.ClassNotFoundException: com.example.WordCountMapper原因有两种一是setJarByClass没写或写错类二是 Mapper 类没有被打进 jar。解决确认setJarByClass(WordCountDriver.class)存在并用jar tf target/xxx.jar | grep WordCountMapper检查类是否在包里。如果用的是 IDEA 直接运行本地模式可能不报集群模式必报。4.3 现象输出目录已存在导致作业失败原因Hadoop 不允许覆盖已存在的输出目录这是保护机制。解决提交前删除hdfs dfs -rm -r /user/root/mr-output。如果想让程序自动处理可以在 Driver 里用FileSystem判断并删除但实验阶段手动删更直观。4.4 现象中文分词结果乱码原因输入文件编码不是 UTF-8或者Text按字节处理时切断了多字节字符。解决确保输入文件是 UTF-8split时不要用会切断字节的方式。如果要做中文词频常见做法是引入分词库在 Mapper 里先分词而不是简单按空白切。4.5 现象reduce 阶段报Shuffle error或超时原因数据倾斜某个 key 的 value 特别多单个 reducer 压力过大。解决先看part-r-00000是否某个词计数异常高实验阶段可以加 Combiner 减少网络传输job.setCombinerClass(WordCountReducer.class)因为求和满足结合律可以安全复用 Reducer。5. 进阶Combiner、分区与本地验证的实用技巧5.1 用 Combiner 减少 shuffle 数据量Combiner 本质是「map 端的迷你 reducer」在 map 输出后先做一次局部聚合减少传到 reduce 的数据。WordCount 里求和满足结合律和交换律所以可以直接复用 Reducer// 在 Driver 里加这一行Combiner 逻辑与 Reducer 相同 job.setCombinerClass(WordCountReducer.class);逻辑说明Combiner 不是必须的但数据量大时效果明显。参数上只有满足结合律的操作才能用 Combiner比如求和、求最大值求平均值不能直接用因为会改变语义。这是面试常问的点也是实验报告里能加分的细节。5.2 自定义 Partitioner 控制 key 去哪个 reducer默认 Partitioner 用key.hashCode() % numReduceTasks如果你想按单词首字母分区可以自定义import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; public class FirstLetterPartitioner extends PartitionerText, IntWritable { Override public int getPartition(Text key, IntWritable value, int numPartitions) { // 按首字母分成 3 个区注意取绝对值防止负数 char first key.toString().charAt(0); return Math.abs(first) % numPartitions; } }逻辑说明getPartition返回分区号必须落在[0, numPartitions-1]。参数上numPartitions等于 reduce 任务数由job.setNumReduceTasks(n)决定。自定义分区后相同首字母的单词会进同一个 reducer方便做分组统计。5.3 本地模式先验证逻辑再上集群每次改代码都提交集群很慢常见做法是先在本地用一个小文件跑 LocalJobRunner// 临时把框架设成 local验证逻辑用不要提交到集群 conf.set(mapreduce.framework.name, local); conf.set(fs.defaultFS, file:///);逻辑说明本地模式不走 HDFS 和 YARN输入输出都是本地路径调试快。参数上fs.defaultFS设成file:///才会读本地文件。验证通过后把这两行去掉再打包上集群否则作业会找不到 HDFS 路径。5.4 一个我自己的习惯我做完每个 MapReduce 实验都会先拿 10 行数据在本地跑一遍确认 Mapper 输出和 Reducer 输出符合预期再上集群跑全量。这样能把「逻辑错误」和「环境错误」分开省下大量排查时间。实验报告里如果能写清这个验证过程比只贴最终结果更有说服力。希望帮到你。本文还有配套的精品资源点击获取