☰
Hadoop MapReduce伪分布式实战:从WordCount到避坑指南
2026/10/9 4:27:07 网站建设 项目流程

简介:《云计算技术实验报告三运行Hadoop MapReduce程序》是一份面向云计算、大数据初学者的实验报告范本,内容围绕在Linux环境下完整实现Hadoop MapReduce程序的开发全流程。报告中详细记录了从Java源码编辑、CLASSPATH环境变量配置、javac编译、jar打包到通过bin/hadoop jar命令提交并运行任务的步骤,并配套展示了Hadoop自带wordcount程序的HDFS操作与结果验证,适合需要完成同类课程实验或快速上手MR编程模型的读者参考。压缩包包含1个PDF文件,大小约603KB,内容结构清晰,涵盖实验目的、算法分析、关键命令、实验结果与总结感想。目前已有382人学习浏览,报告以实际运行案例解释MapReduce的分区、排序与Shuffle流程,能帮助读者加深对分布式计算框架工作机制的理解。

1. 先把实验跑通再说原理:一次WordCount的完整旅程

云计算技术课的第三个实验,标题叫“运行Hadoop MapReduce程序”。我最早在虚拟机上做这个实验时,以为最难的是写Java代码,结果最简单的是代码,最折腾的是环境:Java版本不对、SSH免密没配、YARN起不来、作业一直停在ACCEPTED状态。这篇笔记就是把“运行Hadoop MapReduce程序”拆成环境搭建、代码提交、任务调度和排错四件事,适合正在做实验报告、Hadoop课程设计,或者想自己复现一次MapReduce全流程的从业者。跑通一次WordCount,你对HDFS、YARN、Shuffle的理解会立刻从“看过”变成“摸过”。

2. 实验环境的搭建:从Linux装包到伪分布式的三个配置文件

做“运行Hadoop MapReduce程序”这个实验,最稳的起点不是直接搭三台机器的集群,而是先把伪分布式跑通。伪分布式就是让一个节点同时扮演NameNode、DataNode、ResourceManager、NodeManager四个角色,数据、调度、计算都在本机完成。它能让你完整经历HDFS上传、提交作业、Map执行、Shuffle、Reduce执行、结果回写这一整条链路,同时把环境复杂度控制在一个人能Hold住的范围。

网上有打包好的Hadoop Docker镜像,确实能省掉配置步骤,但实验报告通常需要你写清楚“为什么这样配”,本地手动装一次反而更划算。至于Hadoop HA、Hadoop和Zookeeper整合实战,那是多节点生产环境的课题,单机实验阶段先不碰,但要在报告里提一句“生产环境还需要引入ZooKeeper做故障切换”,就能让老师看出你理解了边界。

2.1 为什么伪分布式够用:先搞清楚实验要验证什么

云计算实验的核心目标是验证“MapReduce程序能不能在一个分布式文件系统上处理数据”。伪分布式虽然只有一个节点,但HDFS的块机制、YARN的资源调度、MapTask和ReduceTask的调度逻辑都真实发生了,和集群没有本质区别。区别只在于数据量和容错能力——单机没有真正的机架感知,也没有多副本跨节点分布,所以对外部读写的性能参考意义不大。

我一般建议做这个实验时把目标定成:能解释清楚一个输入文件从上传到HDFS再到输出结果的全过程,而不是纠结数据跑得有多快。课程设计里常见的误区是一上来就搭三节点集群,结果半天时间全耗在SSH同步和配置分发上,反而不如先跑通伪分布式,再用同样的配置扩展到集群。这也是为什么大多数实验报告的标准做法都是“先伪分布、后集群”。如果后续需要做三节点扩展,核心配置文件几乎不用改,只要把hdfs-site.xml里的副本数、yarn-site.xml里的内存参数调一下就行。

2.2 从零到能启动:JDK、SSH、解压与目录规划

下面这套步骤以CentOS 7或Ubuntu Server为例,Hadoop版本用3.x。版本差异主要影响Web UI端口和个别默认参数,配置思路通用。先准备好JDK 8和Hadoop安装包,然后按这个顺序操作:

