☰
Hadoop词频统计全流程实战:MapReduce与HDFS的工程应用解析
2026/10/14 6:01:11 网站建设 项目流程

简介:这是一份面向Hadoop初学者的完整词频统计MapReduce资源,基于Hadoop2.2.0环境编写,适合正在学习分布式计算、希望掌握MapReduce编程模型的数据开发人员。压缩包共17个文件,含7个Java源码、7个编译后的class、1个约十万单词的测试文本及Eclipse工程配置文件(.project、.classpath),整体仅154KB,轻量便携。源码与class并存,可直接运行也可对照研读;十万级单词的测试数据能有效验证Mapper分词、Reducer汇总及Job调度的实际表现。资源已吸引5868人学习,配套作者提供的详细解说,可帮助理解从输入分片、Map阶段、Shuffle到Reduce的完整流程。作为经典入门案例,它能让读者快速熟悉自定义Mapper与Reducer类的编写、输入输出键值对的设定,以及作业运行结果的解析方法。整个工程麻雀虽小五脏俱全,对初学者理解分布式计算中的数据流和任务调度很有帮助,是快速上手离线计算的实用参考。

1. 把一批文本丢进 Hadoop 数词频:这活儿看着简单,坑比想象中多

如果你手里有几百 MB 甚至上 GB 的日志、网页正文或爬虫抓回来的文本,想统计哪些词出现得最多,单机脚本往往是最先想到的方案。可数据一旦涨到单机内存放不下、单线程跑不动,你就得把计算搬到集群上。Hadoop 词频统计就是经典的 MapReduce 入门案例:它用「分而治之」的思路把大文本拆成小块并行处理,再把结果归并成一份词频表。这个完整版资源涵盖了环境准备、HDFS 数据落地、Mapper/Reducer 编写、打包提交、参数调优到作业排错的全流程,适合刚接触 Hadoop 的开发者,也适合准备把课堂作业搬到真实集群上跑一遍的人。读懂它,你顺便也就理解了 MapReduce 的运行模型和 HDFS 的文件组织方式——这比单纯背概念有用得多。

2. 先把环境立起来:运行边界、HDFS 数据落地与输入格式约定

2.1 伪分布式还是真实集群:先搞清楚计算跑在哪里

词频统计的代码在不同运行环境下差别并不大,但环境本身的差异会直接影响你排查问题的思路。Hadoop 的安装部署常见有两种形态:单机伪分布式和真实多节点集群。

伪分布式适合本机学习与调试。它在一台机器上同时启动 NameNode、DataNode、ResourceManager、NodeManager 四个进程,模拟出一个最小集群。资源有限时,这是最快能跑通词频统计的方式。配置上需要修改etc/hadoop下的四个 XML 文件:

<!-- core-site.xml:指定 NameNode 地址 --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration>
<!-- hdfs-site.xml:伪分布式副本数设为 1 --> <configuration> <property> <name>dfs.replication</name> <value>1</value> </property> </configuration>
# mapred-site.xml 通常由模板复制而来 cp $HADOOP_HOME/etc/hadoop/mapred-site.xml.template $HADOOP_HOME/etc/hadoop/mapred-site.xml

fs.defaultFS决定了所有 HDFS 路径的根地址,后续你在命令行里写的/wordcount/input,实际对应的是hdfs://localhost:9000/wordcount/input。伪分布式下副本数必须显式设为1,否则 DataNode 只有一台,副本无法落盘,HDFS 会一直处于副本缺失状态,文件上传后看起来正常,实际数据块永远不满足健康要求。

如果条件允许,也可以用三节点或更多节点搭真实集群。多节点下要注意 NameNode 和 ResourceManager 所在节点的内存分配,默认堆内存往往不够用,启动后卡在SafeMode或频繁 Full GC,这类问题的典型特征是「进程在但服务不可用」。对于只想跑通词频统计的场景,我没有必要建议你直接上多节点——先在伪分布式上把逻辑吃透,代价小得多。

2.2 HDFS 目录与文件准备:让数据先进集群

