ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Hadoop WordCount实验报告:MapReduce原理与源码解析

Hadoop WordCount实验报告:MapReduce原理与源码解析 简介一份大数据平台编程实验报告面向刚开始接触Hadoop与MapReduce编程模型的计算机相关专业学生。报告完整记录了WordCount单词统计程序的实现过程从Hadoop虚拟机安装、环境配置、Eclipse与Hadoop连接建立到MapReduce代码编写与运行均有逐步说明。核心的wordcount.java源码可直接参考涵盖自定义Mapper、Reducer类及Job提交逻辑便于读者对照学习MapReduce执行流程并在此基础上改写扩展。资源为1个doc格式文档压缩包大小仅758KB包含实验目的、环境版本、详细操作步骤与完整源码。已有1726人浏览学习对正在完成同类课程实验或自学入门Hadoop编程的读者具有较高参考价值。1. 这份带源码的 Hadoop WordCount 实验报告到底在让你练什么大数据实验报告里Hadoop 编程实现 wordcount 单词统计程序附源码.doc 几乎是所有人交出的第一份 MapReduce 作业它看起来只是数单词实际是把 HDFS 上的文件切分、分发、合并的整套流程走了一遍。这篇笔记按实验报告“是什么、怎么做、坑在哪”的顺序展开先用一个 Mapper 和一个 Reducer 讲清 WordCount 的运行模型再给出能直接编译打包、提交到伪分布式集群的完整源码最后把路径、权限、日志这些最容易卡住实验的细节单独拎出来。适合正在做 Hadoop 课程设计、需要写实验报告的同学也适合用 WordCount 验证新集群能不能用的工程师。2. 单词统计的运行模型Map、Shuffle、Reduce 各自做了什么2.1 单机几行代码就能数完为什么还要上 Hadoop先用最直觉的方式统计单词Python 里就是这么写from collections import Counter total Counter() with open(input.txt, encodingutf-8) as f: for line in f: total.update(w for w in line.lower().split()) print(total)这段代码在小文本上没问题但换成真实场景输入文件总和超过单机内存或者文件分片存储在 HDFS 的不同节点上单机程序就只能先把数据全部拉回一台机器传输和内存都会先崩掉。MapReduce 的核心思路是“数据不动、计算动”每个分片在它所在节点被本地 Map 处理中间结果再经 Shuffle 按键分组最后交给 Reduce 汇总。WordCount 虽然小却完整演示了这条机制这也是它被反复用作 Hadoop 入门实验的根本原因。把 WordCount 选作课程设计还有一层现实理由这种统计不需要跨节点做关联输入输出是最基础的文本到文本跑通它等于验证了 HDFS 上传、YARN 调度、Map 输出写盘、Reduce 拉取数据这条完整链路。所以报告里“会写 WordCount”不是目的目的是会用这套链路。2.2 Map 阶段读一行、切一次词、打一个“出现 1 次”的标签Mapper 的 map 方法签名是void map(LongWritable key, Text value, Context context)key 是当前行在文件里的字节偏移value 是整行内容。框架每次把一行文本交给 map不需要自己管理分片在哪个节点、从哪个字节开始读。WordCount 在 map 里只做三件事把行转小写、按分隔符切词、对每个词输出一次word, 1。新手容易忽略一个点map 的输出不会直接写 HDFS而是先写 Map 任务所在节点的本地磁盘再按键分成多个区。默认分区器按 key 的哈希值决定进哪个区目的是让同一个 word 的所有word, 1最终进入同一个 reduce 任务。你在 map 里执行context.write(word, one)之后框架会在后台完成分区、排序和合并这些都不需要写代码。2.3 Shuffle 和 Sort框架替你完成的“分组”Shuffle 在实验报告里容易被一句话带过但它其实是理解 WordCount 的关键。Shuffle 分两半map 端把输出分好区落盘reduce 端再从所有 map 任务拉取属于自己分区的数据按键排序、合并。等 reduce 方法真正被调用时同一个 key 的所有 value 已经装进一个 Iterable这就是reduce(Text key, IterableIntWritable values)里 values 的来源。排序默认按 key 的字典序所以 WordCount 的输出文件天然是从 a 到 z 排好序的。如果实验报告里想展示这一步可以看任务日志里的 Shuffle 统计量也可以直接观察输出文件是否有序。排序规则还能改写成按频次排第 6 章会单独讲怎么把它变成 Top N 统计。2.4 Combiner 和选型理由为什么 Java 版是实验默认选择Combiner 是可选的 map 端“预聚合”WordCount 里可以直接复用 Reducer 类因为求和满足交换律和结合律。设置 Combiner 后同一个 Map 任务内部的相同单词会先在本地加一遍再进入 Shuffle小文件测试看不出区别几 GB 输入时能明显减少网络传输。要注意 Combiner 跑在 map 节点上必须保证它不改变最终语义像求平均值这种操作就不能简单复用 Reducer。至于实现选型实验里交 Java 版是更稳的选择。Hadoop Streaming 用 Python 也能写但 Java 版直接暴露 Mapper 和 Reducer 接口出错时堆栈能定位到具体代码行排错路径最短同时 Java 版打成 jar 后不依赖集群额外安装的 Python 环境。这个理由写进“方案选型”一节比“我用的是 Java”更有说服力。3. 从零写一个可提交的 WordCount工程骨架、打包命令与两个必调参数3.1 工程骨架常见做法是用 Maven 管理 Hadoop 依赖。工程结构如下wordcount/ ├── pom.xml └── src/main/java/com/example/WordCount.java只有两个文件。pom.xml里只要一个 Hadoop 客户端依赖它会连带拉进 hdfs、mapreduce、common 相关类库。完整内容project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdwordcount/artifactId version1.0/version packagingjar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId configuration archive manifest mainClasscom.example.WordCount/mainClass /manifest /archive /configuration /plugin /plugins /build /project两个参数值得说明maven.compiler.source/target设成 1.8是为了和大多数 Hadoop 集群的 JDK 版本对齐mainClass写在 manifest 里运行时就不用手动补全类名。版本号 3.3.4 可以换成你机器上实际安装的 Hadoop 版本只要大版本是 2.x 或 3.x这段代码不用改动。3.2 完整源码Mapper、Reducer、Main 入口一次写完import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; 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 { public static class TokenizerMapper extends MapperLongWritable, Text, Text, IntWritable { private final static IntWritable one new IntWritable(1); private Text word new Text(); Override public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line value.toString().toLowerCase(); String[] words line.split([^a-zA-Z0-9]); for (String w : words) { if (w.isEmpty()) { continue; } word.set(w); context.write(word, one); } } } public static class IntSumReducer extends ReducerText, IntWritable, Text, IntWritable { private IntWritable result new IntWritable(); Override 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); } } 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); } }逐段过一遍MapperLongWritable, Text, Text, IntWritable的四个泛型分别是输入 key 类型、输入 value 类型、输出 key 类型、输出 value 类型。LongWritable和Text是 Hadoop 自带的序列化类型比直接用 Java 的 Long 和 String 更适合在节点间传输。split([^a-zA-Z0-9])的意思是按“既不是字母、也不是数字、也不是撇号”的连续字符切分。这样dont会保留成完整单词但state-of-the-art会被拆成三段。如果实验文本是干净英文直接改成line.split(\\s)更简单按空白切即可。job.setCombinerClass(IntSumReducer.class)这一行就是前面说的 map 端预聚合因为求和适合本地先加一遍。waitForCompletion(true)必须传 true作业提交后会持续打印进度调试阶段靠它看每个阶段百分比。3.3 打包、造数据、提交运行在工程根目录执行mvn clean package ls target/wordcount-1.0.jar不习惯 Maven 的话用 javac 也能编译需要把 Hadoop 的 classpath 带进去javac -cp $(hadoop classpath) -d . WordCount.java jar cf wc.jar WordCount*.class hadoop jar wc.jar com.example.WordCount /input /output提交前先往 HDFS 放一个测试文件。伪分布式环境里输入路径必须是 HDFS 路径不是 Linux 本地路径hdfs dfs -mkdir -p /input echo hello hadoop hello world | hdfs dfs -put - /input/a.txt hdfs dfs -cat /input/a.txthdfs dfs -put -的-表示从标准输入读数据适合快速造实验数据。然后提交hadoop jar target/wordcount-1.0.jar /input /output看到日志里出现completed successfully后检查结果hdfs dfs -cat /output/part-r-00000预期输出hadoop 1 hello 2 world 1part-r-00000是 reduce 阶段写出的文件r代表 reduce后面数字是任务编号。如果设置多个 reduce会生成part-r-00000、part-r-00001等多个文件。4. WordCount 跑不通时的排查顺序路径、目录、权限与日志里的 6 个坑以下每一条都是从实验报告批改现场和伪分布式翻车经历里攒出来的按作业提交后事件发生的顺序排4.1 提交后一直卡在 Running job先查 jps现象hadoop jar提交后日志停在Running job不动或很快报 ApplicationMaster 启动失败。原因伪分布式下 NameNode 或 ResourceManager 根本没起来或者之前格式化过 NameNode 后又改了配置集群元数据不一致。解决先执行jps伪分布式正常要看到 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager 五个进程。缺哪个就去logs/目录看对应日志。如果 ResourceManager 和 NodeManager 状态不同步最稳妥是停掉所有进程重新start-dfs.sh和start-yarn.sh不要只重启单个进程。4.2 Input path does not exist最大头的报错现象提交后立刻抛出Input path does not exist: hdfs://localhost:9000/user/root/input。原因/input目录在 HDFS 上不存在或者文件没有真正传上去。初学者最容易犯的错是把本地 Linux 路径当成 HDFS 路径用。解决不要凭记忆猜路径先hdfs dfs -ls /确认目录层级再hdfs dfs -ls /input确认文件在。hadoop jar接收的路径一律是 HDFS 路径本地文件必须先put上去。这个坑值得写进实验报告的“实验结果与分析”里。4.3 FileAlreadyExistsException输出目录被当成不允许覆盖的保护现象提交时报org.apache.hadoop.mapreduce.lib.output.FileAlreadyExistsException: Output directory hdfs://localhost:9000/output already exists。原因MapReduce 故意设计成输出目录在提交时必须不存在防止误覆盖上一次实验的结果。解决实验前手动清理hdfs dfs -rm -r /output不建议在代码里自动删除输出目录团队共用集群时很容易删掉别人的结果。把它当成实验流程的一部分提交前先删一次。4.4 Permission deniedroot 用户在 HDFS 里不是超级用户现象日志出现AccessControlException: Permission denied: userroot, accessWRITE, inode/output:hdfs:supergroup。原因HDFS 权限默认开启Linux 的 root 在 HDFS 里没有写权限目录 owner 是 hdfs。解决实验环境最简单的办法是用环境变量临时切换身份HADOOP_USER_NAMEhdfs hadoop jar target/wordcount-1.0.jar /input /output这个环境变量只对当前命令生效适合单机实验。另一个办法是hdfs dfs -chmod -R 777 /input但别在生产集群开dfs.permissions.enabledfalse某些教程为了省事这么写只适合自己虚拟机里的学习环境。4.5 类找不到或 Java 版本不匹配编译环境与运行环境不一致现象提交后很快报ClassNotFoundException: com.example.WordCount或者UnsupportedClassVersionError。原因前者是 jar 里没有包含主类或运行时类名写错后者是本地编译用的 JDK 比集群上的 JRE 新编译出的 class 文件版本集群不认。解决检查 jar 内容jar tf target/wordcount-1.0.jar | grep WordCount如果类不在检查 pom 的mainClass和源码包路径是否对应。版本问题就对比开发机和集群的java -version把 pom 里的maven.compiler.source/target调到和集群一致。Windows 开发、Linux 集群这种组合最容易踩这个坑。4.6 结果和预期不一致正则切词和大小写合并的边界现象Hello和hello被算成两个词或者hello,和hello没合并。原因map 里没做toLowerCase()切词正则把标点一并算进了单词。解决WordCount 的“单词”定义完全由切词规则决定。实验输入是干净英文时用line.split(\\s)最贴题意想剔除标点就用[^a-zA-Z0-9]遇到中文文本就要换用[^\\s]。这块没有标准答案按实验要求调正则即可同时在实验报告里写清楚你选的是哪种规则。5. 伪分布式与集群模式差在哪提交命令、参数和实验报告的关键截图5.1 三种运行模式与提交差异Hadoop 作业可以在三种形态下跑本地模式不启动 HDFS 和 YARN作业跑在单个 JVM 里适合先验证 Mapper 和 Reducer 逻辑伪分布式在一台机器上同时跑 NameNode、DataNode、ResourceManager、NodeManager模拟完整集群是多数课程实验的标准形态完全分布式才是多节点真实集群数据块真正跨机器存储。提交命令本身没有本质区别都是hadoop jar。真正的差别在三处输入输出路径是否在 HDFS 上、作业是否被 YARN 调度、Map 并行度是多少。伪分布式下一个 128MB 以内的小文件通常只产生 1 个 map因为 Hadoop 3.x 默认一个分片是 128MB。想看到多个 map 任务要么故意准备一个超过分片大小的输入要么调小分片上限参数。5.2 实验前建议调的四个参数参数默认值伪分布式建议说明dfs.replication31只有一台 DataNode副本 3 没有意义还会白白占三倍磁盘mapreduce.job.reduces11WordCount 不需要多个 reduce保持 1 个方便看结果mapreduce.input.fileinputformat.split.maxsize128MB 左右32MB想看到多个 map 任务时调小单位是字节mapreduce.map.memory.mb1024512小实验压小内存避免小机器容器申请失败dfs.replication配在hdfs-site.xml里改完重启 HDFS 生效。分片大小可以在提交时用-D临时指定不需要改配置文件hadoop jar target/wordcount-1.0.jar \ -D mapreduce.input.fileinputformat.split.maxsize33554432 \ /input /output33554432 是 32MB 的字节数。调小分片后日志里 Map 任务数会从 1 变成更多能直观感受到 YARN 并行调度的效果实验报告里对比截图也更好写。5.3 实验报告里必须有的四样东西批改这类报告时能拿分的不只是源码。按这个结构写基本不会被挑毛病实验目的两三行、实验环境写清 Hadoop 版本和 Java 版本及部署形态、实验步骤里每步配一条命令、实验结果贴运行日志和输出文件、最后把源码附在附录里。关键截图四张jps进程列表、hadoop jar提交后从Running job到completed successfully的日志、hdfs dfs -cat /output/part-r-00000的输出前 20 行、任务结束后 Web UI 上作业的运行状态页3.x 的 ResourceManager 默认端口 8088。这四张截图像证据链一样证明作业真跑过而不是只贴了个代码。输出文件建议整理成表格单词频次hadoop1hello2world1把part-r-00000的内容剪贴进报告时只保留前二三十个词频即可完整结果作为附录。如果实验结果超过一页说明输入文件太大实验报告里截取一段代表性输出就行没必要全部贴。6. 基于 WordCount 改一改三个顺手能做的统计拓展6.1 单词平均长度不改 Reducer只加计数器MapReduce 自带 Counter 机制可以在不改变输出文件结构的前提下统计全局信息。在 map 里维护总字符数和总词数累加进自定义计数器作业跑完从 Counter 里读数context.getCounter(stat, totalLen).increment(line.length()); context.getCounter(stat, totalWords).increment(words.length);日志里 Counters 部分会出现stat组下的两个计数相除就是平均长度。这个改法不需要动 Reducer是最小的功能扩展。6.2 输出每个单词首次出现的文件名map 阶段可以通过context.getInputSplit()拿到当前分片对应的文件路径拼到 value 里输出。这已经不是标准的 WordCount而是开始学“如何携带上下文信息”。注意 Reducer 里不能对文件名求和得改成去重收集比如存进 TreeSet 后拼接成字符串。功能本身不复杂但比默认 WordCount 更能体现对 MapReduce 数据流的理解。6.3 按词频排序的 Top N把排序键从单词换成频次默认输出按键排序WordCount 输出的是单词字典序。要改成“出现次数最多的前 N 个词”常见做法是自定义一个WritableComparable作为输出 key比较逻辑里优先比频次。这条路径会牵出二次排序和 GroupingComparator属于 MapReduce 里偏进阶的内容面试常考。作为实验报告的“拓展与思考”写三百字解释思路即可不一定要跑通。我自己的习惯是WordCount 这个小工程不只用来交作业装好一套新 Hadoop 环境后先把它跑一遍等于同时验证了 HDFS 读写、YARN 调度和日志定位三条链路。它简单到不会掺入业务干扰又完整到覆盖提交全过程。以后排查集群问题全靠第一次跑通时保存的日志和输出做基线对比。希望这份笔记能帮你少走一段弯路。本文还有配套的精品资源点击获取
返回列表