# 1. 安装JDK并确认版本,Hadoop 3.x要求JDK 8及以上 tar -zxvf jdk-8u202-linux-x64.tar.gz -C /usr/local/ cat >> ~/.bashrc <<'EOF' export JAVA_HOME=/usr/local/jdk1.8.0_202 export PATH=$PATH:$JAVA_HOME/bin EOF source ~/.bashrc java -version # 2. 解压Hadoop到固定目录,尽量避免中文路径和带空格的目录 tar -zxvf hadoop-3.3.6.tar.gz -C /usr/local/ mv /usr/local/hadoop-3.3.6 /usr/local/hadoop # 3. 配置HADOOP_HOME,很多报错都源于这里漏配 cat >> ~/.bashrc <<'EOF' export HADOOP_HOME=/usr/local/hadoop export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin EOF source ~/.bashrc hadoop version # 4. 配置SSH本机免密,MapReduce调度时需要SSH连接localhost启动NodeManager ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhost

参数说明:JAVA_HOME必须指向实际JDK解压路径,Hadoop启动脚本会直接读取这个变量;HADOOP_CONF_DIR建议显式声明,否则有些版本会在启动时误读默认配置目录;SSH免密配置完成后,一定要执行一次ssh localhost确认不用输密码,否则后面start-dfs.sh会反复要求认证。

这步有一个关键细节:修改~/.bashrc后,所有后续启动命令都要在同一个Shell会话里执行。我见过不少人是配完环境变量直接新开终端,结果新终端没有加载配置,Hadoop命令提示找不到。稳妥做法是执行完source ~/.bashrc后,先hadoop version确认能输出版本号,再继续下一步。

2.3 三个配置文件:core-site、hdfs-site、yarn-site的必调项

伪分布式环境下,真正需要手工改的只有三个文件。core-site.xml里需要指定NameNode的地址;hdfs-site.xml里把副本数降为1,因为单节点存三份没有意义;yarn-site.xml是伪分布式和单机模式的关键区别——必须显式开启YARN调度,并给容器预留合理内存。以下是我的常用配置基线:

<!-- core-site.xml --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> <description>NameNode的RPC地址,客户端提交作业时依赖此地址</description> </property> </configuration>
<!-- hdfs-site.xml --> <configuration> <property> <name>dfs.replication</name> <value>1</value> <description>伪分布式只有一个DataNode,副本数必须为1,否则会一直处于副本不足告警</description> </property> </configuration>
<!-- yarn-site.xml --> <configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.aux-services.mapreduce_shuffle.class</name> <value>org.apache.hadoop.mapred.ShuffleHandler</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>4096</value> <description>给YARN分配的总内存,虚拟机上不能超过物理内存</description> </property> </configuration>

参数说明:yarn.nodemanager.aux-services配成mapreduce_shuffle是很多新手最容易漏掉的一项,不配的话作业能提交,但MapTask的数据永远传不到ReduceTask,最终表现为Reduce一直挂起。yarn.nodemanager.resource.memory-mb必须小于或等于物理内存,如果你虚拟机只给了2G,这里填4096会导致NodeManager直接启动失败。

配置完成后执行格式化与启动:

# 首次启动前必须格式化NameNode,之后不要重复执行,否则元数据会丢失 hdfs namenode -format start-dfs.sh start-yarn.sh jps

jps输出里能看到NameNode、DataNode、ResourceManager、NodeManager四个进程就是成功的。Hadoop 3.x的NameNode Web UI端口是9870,ResourceManager UI端口是8088,浏览器能打开这两个页面,环境才算真正就绪。

提示:jps能看到进程不代表集群可用。我习惯每次都先跑hdfs dfsadmin -report看一下DataNode是否正常注册,再跑yarn node -list确认NodeManager在线,两个命令都通过再进下一步。

3. 提交第一个MapReduce程序:WordCount从编写、打包到运行

环境就绪以后,要做的第一件事不是急着调复杂算法,而是把最经典的词频统计跑通。网上大多数MapReduce编程实例也都是拿WordCount当模板,因为它足够短,却覆盖了MapReduce的完整数据流。你只要能把这个程序从编译到结果输出讲清楚,实验报告的核心分就拿到了。

3.1 为什么第一课永远是WordCount:一次MapReduce的数据流还原