代码写好之前,数据必须先进 HDFS。MapReduce 作业读取的是 HDFS 上的文件路径,而不是本地文件路径。这一步经常有人忽略:本地明明有input.txt,作业提交后却报FileNotFoundException,原因就是路径写错了位置。

# 创建输入目录 hdfs dfs -mkdir -p /wordcount/input # 把本地文件上传到 HDFS hdfs dfs -put ./data.txt /wordcount/input/ # 确认文件确实落到了 HDFS hdfs dfs -ls /wordcount/input

-mkdir -p的作用是递归创建路径,/wordcount和/wordcount/input会一次建好,避免「父目录不存在」的报错。上传完成后,-ls是最基本的校验手段,你需要在输出里看到文件大小和副本数,副本数显示为1是伪分布式的正常状态,显示为0则说明数据块异常,后面跑作业大概率会失败。

输入数据放在哪个目录、目录叫什么名字都不重要,重要的是路径前后一致。后续提交作业时,hadoop jar命令里写的输入路径必须和这里完全一致。路径写错的表现非常统一:提交后几秒内报错退出,错误信息里带着文件不存在的描述,遇到这种报错先回来检查路径。

2.3 数据清洗与格式约定:别让脏数据污染词频

文本数据很少是干干净净的。爬虫抓来的页面夹杂 HTML 标签,日志里带时间戳和 IP,中文文本可能存在全角标点。词频统计对输入格式有一个最简单的约定:一行作为一条记录。

# 建议先看一眼原始数据长什么样 hdfs dfs -cat /wordcount/input/data.txt | head -50

head命令在大文件场景下尤其重要。几百 MB 的文件你不可能全部看一遍,但前 50 行通常能暴露大部分问题:行分隔符是不是\n、文件是不是 UTF-8 编码、有没有空行、有没有混入二进制字符。这些信息直接决定你的 Mapper 里需要做多少预处理逻辑。

如果你手头的数据完全没清洗过,我一般会先用本地命令做一轮粗洗,再上传 HDFS。

# 本地粗清洗:去 HTML 标签、压缩连续空格、删空行 sed 's/<[^>]*>//g' raw.html | tr -s ' \t' ' ' | sed '/^$/d' > data.txt # 确认编码是 UTF-8,避免后续乱码导致词频统计结果不可信 file data.txt

sed的标签去除规则只适用于简单场景,嵌套复杂或属性里带>的 HTML 会被误删,但作为预处理已经比什么都不做强。编码检查在这套流程里几乎算得上必做项——Hadoop 默认按 UTF-8 解析文本,如果文件实际是 GBK 编码,Mapper 读进来的中文会变成乱码,词频统计结果就全废了。后面避坑章节我会再展开讲这个问题。

3. MapReduce 核心实现:从分词逻辑到 Combiner 的取舍

3.1 整体思路:Map 阶段拆分、Reduce 阶段聚合

词频统计的 MapReduce 思路本身不复杂:Map 阶段把每一行文本拆成单词,每遇到一个单词就输出一次(单词, 1);Reduce 阶段把相同单词的所有计数加起来,输出(单词, 总次数)。这个思路的合理性在于,每个单词的计数完全独立,可以并行计算互不干扰,这正是 MapReduce 擅长的场景。

但完整的词频统计不是「能跑」就行。真正决定代码质量的,是分词规则怎么定、大小写怎么处理、标点怎么过滤、以及中间结果怎么减少网络传输。这些细节会直接影响最终词频表的准确性。下面我从 Mapper 写到 Reducer,逐步把每个决策点讲清楚。

3.2 Mapper 实现:分词、清洗与上下文输出

package org.example; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; import java.util.StringTokenizer; public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private static final IntWritable ONE = new IntWritable(1); private Text word = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 将输入行转成 String 并统一小写,避免 "Hadoop" 和 "hadoop" 被统计成两个词 String line = value.toString().toLowerCase(); // 用 StringTokenizer 按空格切分,比 split 更高效 StringTokenizer tokenizer = new StringTokenizer(line); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); // 过滤掉空串和纯标点符号 if (token.isEmpty()) { continue; } // 去除单词前后残留的标点,例如 "hello," -> "hello" String cleaned = token.replaceAll("^[^a-z0-9]+|[^a-z0-9]+$", ""); if (cleaned.isEmpty()) { continue; } word.set(cleaned); context.write(word, ONE); } } }

