简介:一套面向Hadoop初学者的词频统计完整实现包,基于Hadoop2.2.0,有效解决入门者缺少可运行范例、难以直观理解MapReduce分布式计算流程的问题。包体共17个文件,包含7个Java源码、7个编译后的class文件、1个约十万单词的TXT测试文件,以及Eclipse工程的.project与.classpath配置,整体仅154KB,轻量且便于直接导入工程运行。压缩包采用zip格式,解压后源码与class文件分目录存放,便于对照学习。该资源已有5865人学习下载,是Hadoop词频统计经典案例中较受关注的参考实现。借助这套文件,读者可以获取从Mapper、Reducer到作业提交的完整可编译代码,利用自带测试文件验证词频输出,并通过对照源码与class文件辅助理解编译部署细节,直观感受Map、Shuffle、Reduce阶段的协作过程,适合课程实验、自学入门、技术面试准备或二次开发模板使用。
1. Hadoop 词频统计在跑什么:一条命令背后的一整套分布式作业
标题写着「Hadoop 词频统计(完整版)」,它不是一个需要费力解释的新技术,而是一条把 HDFS、MapReduce、YARN 串起来的最小闭环。你在这道题里做的每一件事——建目录、传文件、写 Mapper、打包、执行 hadoop jar 提交、看运行日志——都直接对应一次真实的分布式作业链路。反直觉的地方在于:代码五分钟能写完,环境却可能卡你三天,而最容易让人翻车的不是算法,是那些没人明说的小前提。
这个方向适合三类人。刚把 Hadoop 装完、还不知道下一步该干什么的初学者;课程设计要交一个能现场演示的项目、不想只截图的在校生;准备面试、想亲手把 WordCount 从头跑到尾的求职者。它不能让你立刻上手生产集群,但能让你在提交作业之前,先对「数据在哪、计算在哪、日志在哪」有清晰的认知。
一句话把目标说透:用 MapReduce 统计一批文本里每个词的出现次数,输入从 HDFS 读,结果写回 HDFS,中间所有 shuffle 和归并不落本地磁盘。这个闭环一旦跑通,后续再学分区、排序、Combiner、合并去重,都是往这条链路上加零件。
2. 准备一个能跑词频的 Hadoop:伪分布式是性价比最高的起点
2.1 先分清三种部署模式:伪分布式不是低配,是单机调试的最优解
Hadoop 的部署模式按「进程是否分开、数据是否走 HDFS」分成三种,常见做法是单机模式、伪分布式、完全分布式,选型直接决定你接下来的排查路径。
单机(本地)模式下,MapReduce 跑在本地文件系统上,不需要启动 NameNode 和 DataNode,也不经过 YARN。用它调 Mapper 的小逻辑可以,但跑出来的作业根本看不到分布式链路,很多装完 Hadoop 的同学被「hadoop jar 报错找不到 HDFS」卡住,就是因为还在单机模式里。完全分布式则至少要三台机器,要处理免密登录、节点间时间同步、多份配置分发,词频统计的代码本身不复杂,把时间耗在集群铺底上不划算。
伪分布式是中间那条性价比最高的路:所有 NameNode、DataNode、ResourceManager、NodeManager 进程都在一台机器上,但数据通路和作业调度流程与真实集群一致。我一般建议第一遍词频统计就在伪分布式上做,而且在 Ubuntu 虚拟机或裸机里做,而不是直接在 Windows 上用 IDEA 调试。Windows 下用 IDEA 搭 Hadoop 开发环境并不是不行,问题是本地文件系统权限模型和 HDFS 不一致,还常缺 winutils.exe 的可执行权限,很多人在这上面耗掉半天。先用 Linux 把通路跑通,再回到 IDEA 里做编码调试,是更稳的路径。想省掉系统依赖,也可以直接用 Hadoop 的 Docker 镜像,效果接近,前提是你对容器目录挂载不陌生。
2.2 从零装一个伪分布式:JDK、SSH、四个配置文件和一次格式化
伪分布式的安装步骤看起来长,实际就三件事:让 Hadoop 能找到 JVM、让 SSH 能免密连本机、让四个 XML 配置指向正确参数。下面步骤以 Hadoop 3.x 在 Ubuntu 上的做法为例,路径按你实际解压位置改。
先装 JDK 8 或更高版本,解压 Hadoop 到 /opt/hadoop,然后追加环境变量。Hadoop 3.x 要求 JDK 8 以上,我自己常用 JDK 8,最稳,内存占用也小。
# /etc/profile 追加以下内容,Java 路径改成你自己的实际路径 export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export HADOOP_HOME=/opt/hadoop export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin source /etc/profile java -version # 确认 JDK 可用 hadoop version # 确认 Hadoop 命令可用然后配置 SSH 免密。伪分布式下,NameNode 要用 SSH 拉起本机的 DataNode 和 SecondaryNameNode,没有免密会一直在启动时让你输密码,脚本直接挂起。
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ssh localhostssh localhost 能直接登录、不要求输入密码,这一步就算过了。接下来是核心配置文件,共四个。先说一个总开关:core-site.xml 里的 fs.defaultFS 定义了整个集群的默认文件系统,必须写成 hdfs://localhost:9000,这样后续所有相对路径才走 HDFS;如果漏配,hadoop jar 提交的词频作业会把输入输出都当成本地路径,直接报 Input path does not exist,这个错误在第 5 章还会再见到。
hdfs-site.xml 里三个参数值得注意。dfs.replication 在伪分布式下必须设为 1,因为只有一个 DataNode 节点,副本数设成默认的 3 会导致块一直处于 under-replicated 状态,fsck 不断告警。dfs.namenode.name.dir 和 dfs.datanode.data.dir 建议指到 /opt/hadoop/data 下的独立目录,不要用默认的 /tmp/hadoop-*。系统重启会清掉 /tmp,NameNode 的元数据也没了,下次启动先报文件不存在,再报 clusterID 不匹配,属于最典型的翻车现场。
mapred-site.xml 在 3.x 里一开始只有 mapred-site.xml.template,需要自己复制一份,配置里只需一句话:mapreduce.framework.name 设为 yarn,表示作业交给 YARN 调度。保持默认 local 的话,作业会在本地跑,你依然看不到 ResourceManager 上的任务链路。
yarn-site.xml 里两个关键参数:yarn.nodemanager.aux-services 必须写成 mapreduce_shuffle,这是 NodeManager 为 MapReduce 的 shuffle 阶段开的辅助服务,漏配的话作业会一直停在 0% 不前进;yarn.nodemanager.resource.memory-mb 按物理内存调整,单机 8G 内存的机器我一般给 4096,给太少容器会被快速杀,给太多会和操作系统争内存。
注意:hdfs namenode -format 只需要在第一次启动前执行一次,重复格式化会让 NameNode 生成新 clusterID,与 DataNode 里已有的一致不上,启动日志会出现两个 clusterID 的报错。
hdfs namenode -format $HADOOP_HOME/sbin/start-dfs.sh $HADOOP_HOME/sbin/start-yarn.sh2.3 验证进程与数据通道:jps 缺哪个进程,先查哪个日志
启动完先不要急着写代码,用 jps 看进程是否齐全。这个命令输出的五条记录分别对应 HDFS 和 YARN 的核心角色,缺哪一个,后面的词频统计都会以奇怪的方式失败。
| 进程名 | 职责 |
|---|---|
| NameNode | 管理文件系统的命名空间和块元数据 |
| DataNode | 实际存储数据块,响应读写请求 |
| SecondaryNameNode | 定期合并 NameNode 的编辑日志,不是热备 |
| ResourceManager | 接受作业提交,分配容器 |
| NodeManager | 启动和监控容器,执行任务 |
如果进程有缺失,去 $HADOOP_HOME/logs 下看对应日志,通常是配置路径不存在或权限不对。进程都齐了之后,用 HDFS 命令确认数据通道可用:
hdfs dfsadmin -report # 看 DataNode 是否在线,容量是否正常 hdfs dfs -ls / # 能列出根目录,说明 HDFS 读写链路通了到这里,伪分布式环境就绪。之后在 Windows 上用 IDEA 搭 Hadoop 开发环境时,也建议先用这套 Linux 环境把 HDFS 路径传熟,编码调试可以两边切换着来,不用死磕一个环境。
3. 把文本送进 HDFS:输入数据的三种准备方式和路径选择
3.1 本地文件与 HDFS 是两套文件系统:上传和路径是词频统计的第一个坑
很多人写完代码第一次提交就报 Input path does not exist,查看错误信息发现路径是 file:/home/user/xxx,而不是 hdfs://localhost:9000/xxx。本质原因是:伪分布式下,你跑命令的 shell 看到的文件系统是 Linux 本地,而 MapReduce 作业从 HDFS 读数据,两边互不相通。文本必须先用 hdfs dfs -put 上传到 HDFS 的某个目录,再把那个目录路径传给作业。
这里有个容易混淆的点:hadoop fs 和 hdfs dfs 两个命令在 3.x 里基本等价,都是操作 HDFS 的入口。我习惯用 hdfs dfs,因为它更明确表达操作对象。上传之前先规划目录,常见做法是建一个 /wordcount 目录,下面分 input 和 output 两个子目录。输出目录不要提前创建,MapReduce 会自己建,而且要求它必须不存在,否则提交时直接报错。
3.2 上传与校验:mkdir、put、fsck 三个命令一次做完
给一组能直接抄的命令。假设文本在本地 /home/user/input/words.txt。
hdfs dfs -mkdir -p /wordcount/input hdfs dfs -put /home/user/input/words.txt /wordcount/input/ hdfs dfs -ls -R /wordcount hdfs dfs -stat "%b %n" /wordcount/input/words.txt hdfs fsck /wordcount/input/words.txt -files -blocksmkdir -p 会递归创建目录,目录不存在时不会报错;put 把本地文件复制进 HDFS,原文件保留;ls -R 确认文件已就位并看大小;stat 里的 %b 是文件字节数,%n 是文件名,用来确认文件完整;fsck 查看文件被切成几个块、副本数是否满足配置。对小文件来说,这里能看到它只有一个块、一个副本,这就自然引出小文件问题。
词频统计的输入通常只有几 MB,而 HDFS 默认块大小是 128MB。每个文件会占用一个 InputSplit,也就是说即使文件只有 1KB,MapReduce 也会为它启动一个完整的 Map 任务,启动开销远大于处理本身。目录里如果放了 100 个小文件,就会启动 100 个 Map 任务,伪分布式下单机资源很快被打满,作业表现为「卡住」。常见做法是把多个小文件先合并成一个文本再上传,或者直接在 HDFS 上用 hadoop fs -put 一次传一个较大的文件。先记住结论:词频统计的输入文件宁少勿多,一个输入目录下一个大文件是最省事的形态。
3.3 中文文本的三道关口:编码、BOM 与换行符
词频统计的素材里中文很常见,课程设计几乎都逃不掉。Hadoop 的 TextInputFormat 默认按行读取,字节流解码用作业运行环境的默认编码,最稳妥的是 UTF-8 无 BOM。三个典型案例我都在不同机器上遇到过。
第一,文件是 GBK 编码,输出里中文全变问号或乱码。解决方法是先确认编码,再转码。第二,文件是 UTF-8 但带 BOM。BOM 是文件开头的三个不可见字节,解码后第一个词会被拼上一个 \ufeff 前缀,统计结果里出现一个带着隐形字符的「脏词」,从显示上几乎看不出来,只能靠排序对比发现多了一个词。第三,文件从 Windows 复制过来,换行是 CRLF,单词末尾会带着 \r,同一个词在输出里出现两种形态:一个带 \r,一个不带,词频被拆开。
准备文本时用下面这三条命令做一次预处理,之后不用再排查编码问题:
file -i words.txt # 查看实际编码 iconv -f GBK -t UTF-8 words.txt > words_utf8.txt # 如果确认是 GBK 就转码 sed -i 's/\r$//' words_utf8.txt # 去掉行尾的 CR 字符转码后建议再用 file -i 确认一次输出。这三道关口处理完,中文文本进入 HDFS 才没有后续隐患。编码问题在词频统计里表现成「乱码」,在稍大的作业里会演化成排序错乱或者 reduce 阶段数据处理异常,前置处理比事后排查省时间。
4. 编写词频统计的 MapReduce:Mapper、Reducer 与驱动类的完整实现
4.1 先把三阶段分工讲清:WordCount 为什么是面试常客
词频统计的经典实现是每行文本按分隔符切词,每个词输出一条<词, 1>,shuffle 阶段 Hadoop 自动按词分组,同一个词的所有 1 汇到同一个 Reducer,Reducer 求和。整个计算被拆成三个阶段:Map 负责并行切词,Shuffle 负责合并同类项,Reduce 负责归并计数。
这里有一个面试里必考的点:Combiner 为什么能放在 Mapper 之后。Combiner 是在 Map 输出端做的本地预聚合,因为「求和」满足交换律和结合律,先在本机把同一个词的计数加一次,再传给 Reducer,网络传输量大大减少。词频统计是 Combiner 的最佳示范,它能让你直观看到加了 Combiner 之后,job 的 counter 里 map 输出记录数明显下降。这正是 hadoop 面试题里常问的 shuffle 过程在真实作业里的体现。
关于 Mapper 输入输出的类型设计,锁定为 LongWritable -> Text -> Text -> IntWritable。行偏移量当 key,行文本当 value;输出时反过来,词当 key,固定的 1 当 value。类型定错会在 reduce 端反序列化时直接抛异常,这是新手最常见的运行时错误。
4.2 Mapper 和 Reducer 代码:一个处理切词,一个处理求和
下方是 Mapper 的完整实现,注意切词的正则我写了注释说明。用 \W+ 切英文天然合适,加了中文就翻车,因为 \W 会把中文字符也当成分隔符,一整行中文会被切没。想要统计中文,一般用 \s+ 按空白切,或者先做分词再喂给 MapReduce。
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; public class TokenizerMapper extends Mapper<LongWritable, 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 { // 英文文本用 \W+ 切分,只保留字母和数字 // 含中文的文本建议改成 split("\\s+"),否则中文会被当成分隔符 String[] tokens = value.toString().split("\\W+"); for (String token : tokens) { if (token.length() > 0) { word.set(token); context.write(word, one); } } } }Reducer 的代码更短,它收到的 values 是这个词在 shuffle 之后汇集的全部 1,直接累加即可:
import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; public class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); @Override protected 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); } }两个类都不需要维护状态,也不用考虑线程,这是 MapReduce 最容易理解的地方。唯一的边界条件是:values 是 Iterable 不是 List,如果需要多次遍历,必须先 copy 到本地集合,否则第二次遍历拿到的是空集。比如想先求总数再算占比,就会发现自己遍历第二次拿不到数据。
4.3 驱动类:五个必设参数和两个减配选项
驱动类把上面两个类拼装成一个 Job。五个必设参数分别是 setJarByClass、setMapperClass、setCombinerClass、setReducerClass、输出 key/value 类型。少了任何一个,作业都能提交,但行为会和预期不同:不设 setJarByClass,远程节点找不到用户类;不设输出类型,默认是 LongWritable 和 Text,reduce 输出类型不一致时序列化直接报错。
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 WordCount { public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: WordCount <input> <output>"); System.exit(2); } Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCount.class); job.setMapperClass(TokenizerMapper.class); // 中文文本统计时 Combiner 仍然适用,因为加法满足结合律 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); } }注意:输出目录不能提前存在。Hadoop 在作业启动时会检查输出路径,已存在的目录会直接触发 FileAlreadyExistsException。
两个减配选项也值得知道。如果只想验证 Mapper 到输出的过程,可以调用 setNumReduceTasks(0),作业会跳过 reduce,直接输出 map 的结果,这在排查「为什么输出为空」时非常有用。另一个是压缩相关配置,测试阶段不要开启输出压缩,否则 part 文件是 .gz 后缀,直接 cat 全是乱码,影响人工判断。
4.4 编译打包与提交:hadoop classpath 和 hadoop jar 的意义
代码写在三个文件里,编译前先确认环境里能找到 Hadoop 的类库。hadoop classpath 命令会把所有依赖 Jar 一次性拼出来,避免编译期缺包,这比手动去 lib 目录逐个找快得多。Eclipse 里配置 Hadoop 项目也是这个套路,把 $(hadoop classpath) 的输出配到项目 classpath 即可。
mkdir -p /home/user/wordcount/classes cd /home/user/wordcount javac -encoding UTF-8 -classpath $(hadoop classpath) -d classes \ TokenizerMapper.java IntSumReducer.java WordCount.java jar -cvf wordcount.jar -C classes . hadoop jar wordcount.jar WordCount /wordcount/input /wordcount/output/wc001javac 的 -encoding UTF-8 是专门为中文源码准备的,不加的话编译器在中文注释和字符串上可能出现编码警告。hadoop jar 的第一个参数是 Jar 包,第二个是主类名,后面依次是输入目录和输出目录。注意这里传的是 HDFS 路径,不要再加 file:// 前缀,否则又会绕回第 3 章说过的本地路径问题。
提交之后终端会逐行打印 map 和 reduce 的进度百分比。看到两个进度都到 100%、出现 Job completed successfully,接着用 hdfs dfs -cat /wordcount/output/wc001/part-r-00000 查看结果。如果你是在 Windows 下用 IDEA 搭 Hadoop 开发环境,可以在本地用 Configuration 加载四个 XML 配置调试,但提交到伪分布式集群仍然建议用 Linux 命令行,不要试图把 IDEA 的本地提交和远程集群调度混在一起。
5. 词频统计避坑:5 条真实翻车记录与排查顺序
5.1 现象:作业提交后一直 Running job,进度停在 0%
这是伪分布式里最常见的问题,几乎每个初学者都遇到过。hadoop jar 提交后,日志停在 Running job,等十分钟还是 0%,不是数据量大,是任务根本没被调度。
原因有两种。第一种是 YARN 没真正起来,可能 start-yarn.sh 忘了执行,或者 ResourceManager 进程被系统杀掉;第二种是 yarn-site.xml 的 aux-services 漏配,NodeManager 不知道要为 shuffle 开辅助服务,Map 阶段完成后 Reduce 无法拉数据,整个作业僵在那里。
解决按顺序排查:先 jps 看有没有 ResourceManager 和 NodeManager;再执行 yarn node -list -all 看节点状态;没反应就去 $HADOOP_HOME/logs/yarn-*.log 看 ResourceManager 的启动日志;确认进程都在后,检查 yarn-site.xml 里 aux-services 是否写了 mapreduce_shuffle。这四步走完,绝大多数「永远 Running」都能定位,不用重启机器,也别靠玄学。
5.2 现象:作业秒完,输出目录里只有 _SUCCESS,没有词频结果
作业状态显示成功,但 part-r-00000 是空的,或者文件里只有几行,和预期差很远。这种「黑匣子式」的成功最让人头疼,因为它不报错。
原因通常是两类:输入目录是空的,或路径名写错指到一个不存在的目录;Mapper 里切词后所有 token 都为空,最常见是用了 \W+ 处理全中文文本。这两类在日志里看 counter 就能发现:map input records 是 0,或者 mapper output records 是 0。还有一种属于作业直接失败,日志里出现 InputMismatchException,那就要查 Mapper 和 Reducer 的泛型是否匹配,常见是输出 key/value 类型写反。
排查时我一般会在驱动类里临时加 setNumReduceTasks(0),重跑一次看 map 的中间结果。有产出就说明 map 正常、问题在 reduce;map 输出是 0,问题在切词或输入路径。定位后把临时代码改回去。这条排查路径比一行行读源码高效得多。
5.3 现象:中文统计结果全乱码,或第一个词前面带着隐形字符
输入准备和代码都对,结果里的中文却变成问号,或者排序后第一个词看起来空白但实际存在。这类问题的根源都在编码链路:文件编码、编译编码、运行编码三处必须统一成 UTF-8。
文件侧的处理在第 3 章已经给过 iconv 命令;编译侧用 javac -encoding UTF-8;运行侧在 hadoop jar 前设置 HADOOP_OPTS。三步做完,中文乱码基本不会出现。BOM 导致的隐形前缀词单独说明:把输出用 hdfs dfs -cat 导出来,在 Linux 上用 sed -n l 查看第一个词的不可见字符,如果看到 \ufeff,说明输入文件带 BOM,去掉 BOM 后重跑。
export HADOOP_OPTS="-Dfile.encoding=UTF-8" hadoop jar wordcount.jar WordCount /wordcount/input /wordcount/output/wc0015.4 现象:本地用 IDE 跑通了,命令行提交却报 Input path does not exist
在 Windows 下用 IDEA 搭 Hadoop 开发环境的人最容易遇到这个割裂:代码在本地调试能出结果,换成 hadoop jar 提交,报错路径不存在。
原因很直接:IDEA 里 Configuration 默认使用本地文件系统,你给的路径被解析成 file:/ 开头;而伪分布式集群要求 hdfs:// 开头的路径。解决方法是提交前确认路径、上传、再提交三步走:hdfs dfs -ls /wordcount/input 看文件在不在;不在就先 put;然后传给 WordCount 的两个参数写 HDFS 路径。同一个作业,IDE 里跑通只说明代码逻辑正确,不代表数据到了 HDFS,这是两个环境,排错时别混着看。
5.5 现象:容器被 ApplicationMaster 反复 kill,日志说超出物理内存
作业一开始跑就报 Container killed by the ApplicationMaster,或者 running beyond physical memory limits,尤其容易出现在单机内存不大、又同时跑多个 Map 任务的伪分布式上。
原因是默认参数和物理机内存不匹配。YARN 默认按节点资源调度,单机 8G 内存跑一个作业,mapreduce.map.memory.mb 默认 1024MB,Map 任务多了以后,叠加系统开销很容易触顶。解决思路是调小 YARN 的资源预算而不是加内存:yarn.nodemanager.resource.memory-mb 设为物理内存的一半左右,yarn.scheduler.maximum-allocation-mb 同步调小,mapreduce.map.java.opts 的 -Xmx 控制在 768m 以内。注意调参数是止血,真正的长期方案是上完全分布式,或者换更大内存的机器。
6. 从「跑通」到「跑对」:用英文文本验证正确性,再拿中文文本做边界测试
6.1 用已知答案的文本做回归
作业跑通后第一件事不是看结果,是验证结果对不对。我会准备一份只有三个词的小文本,比如 a b a c b a,期望输出是 a 3、b 2、c 1。跑完用命令对比:
echo "a b a c b a" | tr ' ' '\n' | LC_ALL=C sort | uniq -c hdfs dfs -cat /wordcount/output/wc001/part-r-00000两侧结果一致,作业才算真跑通。对比时注意把本地排序的语言环境固定为 C,否则 sort 的排序规则和 Hadoop 的 Text 字节序不一致,会误判为结果错误。
6.2 下一步值得做的两个方向:合并去重和资源调度观察
跑通后想进阶,最自然的延伸是改 Mapper 的输出 key。把「词频统计」改成「合并去重」只需要换 Reducer 逻辑:统计时对 value 求和,去重时直接输出 key,一条链路上换一段代码得到两种结果,这正是 hadoop 课程里常用的一题多改。另一个方向是去对比 Spark 和 Flink 各自版本的 wordcount,理解同样的 Map-Reduce 思想在内存计算框架下为什么有不同表现,但 Hadoop 这一版的核心价值在于把 HDFS、MapReduce、YARN 的完整链路走一遍,这个体验不可替代。
作业提交到 YARN 的流程也可以通过 Web 界面观察:默认 8088 端口打开 ResourceManager 页面,能看到 Application 的状态从 ACCEPTED 到 RUNNING 再到 FINISHED 的完整转换。把这个过程截下来,课程设计答辩时比贴代码更有说服力。
我自己养成的习惯是:每次作业结束,先看 _SUCCESS 是否存在,再看 counter 里 map input records 与 reduce output records 是否对得上,最后才打开结果文件。三个点确认完,才敢说这个作业是真的完成了。这套验证习惯沿用到现在,希望帮到你。
本文还有配套的精品资源,点击获取