WordCount处理的是文本文件,输入格式是LongWritable加Text,也就是“行偏移量加行内容”。Map阶段把每一行按空格拆成单词,输出(单词, 1);Map结束后,框架会对所有key做分区、排序、合并,相同单词的计数被聚到同一个ReduceTask。ReduceTask拿到(单词, [1, 1, 1...])以后累加,最终输出(单词, 总数)。

这条链路里有三个容易被忽略的点:第一,Map的输出不是直接写磁盘,而是先写入内存缓冲区,达到阈值才溢写;第二,Shuffle阶段会在Map端做一次合并,在Reduce端再做一次归并,两个环节都有可能成为瓶颈;第三,MapTask和ReduceTask之间通过HTTP传输数据,这就是yarn-site.xml里那个aux-services参数的作用。理解了这三件事,再看后面的配置文件就有依据了。

3.2 写代码、离线编译与打包:javac、jar与Main-Class

写WordCount只需要一个Java文件。我一般把三个类全写在一个文件里:Map类、Reduce类、主类。这样编译和打包都不用管复杂的Gradle或Maven依赖,一条javac命令就能完成。

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(); 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); } } 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类,因为词频累加满足交换律和结合律,可以在Map端先合并一次,减少Shuffle数据量。如果你自己写的Reducer不满足这个条件,千万不能复用,否则结果会错。setJarByClass用来告诉Hadoop从哪个Jar里加载类,本地跑的时候不明显,提交到YARN时必须写。

编译和打包用下面两条命令:

# 用hadoop classpath自动带上全部依赖,避免手动找jar包的痛苦 javac -classpath "$(hadoop classpath)" -d wordcount_classes WordCount.java # 打成普通jar包即可,不需要生成可执行jar jar cf wordcount.jar -C wordcount_classes .

参数说明:hadoop classpath会输出一大串路径,它从HADOOP_HOME环境变量读取Hadoop安装位置。这一步经常出现“找不到命令”或“classpath为空”的问题,基本都可以回溯到2.2节里的HADOOP_HOME漏配。网上有时能直接找到别人编译好的Hadoop已编译Jar包,但那个包要能跑起来,前提是你本机环境变量全部正确,否则运行时会报ClassNotFoundException,这个前面省的时间后面全得还回去。

3.3 用yarn jar提交作业:运行参数与输出目录的规矩

打包完成后,先准备输入数据并上传到HDFS。注意Hadoop MapReduce程序读取的是HDFS路径,不是Linux本地路径,这是最常见的第一个理解断层。

# 建一个测试目录,随便放几个文本文件 mkdir -p input_data echo "hello hadoop hello mapreduce" > input_data/a.txt echo "mapreduce is fun hadoop is powerful" > input_data/b.txt # 创建HDFS目录并上传 hdfs dfs -mkdir -p /input hdfs dfs -put input_data/*.txt /input/ # 提交作业,输出目录必须是不存在的路径 hadoop jar wordcount.jar WordCount /input /output

提交命令的参数含义如下:

参数作用说明
hadoop jar提交作业等价于yarn jar,新版推荐用后者
wordcount.jar包含程序类的Jar包路径是本地文件系统路径
WordCount主类全限定名这里类和包同名,所以不用写包名
/inputHDFS输入目录可以传多个路径,目录下所有文件都会参与计算
/outputHDFS输出目录必须不存在,否则作业直接失败

运行成功后,HDFS根目录下会出现一个/output目录,里面至少有一个_SUCCESS文件和一个part-r-00000文件。查看结果用hdfs dfs -cat /output/part-r-00000。如果只想验证代码逻辑,可以用-D mapreduce.job.reduces=1把Reduce数量固定为1,这样结果只落在一个文件里,方便看全貌。

注意:/output每次运行时都必须先删除或换新路径,重复运行同一个实验时尤其容易踩这个坑。

4. 搞清楚你提交的作业在集群里到底怎么跑:InputSplit、Map数量与数据倾斜

作业能跑通只是及格线。实验报告要拿高分,下一步必须解释清楚“运行中的Hadoop任务里,InputSplit到底是什么,Map数量由什么决定”。这个问题在Hadoop面试题里出现频率也很高,因为它直接决定了作业的并行度和执行效率。