这里有几个关键决策点。LongWritable作为输入 key 是 Hadoop 框架的固定设计,它代表当前行在文件中的偏移量;Text是 Hadoop 的 UTF-8 文本类型,比 Java String 更节省序列化开销。输出类型定为Text和IntWritable,后续 Reducer 的输入类型必须与之对应。

toLowerCase()会把所有单词统一成小写,让The和the合并统计。这个规则并非对所有人都合适——如果你要统计的是专有名词或代码标识符,大小写区分恰恰是必要的。replaceAll这一行做的是去除单词首尾的标点,中间的连字符和撇号保留,比如state-of-the-art会被当作一个词,don't不会被拆开。如果你希望连字符也作为分隔符,把正则改成[^a-z0-9]整体过滤即可。

3.3 Reducer 实现:累加、字段复用与空值处理

package org.example; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class WordCountReducer 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); } }

Iterable<IntWritable>是框架保证的:同一 key 的所有 value 会被分到同一个 Reducer 的同一个 reduce 方法调用里。这个机制由 Map 阶段的 shuffle 和 sort 过程完成,不需要你关注细节。

这里有一个实用的小技巧:IntWritable实例只在循环外创建一次,result.set(sum)复用同一个对象。Hadoop 的序列化机制在大量输出时对对象创建非常敏感,频繁new会导致 GC 压力明显上升。这个优化在大数据量下收益明显,小文件场景看不出差别,但养成分块复用的习惯不是坏事。

如果你想让输出结果按词频从高到低排序,标准的 wordcount Reducer 是做不到的,因为 Reduce 阶段按键排序而非按值排序。常见做法是跑一个二次 MapReduce:第一个作业输出(单词, 词频),第二个作业把键值对倒置成(词频, 单词),Hadoop 默认按 key 排序,词频自然就排好了。

3.4 别忘了 Combiner:先做一轮本地聚合

Combiner 是词频统计里性价比最高的优化手段。它的作用是在 Map 端输出数据进入 shuffle 之前,先做一轮局部聚合。以 wordcount 为例:某个 Map 任务处理了 1000 个(hadoop, 1),没有 Combiner 时这 1000 条记录全部通过网络传输到 Reducer;加上 Combiner 后,Map 端先聚合成(hadoop, 1000),网络传输量直接少三个数量级。

// 在 Driver 中设置 Combiner job.setCombinerClass(WordCountReducer.class);

Combiner 可以复用 Reducer 类,因为词频统计的聚合操作是幂等的——局部求和再全局求和,与直接全局求和结果一致。但这不是所有场景都成立,比如求平均值就不能直接复用 Reducer,局部平均和全局平均是两回事。如果你把 Combiner 用在求平均值的作业里,结果会完全错乱,这点务必记住。

Combiner 不一定会被调用,这是 Hadoop 框架的自由裁量。当 Map 端输出数据量很小时,框架可能直接跳过 Combiner,因为聚合收益极低。所以不要写依赖 Combiner 必然执行才能正确的业务逻辑,它只是一个性能优化手段,不参与正确性保障。

3.5 Driver 组装:把 Mapper、Reducer 与类型配置串起来