4.1 InputSplit是什么:运行中的任务如何把文件切成可计算的切片

InputSplit是MapReduce框架对输入数据的逻辑划分,而不是物理存储单位。HDFS上文件被切成了块(Block),默认128MB一块,这是存储层;而MapReduce在读取数据时会按InputSplit创建对应数量的MapTask,默认情况下一个Block对应一个Split,所以Map数量约等于文件总大小除以块大小。

这里有个容易混淆的点:Split是逻辑概念,Block是物理概念。一个Split可以横跨多个Block,也可以只包含一个Block的一部分,具体取决于文件格式和切分逻辑。框架拿到Split列表以后,会尝试把Split分配给本地存有对应Block的节点,这就是“计算向数据移动”的基本原理。

在运行中的任务里,我们要观察Split只需要看两处:一是提交作业时控制台输出的number of splits,二是ResourceManager UI上该作业的Map数。输入文件越大,这个数字越直观。执行下面的命令可以查HDFS上每个文件的块分布:

hdfs fsck /input -files -blocks -locations

输出里每行会列出一个文件路径、块大小、块的副本位置。把这些信息和Map数量对照,就能确认Map数与块数基本相等。如果作业卡在本地读数据,还可以用-D mapreduce.job.maps来强制指定Map数量,但真实执行时框架会忽略这个参数,因为InputFormat自己会计算Split,正确做法是调整Split大小而不是直接命令Map数量。

4.2 怎么控制Map数量:从minSize、maxSize到CombineFileInputFormat

Split大小由三个参数共同决定,公式是max(minSize, min(maxSize, blockSize))。这里的minSize是mapreduce.input.fileinputformat.split.minsize,maxSize是mapreduce.input.fileinputformat.split.maxsize,blockSize就是HDFS块大小,默认128MB。

把这个公式记住,Map数量就能控制了。想增大Map数、让每个Map处理更少数据,就把maxSize调小,比如调到16MB;想减少Map数,就调大minSize。实际使用中,直接改maxSize的场景占绝大多数。提交作业时这样传参:

hadoop jar wordcount.jar WordCount -D mapreduce.input.fileinputformat.split.maxsize=33554432 /input /output

参数说明:33554432是32MB的字节数。Hadoop配置项很多都是字节为单位,写数值时先做进制换算,直接写32不会生效,而且不会报错,只会让你的结果和预期完全对不上。

还有一类场景常见于“HDFS和MapReduce综合实训”:输入目录里有大量小文件,每个文件几十KB,默认逻辑下一个文件可能就生成一个Split,Map数量暴涨到几千甚至上万,每个Map处理的数据太少,调度开销反而拖慢整体速度。此时不能靠改maxSize解决,得用CombineFileInputFormat把小文件合并成一个Split。在代码里把job.setInputFormatClass(CombineFileInputFormat.class)即可,它允许一个MapTask处理多个小文件,这是处理小文件问题最直接的答案。

4.3 数据倾斜与Shuffle:combiner、自定义partitioner如何选

数据倾斜在WordCount里不明显,因为单词分布还算均匀;但实验如果让你统计日志里的异常号、用户访问量,数据倾斜立刻就会出现:某个key的条目特别多,它的ReduceTask要处理几百万条记录,其他ReduceTask却很快就跑完了。最典型的现象是所有Map都完成,但Reduce进度一直卡在33%或67%,整体作业卡在最后阶段。

解决倾斜有三板斧。第一板斧是加Combiner,把Map端相同key的局部结果先合并,这是最简单有效的手段。第二板斧是自定义Partitioner,按业务把数据尽量打散。比如统计IP访问量时,可以按源IP的哈希值分桶,而不是直接用IP本身分区。下面给一个按key长度分区的简化写法:

import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; public class LengthPartitioner extends Partitioner<Text, IntWritable> { @Override public int getPartition(Text key, IntWritable value, int numPartitions) { // 如果key很长,大概率是同一个单词或同一类数据,尽量散列到不同分区 return (key.toString().length() * 31) % numPartitions; } }

逻辑说明:Partitioner决定每一条Map输出进入哪个ReduceTask。默认实现是key.hashCode() % numPartitions,它对分布均匀的key很公平,但对热门key无能为力。按自定义规则重新散列,本质是让每个分区的数据量尽量接近。

第三板斧是调整Reduce端参数:把mapreduce.reduce.memory.mb调大,或者开启mapreduce.map.output.compress=true减少Shuffle网络传输量。对实验而言,Combiner已经能解决大部分问题,自定义Partitioner更适合作为报告里的加分项。单独设置Combiner的写法是job.setCombinerClass(LocalSumCombiner.class),要求Combiner的输入输出类型与Reducer一致,否则类型不匹配会在运行时才报错。

5. 避坑指南:MapReduce实验里最常见的五个翻车现场

这一章是全篇最有血泪经验的部分。从课程设计到生产排查,下面五个问题覆盖了伪分布式实验里绝大多数“看着配置没问题、代码也没问题、就是跑不起来”的情况。每条按“现象、原因、解决”的顺序写,自己踩坑时按图索骥即可。

5.1 启动后进程不对:JAVA_HOME、Hostname、SSH免密三位一体检查

现象:执行start-dfs.sh后,jps只能看到NameNode或DataNode其中一个,或者干脆一个都看不到;查看日志报错Error: JAVA_HOME is not set and Java could not be found。

原因:这类问题几乎都集中在三处。第一是JAVA_HOME写错或指向了JRE而非JDK;第二是Hadoop使用hostname去注册节点,而/etc/hosts里没有本机的hostname映射;第三是SSH免密没生效,DataNode启动时连接localhost被拒。

解决:先执行echo $JAVA_HOME确认变量值;再检查hostname和/etc/hosts是否一致;最后手动执行ssh localhost看是否还需要密码。三条都通过后,执行stop-all.sh再重新start-all.sh。启动日志在$HADOOP_HOME/logs/目录下,根据进程名找对应的.log文件,这是定位启动问题的第一现场。

5.2 作业一直ACCEPTED不进RUNNING:NodeManager没就绪是首要嫌疑

现象:hadoop jar提交后,控制台停在Submitting application,用yarn application -list能看到作业状态一直是ACCEPTED,等几分钟也不变化。

原因:YARN集群接收了作业,但没有NodeManager能执行它。常见原因是NodeManager进程没起来,或者YARN内存配置超限导致容器无法分配。前者可以回到5.1检查jps,后者要确认2.3节里的yarn.nodemanager.resource.memory-mb填的值没有超过物理内存。

解决:先执行yarn node -list -all,如果输出为空,说明NodeManager没有注册,回到YARN日志排查启动失败原因。如果能看到节点但无法分配容器,执行yarn node -status <节点名>查看可用内存,再对照mapreduce.map.memory.mb默认值调整。一台2G内存的虚拟机,把YARN总内存降到1536MB,Map容器内存降到512MB,通常就能跑通。

5.3 输出目录已存在:FileAlreadyExistsException是最常报的错

现象:第一次运行成功,第二次运行同一个作业时抛org.apache.hadoop.mapred.FileAlreadyExistsException,提示输出路径已存在。

原因:MapReduce框架出于安全设计,不会覆盖已有输出目录,因为输出可能来自上一次作业,直接覆盖会掩盖数据问题。

解决:换一个新路径,或者先确认旧输出没用了再删。实际操作用hdfs dfs -rm -r /output删除。我把这当成一个习惯:每次提交之前先检查输出路径有没有残留,写成脚本也好、手敲命令也好,总之不要在作业跑一半才发现路径冲突。

5.4 中文乱码与编码问题:默认读取行为和你本地文件编码不一致

现象:输出结果里中文全部变成??或者乱码,英文单词正常。有时还会看到单词被意外切割,比如“哈”和“密”被拆成两行。

原因:Hadoop的Text类型默认按UTF-8解码,但Windows上生成的输入文件常是GBK编码。另外StringTokenizer只按空格切分,中文字符之间没有空格,整句话会被当成一个单词,看起来就像“乱码”。

解决:上传前先把输入文件转成UTF-8,用iconv -f GBK -t UTF-8 input.txt > input_utf8.txt转换。如果要处理中文分词,就不要依赖StringTokenizer,改用手工按字符切分或引入分词器。实验场景下最简单的方法是输入文件就存UTF-8,输出文件用hdfs dfs -cat查看时指定编码环境export LANG=zh_CN.UTF-8,可以避开大多数乱码问题。

5.5 容器反复被杀、GC时间过长:虚拟机内存参数没跟随实际环境

现象:作业提交后,Map跑到20%左右就开始重试,然后以Container killed by the ApplicationMaster之类的错误失败。日志里能看到物理内存超过限制的记录,或者GC overhead limit exceeded。

原因:这是YARN按内存上限管理容器导致的。默认情况下mapreduce.map.memory.mb是1024MB,mapreduce.map.java.opts最大堆设为-Xmx 819m。如果NodeManager一共只有2G内存,同时跑两个Map容器就直接超了,容器会被杀掉。

解决:按虚拟机实际内存重新分配,报告里也要写清楚。我常用的最小配置是:

# 在yarn-site.xml中明确指定 yarn.nodemanager.resource.memory-mb=2048 yarn.scheduler.maximum-allocation-mb=2048 # 提交作业时临时指定容器内存 hadoop jar wordcount.jar WordCount \ -D mapreduce.map.memory.mb=512 \ -D mapreduce.reduce.memory.mb=512 \ -D mapreduce.map.java.opts=-Xmx400m \ /input /output

参数说明:-Xmx必须小于mapreduce.map.memory.mb,因为JVM堆只是容器进程的一部分,还有元数据区和系统开销。这段配置本身也是实验报告里很好的“调优记录”素材。

6. 让实验结论可验证:从日志、历史服务到把WordCount升级成TopN

程序跑完只是第一步,能证明它“真的跑对了、可复现”,比程序本身更能体现有没有吃透这个实验。这里分享三个我常用的验证和进阶方法。

6.1 用HistoryServer和YARN日志核对真实执行情况

作业结束后,ResourceManager Web UI的历史列表里会保留每次作业的统计信息:Map输入记录数、Map输出记录数、Shuffle传输字节数、Reduce输出记录数。对照这些数字就能判断是否发生了数据倾斜、Combiner是否生效。

# 查看某个application的容器日志 yarn logs -applicationId application_xxxxxxxx > app.log # 从日志中找到Map或Reduce的计数器输出 grep "Combine output records" app.log | head -5

计数器里Combine output records如果存在且小于Map output records,就说明Combiner确实合并过数据,这个数字可以直接写进实验报告作为证据。

6.2 把WordCount升级成词频TopN:一个性价比很高的进阶练手

要验证对排序和Shuffle的理解,最快的做法是输出词频最高的前10个词。核心不是改Map或Reduce,而是在Reduce端用一个容量为N的小顶堆维护最大值列表。可以参考下面的片段:

public static class TopNReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private TreeMap<Integer, String> top = new TreeMap<>(); @Override public void reduce(Text key, Iterable<IntWritable> values, Context context) { int sum = 0; for (IntWritable val : values) { sum += val.get(); } top.put(sum, key.toString()); if (top.size() > 10) { top.remove(top.firstKey()); } } @Override public void cleanup(Context context) throws IOException, InterruptedException { for (Map.Entry<Integer, String> entry : top.entrySet()) { context.write(new Text(entry.getValue()), new IntWritable(entry.getKey())); } } }

逻辑说明:每个ReduceTask内部只保留10个最大的词频,最后在cleanup阶段统一输出。这个练手能让你理解cleanup方法的用途,也知道全局TopN在分布式环境里不能靠把所有数据做全局排序,而是要先在局部裁剪一轮。

6.3 固定输入与资源参数,确保实验结果可复现

写实验报告时最容易被问倒的一句话是“你这个结果能再跑一遍吗”。我自己的习惯是把三条信息记录下来:输入文件的完整内容和大小、HDFS块大小、YARN内存参数。有了这三样,任何人在同一台配置下都能跑出相同结果。

这个实验做到最后,最大的教训不是代码写不写得出,而是过程中大部分时间花在了环境匹配上。希望这篇笔记能让你少走一些弯路,把更多注意力放到理解分布式计算本身,希望对你有帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询