package org.example; 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 { if (args.length != 2) { System.err.println("Usage: WordCountDriver <input path> <output path>"); System.exit(-1); } Configuration conf = new Configuration(); Job job = Job.getInstance(conf, "word count"); job.setJarByClass(WordCountDriver.class); job.setMapperClass(WordCountMapper.class); job.setCombinerClass(WordCountReducer.class); job.setReducerClass(WordCountReducer.class); // 设置 Mapper 输出类型,若与 Reducer 输出类型不同则必须单独指定 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); // 设置 Reducer 输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); boolean success = job.waitForCompletion(true); System.exit(success ? 0 : 1); } }

setJarByClass用于指定 Jar 包的定位方式,它会在运行时找到包含当前类的 Jar,避免「找不到主类」之类的低级错误。setMapOutputKeyClass和setMapOutputValueClass在这个例子里与 Reducer 输出类型相同,但很多实际作业里二者并不一致。一旦 Mapper 输出类型与 Reducer 实际声明不一致,运行时会直接抛类型转换异常,而且是发生在大量数据正在传输时,排错成本非常高。养成显式声明的习惯,能省掉后续大量的排查时间。

waitForCompletion(true)的参数表示是否打印进度信息。本地调试阶段建议保持true,你会实时看到 Map 和 Reduce 的进度百分比,以及每个阶段的耗时。生产环境可以改成false减少日志量,但排错时没人会这么干。

4. 打包与提交:从 Maven 构建到作业状态判读

4.1 用 Maven 构建 Jar:依赖别写错 scope

代码写好后,需要打成 Jar 包提交到集群。这里有一个新手高频翻车点:把 Hadoop 依赖打成compile范围,导致打出来的 Jar 体积巨大,提交后 ClassNotFound 或版本冲突问题此起彼伏。正确做法是把 Hadoop 相关依赖声明为provided,因为这个 Jar 运行在已有 Hadoop 环境的集群上,CLASSPATH 里已经包含了所有 Hadoop 类库。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>wordcount</artifactId> <version>1.0</version> <packaging>jar</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.3.4</version> <scope>provided</scope> </dependency> </dependencies> </project>

版本号需要与你的集群版本匹配。伪分布式环境中,用hadoop version查一下准确的版本号,再填到pom.xml里。版本不匹配的典型症状是作业能提交但运行时报NoSuchMethodError或NoClassDefFoundError,这类错误在词频统计这种简单作业里极少出现,一旦出现先查版本。

打包时如果本地没有 Hadoop 依赖,Maven 会先去中央仓库下载,这一般没有问题。但如果你在离线环境,需要把依赖提前放到本地仓库或使用私服,否则mvn package会卡在下载阶段。

# 打包并跳过测试 mvn clean package -DskipTests # 确认 jar 包已生成且大小合理(provided 依赖下通常只有几十 KB) ls -lh target/wordcount-1.0.jar

-DskipTests跳过单元测试加速构建,因为词频统计没有本地测试用例,跑了也是浪费时间。Jar 包大小是一个很好的健康指标:几十 KB 说明依赖没被打进去,几 MB 甚至几十 MB 说明provided配置出了问题。

4.2 提交作业:命令参数与原理解读

Jar 包就绪后,接下来就是把作业提交到 YARN 上。提交命令本身的参数不多,但每个参数都对应着一个运行环节:

hadoop jar target/wordcount-1.0.jar \ org.example.WordCountDriver \ /wordcount/input \ /wordcount/output

hadoop jar命令会向 ResourceManager 提交一个 Application,YARN 会为这个 Application 启动一个 Container 运行main方法。org.example.WordCountDriver是主类全限定名,如果你在 Jar 清单里配置了Main-Class,这个参数可以省略,但显式写上更稳妥,特别是在一个 Jar 打包多个作业的情况下。

输入路径/wordcount/input必须是 HDFS 上的路径,输出路径/wordcount/output则是作业运行完成后结果的存放位置。输出路径有一个硬性约束:必须不存在。Hadoop 设计上不允许覆盖输出目录,如果该路径已存在,作业会直接失败。这个坑太常见了,我会在避坑章节专门展开。

4.3 作业状态检查:别被 SUCCEEDED 骗了

作业提交后,控制台会持续打印进度信息。等到出现Job succeeded字样,你还需要做两件事:确认结果文件确实生成、抽查内容是否合理。

# 查看输出目录 hdfs dfs -ls /wordcount/output # 查看结果文件 hdfs dfs -cat /wordcount/output/part-r-00000 | head -20

输出目录下通常会有一个或多个part-r-xxxxx文件,这些就是 Reducer 写出的结果。如果你设置了 0 个 Reducer,文件名会变成part-m-xxxxx,这个差异可以用来快速判断你的作业走的是 Map-only 还是完整流程。

head -20看结果时,前几行往往是字母排序靠前的单词,比如a、about、above。如果你看到一堆乱码或空行,说明编码或预处理环节出了问题。结果文件不能只用head看,还要看文件大小和行数,这个细节留到最后一章讲验证方法。

5. 运行词频统计的五个经典翻车点:现象、原因与排查路径

5.1 输出目录已存在,作业秒失败

现象:提交作业后几秒钟内报错退出,错误信息里出现FileAlreadyExistsException或类似描述,指向你设置的输出路径。

原因:Hadoop 显式禁止输出目录已存在的情况。这个设计是为了避免多次运行同一作业时,结果数据新旧混杂,你无法判断当前拿到的是哪一次运行的内容。

解决:每次运行前清理输出目录,或让输出目录名带上运行批次编号。

# 运行前删除已有输出目录 hdfs dfs -rm -r /wordcount/output

我在调试时每次跑作业前都会强制走一遍这个命令,哪怕心里确信输出目录不存在。这个操作成本极低,但能省掉一次失败的等待。

5.2 Mapper 输出类型与 Reducer 输入类型不一致

现象:作业在 Reduce 阶段大量报错,日志里出现ClassCastException或Type mismatch in key from map。

原因:Mapper 输出的是Text和IntWritable,但 Reducer 的泛型声明写成了Text和LongWritable,或 Driver 里setMapOutputKeyClass配置错误。Shuffle 过程中数据的序列化类型对不上,反序列化时直接崩溃。

解决:在 Driver 里显式声明setMapOutputKeyClass和setMapOutputValueClass。如果 Mapper 输出类型和 Reducer 输出类型一致,很多教程会建议省略,但我实践下来,显式写出来能迫使你在代码里把类型链路确认一遍。凡是涉及类型不匹配,日志里都带Type mismatch in key字眼,看到这个直接检查泛型和 Driver 配置。

5.3 文件编码不对,中文词频统计结果全是乱码

现象:结果文件里中文字符显示为�或成串的十六进制符号,数字和英文词频看起来正常。

原因:Hadoop 默认按 UTF-8 解析输入文件。如果原始文件是 GBK 或 GB2312 编码,中文字符被按 UTF-8 解码后就成了乱码,完整词被切成不可读的字节串,统计结果自然不可用。

解决:在上传 HDFS 前用file命令确认编码,非 UTF-8 文件先转码再上传。

# 查看文件编码 file data.txt # GBK 转 UTF-8 iconv -f GBK -t UTF-8 data.txt > data_utf8.txt

iconv命令在 Linux 和 macOS 上都自带,Windows 上可以用编辑器另存为 UTF-8 编码。从那以后我每次处理文本数据,上传 HDFS 之前都会强制用file命令确认一遍编码,这个习惯帮我规避了大量不可见的数据污染问题。

5.4 所有数据都跑进一个 Reducer,参数调整无效

现象:作业有多个 Reducer,但运行结束后只生成了一个part-r-00000文件,所有结果都在里面。

原因:这种情况常见于设置了setNumReduceTasks(1)但没改回来,或者输入数据量太小,Hadoop 根据数据大小自动把 Reducer 数降到了 1。第二种情况不是错误,在小数据量下 1 个 Reducer 反而是合理的。

解决:先确认实际生效的 Reducer 数量,再决定是否调整。可以通过-D mapreduce.job.reduces=N指定,或者在 Driver 里用job.setNumReduceTasks(N)。对于词频统计,Reducer 数量和结果文件数量一致,如果你想后续做分布式查询,把结果拆成多个文件比单文件更好。

5.5 作业一直处于 ACCEPTED 状态,半天不启动

现象:提交作业后,日志显示Application is ACCEPTED,但迟迟不进入 RUNNING 状态,等了很久还是没反应。

原因:YARN 的资源调度器没有足够的可用资源启动新的 Container。常见于同一台机器上跑着其他占资源的服务,或者集群配置的内存上限很低,单个作业申请的资源超过了集群可用总量。

解决:查看 YARN 的资源状态,动态调整作业申请的资源量。

# 查看集群资源使用情况 yarn resourcemanager -ui 8088 # 浏览器访问后查看应用列表和资源信息 # 或减小作业申请的内存 hadoop jar target/wordcount-1.0.jar \ org.example.WordCountDriver \ -D mapreduce.map.memory.mb=512 \ -D mapreduce.reduce.memory.mb=512 \ /wordcount/input /wordcount/output

mapreduce.map.memory.mb和mapreduce.reduce.memory.mb控制每个 Map/Reduce 任务申请的内存。如果你在伪分布式上运行,默认配置往往会申请 1 GB 甚至更多,而机器本身只有 4 GB 内存,资源不够就会出现这个现象。

6. 从跑通到可信:结果验证、小文件合并与输出压缩

先讲结果怎么验证。很多人跑完作业看到SUCCEEDED就觉得完事了,但作业成功只代表流程执行完毕,不代表结果正确。我一般会找一个几百 KB 的小文件,先在本地用标准 Linux 命令统计一遍,再和 Hadoop 的结果对比。

# 本地统计词频:把文本拆成每行一个词,排序后统计唯一词数量 cat data.txt | tr -s ' \t\r\n' '\n' | sort | uniq -c | sort -rn | head -20 # 查看 Hadoop 词频统计结果 hdfs dfs -cat /wordcount/output/part-r-00000 | sort -t $'\t' -k2 -nr | head -20

这里有个关键点:uniq -c的统计顺序是按键排序而不是按词频排序,所以两个命令都需要加sort -rn按词频从高到低排列。如果本地结果的前 20 个词和 Hadoop 结果的前 20 个词高度重合,说明整个链路基本可信。如果发现某个词两边数值差很大,先检查 Mapper 里的清洗规则是否和本地的tr清洗规则一致——常见的隐藏问题是标点处理,比如hello,和hello在本地被拆成不同词,而 Hadoop 的 Mapper 会把逗号去掉合并成一个词。遇到这种情况不是谁对谁错的问题,而是规则不一致,你需要确定一个统一的清洗逻辑。

再讲小文件问题。词频统计的输入是一堆小文件时,默认的TextInputFormat会为每个文件至少分配一个 Map 任务。假设有 10000 个 1 KB 的小文件,就会启动 10000 个 Map 任务,每个任务的启动开销比处理数据本身还大。解决方法是合并输入分片:

// 在 Driver 中设置 CombineTextInputFormat import org.apache.hadoop.mapreduce.lib.input.CombineTextInputFormat; job.setInputFormatClass(CombineTextInputFormat.class); // 每个分片建议大小,单位字节 CombineTextInputFormat.setMaxInputSplitSize(job, 67108864); // 64 MB

setMaxInputSplitSize的语义是「尽量将小文件合并到不超过该大小的分片」,而不是每个分片恰好这么大。64 MB 与 HDFS 默认块大小一致,是最常用的配置值。合并后 Map 任务数量大幅减少,作业的调度开销和资源占用都会显著下降。

最后讲输出压缩。词频统计结果如果只是给人看的,不压缩也行;但如果结果要作为下一阶段作业的输入,或者需要长期保存,压缩能节省大量存储空间。

# 提交作业时开启输出压缩 hadoop jar target/wordcount-1.0.jar \ org.example.WordCountDriver \ -D mapreduce.output.fileoutputformat.compress=true \ -D mapreduce.output.fileoutputformat.compress.codec=org.apache.hadoop.io.compress.GzipCodec \ /wordcount/input /wordcount/output

GzipCodec是通用性最好的选择,压缩率高且后续任何工具都能解压。如果你不在乎压缩率只在乎速度,可以换成SnappyCodec。开启压缩后,输出目录里生成的是part-r-xxxxx.gz,用hdfs dfs -text可以直接读取压缩内容,不需要先解压:

hdfs dfs -text /wordcount/output/part-r-00000.gz | head -20

从那以后我每次跑完 MapReduce 作业,都会带着三个习惯收尾:先对比小文件本地统计结果验证正确性;检查输出目录下是否有过多小文件需要合并;需要长期留存的中间结果一律开压缩。这套流程跑顺之后,词频统计就不再是「跑通就行」的黑匣子,而是每一步都能说清楚在干什么的工程实践了。希望帮到你。

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

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

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

立即咨询