首页 / 资讯中心 / 文章详情

Hadoop词频统计全链路解析:InputSplit、Combiner与SequenceFile生产实践

Hadoop词频统计全链路解析:InputSplit、Combiner与SequenceFile生产实践 ★ FEATURED ARTICLE
简介本资源是面向大数据初学者与Hadoop入门实践者的完整词频统计MapReduce项目聚焦分布式文本处理核心场景适用于课程实验、课设开发及Hadoop 2.x环境下的MapReduce编程训练。压缩包共17个文件含7个Java源码涵盖Mapper、Reducer、Driver等关键组件、7个编译后class文件、1个约十万单词的测试文本10 Steps To Sales Success.txt以及.project和.classpath等Eclipse工程配置文件整体仅154KB轻量易导入、即开即用。已有5868人学习下载说明其在Hadoop基础实践领域具备广泛参考价值。读者可直接运行复现完整词频统计流程深入理解InputFormat切片机制、Shuffle阶段键值对聚合逻辑、自定义WritableComparable排序实现以及本地模式调试与集群提交的差异要点是掌握MapReduce编程范式的典型闭环案例。1. Hadoop词频统计完整版不是跑通WordCount就完事而是把输入切分、Shuffle机制、Combiner作用、输出压缩全链路拧紧你肯定见过那个被讲烂的 WordCount 示例hadoop jar hadoop-mapreduce-examples-*.jar wordcount /input /output回车一敲结果出来就收工。但真正在某高校课程设计里交作业、在某实验室做文本预处理、或在某跨平台系统中嵌入轻量级离线分析模块时你会发现——90% 的翻车点根本不在 MapReduce 逻辑本身而在InputSplit怎么切、TextInputFormat默认行为怎么影响 key 类型、Combiner为什么没生效、SequenceFileOutputFormat输出乱码、甚至hadoop fs -put时路径末尾多了一个斜杠导致任务静默失败。这份「Hadoop词频统计完整版」不是教学演示包而是一线工程师从某图像处理Demo的文本日志分析需求倒推出来的可复现工程包它含完整源码Java Maven结构、适配 Hadoop 3.3.x 伪分布式环境的配置模板、带注释的core-site.xml/hdfs-site.xml/mapred-site.xml/yarn-site.xml四文件最小集、以及一个能验证InputSplit实际大小与块对齐关系的调试脚本。适合正在啃《Hadoop安装与配置》却卡在“任务提交后没报错也没输出”的人也适合需要把词频结果喂给下游 Spark 或 Flink 做二次聚合的熟手——因为它的输出格式、压缩编码、分区策略全按生产级接口对齐。2. 为什么必须重写 WordCount从 InputSplit 切分逻辑到 Combiner 生效条件的硬核拆解Hadoop 的词频统计看似简单实则是 MapReduce 执行模型的浓缩黑匣子。很多初学者照着教程改个Mapper的context.write()就以为掌握了结果在真实数据上跑出内存溢出、Reducer 数量爆炸、或输出文件数远超预期——问题往往不出在代码而出在对InputSplit、RecordReader、Combiner这三者的协同机制理解偏差。下面我们就从数据读入的第一步开始一层层剥开这个黑匣子。2.1 InputSplit 是什么不是文件块而是逻辑切片单元InputSplit是 MapReduce 框架为每个 Mapper 分配的逻辑数据单元它不等于 HDFS Block也不等于物理文件。它的大小由minSize、maxSize和blockSize共同决定计算公式为splitSize max(minSize, min(maxSize, blockSize))默认情况下minSize 1,maxSize Long.MAX_VALUE,blockSize 128MBHadoop 3.x 默认所以splitSize ≈ blockSize。但注意如果一个文件小于splitSize它会被整个作为一个InputSplit如果大于则按splitSize切分且切分点尽量对齐 HDFS Block 边界。这直接决定了 Mapper 并行度。提示hadoop fs -ls -h /input查看文件大小再用hadoop fs -stat %o %b %n /input/file.txt查看实际 block size 和 offset比只看hadoop fs -du -h更准。2.2 TextInputFormat 的 key 类型陷阱LongWritable 不是行号而是字节偏移量很多人误以为MapperLongWritable, Text, Text, IntWritable中的LongWritable是当前行的行号1, 2, 3…其实它是该行在文件中的起始字节偏移量offset。这意味着如果文件开头有 BOM如 UTF-8-BOM第一行的 offset 可能是 3 而非 0如果文件是拼接生成的如cat a.log b.log all.log中间换行符前后的 offset 会跳变在Combiner阶段key仍是 offset但value是Text行内容——而Combiner的输入 key 必须和 Mapper 输出 key完全一致类型且语义等价否则框架无法聚合。这就是为什么你写了Combiner却发现 Reducer 收到的中间键值对数量一点没少Combiner输入 key 是Text词但Mapper输出 key 是LongWritableoffset类型不匹配框架直接跳过Combiner。2.3 Combiner 生效的三个硬性条件类型、逻辑、数据分布缺一不可Combiner不是“可选优化”而是 Map 端本地聚合的强制关卡。它要真正起作用必须同时满足类型一致Combiner的输入 key/value 类型必须与Mapper输出类型完全相同且输出类型必须与Reducer输入类型一致逻辑幂等Combiner的 reduce 逻辑必须满足结合律即combine(combine(a,b),c) combine(a,combine(b,c))。词频统计天然满足加法满足数据局部性足够同一InputSplit内出现重复词的概率要高。如果数据极度稀疏如每行都是 UUID时间戳Combiner几乎不触发。验证Combiner是否生效最直接的方法对比开启前后Map output records和Combine output records的差值。若后者为 0说明没触发若后者 ≈ 前者 × 0.7说明效果显著。2.4 为什么不用默认的 TextOutputFormatSequenceFileOutputFormat 才是生产接口TextOutputFormat输出纯文本人类可读但机器难解析无 schema、无压缩、无类型信息、无法被 Spark/Flink 直接spark.read.sequenceFile()加载。而SequenceFileOutputFormat是 Hadoop 原生二进制序列化格式支持key/value 类型强声明如Text→IntWritable内置RECORD或BLOCK级压缩io.seqfile.compression.typeBLOCK可被下游引擎直接反序列化避免 JSON/XML 解析开销支持Sync Marker便于断点续读。在某跨平台系统中我们曾因坚持用TextOutputFormat导致下游 Spark 任务每次都要rdd.map(line line.split(\t)).map(arr (arr(0), arr(1).toInt))GC 时间飙升 40%。换成SequenceFileOutputFormat后spark.read.sequenceFile[Text, IntWritable](/output)一行搞定序列化耗时下降 65%。3. 完整版词频统计源码实现Maven 结构、可配置参数、带调试开关的 Combiner本节提供可直接编译运行的 Java 源码已通过 Hadoop 3.3.6 伪分布式环境实测。项目采用标准 Maven 结构关键目录如下hadoop-wordcount/ ├── pom.xml # 指定 hadoop-client 3.3.6 slf4j-log4j12 ├── src/main/java/ │ └── com/example/wordcount/ │ ├── WordCountDriver.java # 主类含 -D 参数解析 │ ├── WordCountMapper.java # 核心 Mapper支持正则过滤 大小写归一 │ ├── WordCountCombiner.java # 显式 Combiner带 DEBUG 日志开关 │ └── WordCountReducer.java # Reducer支持 Top-K 截断 └── src/main/resources/ └── log4j.properties # 关键日志级别控制DEBUG for Combiner3.1 WordCountDriver用-D动态传参绕过硬编码配置// WordCountDriver.java public class WordCountDriver { public static void main(String[] args) throws Exception { if (args.length ! 2) { System.err.println(Usage: hadoop jar wc.jar input output); System.exit(1); } Configuration conf new Configuration(); // 从命令行动态读取参数避免改代码 conf.set(wc.min.freq, 1); // 最小词频阈值 conf.set(wc.regex.filter, [^\\u4e00-\\u9fa5a-zA-Z0-9\\s]); // 非中文/英文/数字/空格全过滤 conf.set(wc.case.sensitive, false); // 是否区分大小写 conf.set(wc.output.format, sequencefile); // text | sequencefile conf.set(wc.compress.output, true); // 是否启用 BLOCK 压缩 Job job Job.getInstance(conf, word count); job.setJarByClass(WordCountDriver.class); // 设置 Mapper/Combiner/Reducer job.setMapperClass(WordCountMapper.class); job.setCombinerClass(WordCountCombiner.class); // 显式设置不依赖推测 job.setReducerClass(WordCountReducer.class); // 设置输出格式根据配置动态切换 String outputFormat conf.get(wc.output.format, text); if (sequencefile.equals(outputFormat)) { job.setOutputFormatClass(SequenceFileOutputFormat.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); } else { job.setOutputFormatClass(TextOutputFormat.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); } }参数说明wc.min.freqReducer 端过滤低频词减少无效 shufflewc.regex.filter正则表达式定义“有效字符”默认过滤标点、特殊符号wc.case.sensitive设为false时Mapper 内部自动word.toLowerCase()wc.output.format决定最终输出是文本还是 SequenceFilewc.compress.output仅对sequencefile生效启用BLOCK级压缩。3.2 WordCountMapper行内分词 正则清洗拒绝简单 split( )// WordCountMapper.java public class WordCountMapper extends MapperLongWritable, Text, Text, IntWritable { private final static IntWritable one new IntWritable(1); private Text word new Text(); private Pattern pattern; private boolean caseSensitive; Override protected void setup(Context context) throws IOException, InterruptedException { Configuration conf context.getConfiguration(); String regex conf.get(wc.regex.filter, [^\\u4e00-\\u9fa5a-zA-Z0-9\\s]); pattern Pattern.compile(regex); caseSensitive true.equalsIgnoreCase(conf.get(wc.case.sensitive, false)); } Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line value.toString().trim(); if (line.isEmpty()) return; // 1. 先用正则替换所有非法字符为空格保留空格用于后续 split String cleaned pattern.matcher(line).replaceAll( ); // 2. 按空白字符分割支持 \s解决多个空格/制表符问题 String[] words cleaned.split(\\s); for (String w : words) { if (w.isEmpty()) continue; // 3. 大小写归一化 String wordStr caseSensitive ? w : w.toLowerCase(); word.set(wordStr); context.write(word, one); } } }关键点说明pattern.matcher(line).replaceAll( )比line.replaceAll([^a-zA-Z], )更安全避免删除中文split(\\s)而非split( )解决连续空格、Tab、换行混用场景setup()中初始化Pattern避免map()内反复编译正则提升吞吐量。3.3 WordCountCombiner带 DEBUG 开关的日志版一眼看清是否触发// WordCountCombiner.java public class WordCountCombiner extends ReducerText, IntWritable, Text, IntWritable { private static final Log LOG LogFactory.getLog(WordCountCombiner.class); private boolean debugEnabled; Override protected void setup(Context context) throws IOException, InterruptedException { Configuration conf context.getConfiguration(); debugEnabled true.equalsIgnoreCase(conf.get(wc.debug.combiner, false)); if (debugEnabled) { LOG.info( Combiner enabled and initialized ); } } Override protected void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable val : values) { sum val.get(); } if (debugEnabled sum 10) { // 仅对高频词打日志避免刷屏 LOG.info(COMBINE: [ key.toString() ] - sum); } context.write(key, new IntWritable(sum)); } }使用方式提交任务时加-D wc.debug.combinertrue然后yarn logs -applicationId app_id | grep COMBINE即可确认是否触发。3.4 WordCountReducerTop-K 截断 SequenceFile 强类型输出// WordCountReducer.java public class WordCountReducer extends ReducerText, IntWritable, Text, IntWritable { private int topK; private PriorityQueueMap.EntryText, Integer topKQueue; Override protected void setup(Context context) throws IOException, InterruptedException { Configuration conf context.getConfiguration(); topK Integer.parseInt(conf.get(wc.topk, 1000)); // 小顶堆保持最大的 K 个元素 topKQueue new PriorityQueue((a, b) - a.getValue().compareTo(b.getValue())); } Override protected void reduce(Text key, IterableIntWritable values, Context context) throws IOException, InterruptedException { int sum 0; for (IntWritable val : values) { sum val.get(); } // 堆维护 Top-K if (topKQueue.size() topK) { topKQueue.offer(new AbstractMap.SimpleEntry(new Text(key), sum)); } else if (sum topKQueue.peek().getValue()) { topKQueue.poll(); topKQueue.offer(new AbstractMap.SimpleEntry(new Text(key), sum)); } } Override protected void cleanup(Context context) throws IOException, InterruptedException { // 倒序输出从高到低 ListMap.EntryText, Integer list new ArrayList(topKQueue); list.sort((a, b) - b.getValue().compareTo(a.getValue())); for (Map.EntryText, Integer entry : list) { context.write(entry.getKey(), new IntWritable(entry.getValue())); } } }注意cleanup()中排序是必须的因为PriorityQueue本身不保证遍历顺序new Text(key)是深拷贝避免 key 被复用覆盖。4. 伪分布式环境搭建与任务提交全流程从 hadoop_home 配置到 yarn logs 排查Hadoop 伪分布式不是“单机模式”而是所有守护进程NameNode、DataNode、ResourceManager、NodeManager、JobHistoryServer在同一台机器上以独立 JVM 运行。它要求严格遵循hadoop_home环境变量、XML 配置、SSH 免密、目录权限四要素。下面是以 Ubuntu 22.04 OpenJDK 11 Hadoop 3.3.6 为例的最小可行配置清单已去除所有冗余参数仅保留运行词频统计必需项。4.1 环境变量与目录准备hadoop_home 必须指向解压根目录# 解压 Hadoop 到 /opt/hadoop-3.3.6不要用软链 tar -xzf hadoop-3.3.6.tar.gz -C /opt/ # 设置环境变量写入 ~/.bashrc export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export HADOOP_HOME/opt/hadoop-3.3.6 export PATH$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export HADOOP_CONF_DIR$HADOOP_HOME/etc/hadoop export HADOOP_MAPRED_HOME$HADOOP_HOME export HADOOP_YARN_HOME$HADOOP_HOME export HADOOP_COMMON_HOME$HADOOP_HOME export HADOOP_HDFS_HOME$HADOOP_HOME # 生效 source ~/.bashrc # 验证 hadoop version # 应输出 3.3.6注意HADOOP_HOME必须是绝对路径且不能包含符号链接如/opt/hadoop → hadoop-3.3.6。某些版本在sbin/start-dfs.sh中会用readlink -f $HADOOP_HOME软链会导致conf目录定位失败。4.2 四文件最小配置core-site.xml / hdfs-site.xml / mapred-site.xml / yarn-site.xml所有配置均位于$HADOOP_HOME/etc/hadoop/下。以下为精简后可直接覆盖使用的版本已关闭 Kerberos、HA、WebHDFS 等非必要模块core-site.xmlconfiguration property namefs.defaultFS/name valuehdfs://localhost:9000/value /property /configurationhdfs-site.xmlconfiguration property namedfs.replication/name value1/value !-- 伪分布式只需 1 -- /property property namedfs.namenode.name.dir/name valuefile:/opt/hadoop-3.3.6/data/namenode/value /property property namedfs.datanode.data.dir/name valuefile:/opt/hadoop-3.3.6/data/datanode/value /property /configurationmapred-site.xml需重命名mapred-site.xml.templateconfiguration property namemapreduce.framework.name/name valueyarn/value /property property namemapreduce.jobhistory.address/name valuelocalhost:10020/value /property property namemapreduce.jobhistory.webapp.address/name valuelocalhost:19888/value /property /configurationyarn-site.xmlconfiguration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.resourcemanager.hostname/name valuelocalhost/value /property property nameyarn.resourcemanager.address/name valuelocalhost:8032/value /property property nameyarn.resourcemanager.scheduler.address/name valuelocalhost:8030/value /property property nameyarn.resourcemanager.resource-tracker.address/name valuelocalhost:8031/value /property property nameyarn.resourcemanager.admin.address/name valuelocalhost:8033/value /property property nameyarn.resourcemanager.webapp.address/name valuelocalhost:8088/value /property /configuration权限与格式检查# 创建数据目录并赋权 mkdir -p /opt/hadoop-3.3.6/data/{namenode,datanode} chown -R $USER:$USER /opt/hadoop-3.3.6/data # 格式化 NameNode仅首次 hdfs namenode -format # 启动 HDFS YARN start-dfs.sh start-yarn.sh mapred --daemon start historyserver # 验证进程 jps | grep -E (NameNode|DataNode|ResourceManager|NodeManager|JobHistoryServer) # 应输出 5 个进程4.3 任务提交与日志定位从 hadoop fs -put 到 yarn logs 全链路# 1. 准备输入数据注意路径末尾不能有 / echo -e hello world\nhello hadoop\nhadoop is great /tmp/input.txt hadoop fs -mkdir -p /input hadoop fs -put /tmp/input.txt /input/ # 注意/input/ 末尾有 /但 /input/input.txt 末尾不能有 / # 2. 编译打包假设在项目根目录 mvn clean package -DskipTests # 输出 target/hadoop-wordcount-1.0.jar # 3. 提交任务开启 Combiner DEBUG hadoop jar target/hadoop-wordcount-1.0.jar \ -D wc.debug.combinertrue \ -D wc.topk10 \ /input /output # 4. 查看 Application ID从 stdout 复制 # 2024-05-20 10:23:45,123 INFO client.RMProxy: Connecting to ResourceManager at localhost/127.0.0.1:8032 # 2024-05-20 10:23:46,456 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1716190999123_0001 # 5. 实时查看日志重点看 Container 日志 yarn logs -applicationId application_1716190999123_0001 | grep COMBINE # 6. 查看输出SequenceFile 需用 hadoop fs -text hadoop fs -ls /output # Found 3 items # -rw-r--r-- 1 user supergroup 0 2024-05-20 10:24 /output/_SUCCESS # -rw-r--r-- 1 user supergroup 1024 2024-05-20 10:24 /output/part-r-00000 hadoop fs -text /output/part-r-00000 | head -10 # 输出示例 # hello 3 # hadoop 2 # world 1 # ...关键技巧hadoop fs -put时目标路径如果是目录末尾加/是安全的但如果是文件末尾加/会创建同名目录导致任务找不到输入yarn logs默认只查最近 3 天如需查更早日志加-am -1all attemptshadoop fs -text可直接解析SequenceFile无需写 Java 程序。5. 避坑指南5 条血泪经验总结每一条都来自某高校课程设计翻车现场Hadoop 词频统计的坑90% 都集中在环境、路径、配置、日志、数据五处。下面这 5 条是某高校课程设计中学生集中暴雷的点每条都按「现象 → 原因 → 解决」给出可立即执行的方案。5.1 现象任务提交后Running状态卡住 10 分钟最后显示FAILED但yarn logs查不到任何 Container 日志原因yarn.nodemanager.aux-services配置缺失或拼写错误如写成mapred_shuffle而非mapreduce_shuffle导致 NodeManager 无法启动 ShuffleHandlerMapper 产出的数据无法被 Reducer 拉取。解决# 检查配置 grep aux-services $HADOOP_HOME/etc/hadoop/yarn-site.xml # 必须输出nameyarn.nodemanager.aux-services/namevaluemapreduce_shuffle/value # 重启 NodeManager yarn --daemon stop nodemanager yarn --daemon start nodemanager5.2 现象hadoop fs -ls /input显示文件存在但任务报FileNotFoundException: File does not exist: /input原因core-site.xml中fs.defaultFS配置为hdfs://localhost:9000但hdfs namenode -format后未启动start-dfs.sh或NameNode进程异常退出常见于磁盘满、端口被占。解决# 1. 检查 NameNode 是否存活 jps | grep NameNode # 2. 若无输出查日志 tail -50 $HADOOP_HOME/logs/hadoop-*-namenode-*.log | grep -i error\|exception # 3. 常见修复清空 data 目录后重 format仅开发环境 rm -rf /opt/hadoop-3.3.6/data/namenode/* hdfs namenode -format start-dfs.sh5.3 现象输出文件part-r-00000内容是乱码如SEQ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^......原因使用TextOutputFormat但误用hadoop fs -text查看-text只支持SequenceFile、Avro等二进制格式或使用SequenceFileOutputFormat但未指定 key/value 类型导致序列化失败。解决# 先确认输出格式 hadoop fs -ls /output/part-r-00000 | grep sequence # 若是 SequenceFile用 -text若是普通文本用 -cat # 若是 SequenceFile 但乱码检查 WordCountDriver 中是否设置了 // job.setOutputKeyClass(Text.class); // job.setOutputValueClass(IntWritable.class); // 且 job.setOutputFormatClass(SequenceFileOutputFormat.class);5.4 现象Combiner 日志里有COMBINE: [hello] - 12但最终输出中hello频次却是8原因Reducer中启用了wc.topk10但hello的全局频次是12而topk截断逻辑在cleanup()中执行Combiner输出的12被Reducer正常接收只是最终没进入 Top-10 列表。解决不要依赖Combiner日志判断最终结果验证最终输出用hadoop fs -cat /output/part-r-00000 | head -20如需调试全量临时注释Reducer.cleanup()中的topK逻辑。5.5 现象本地运行mvn exec:java成功但hadoop jar提交后报ClassNotFoundException: com.example.wordcount.WordCountMapper原因Maven 打包时未将依赖打入 fat jarhadoop jar运行时 classpath 只有 Hadoop 自带 jar找不到你的类。解决修改pom.xml使用maven-shade-plugin打包plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goals goalshade/goal /goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.example.wordcount.WordCountDriver/mainClass /transformer /transformers /configuration /execution /executions /plugin然后mvn clean package使用target/hadoop-wordcount-1.0-jar-with-dependencies.jar提交。6. 进阶技巧用 InputSampler TotalOrderPartitioner 实现全局有序输出以及如何验证 InputSplit 实际切分词频统计的终极需求往往不是“算出频次”而是“按频次从高到低排序并分片存储”。Hadoop 原生提供了TotalOrderPartitioner配合InputSampler实现全局排序无需在 Reducer 端做二次排序。本节就带你把这一能力落地并手把手教你验证InputSplit到底怎么切——因为很多面试题如“25. 在一个运行的 hadoop 任务中,什么是 inputsplit?”考的就是你能不能拿出证据。6.1 全局有序输出三步启用 TotalOrderPartitionerTotalOrderPartitioner的核心思想是先采样输入数据生成一个“分位点文件”partition file它定义了每个 Reducer 应该处理的 key 范围如 Reducer 0 处理a~mReducer 1 处理n~z。这样每个 Reducer 输出的文件天然有序且合并后仍是全局有序。步骤 1生成分位点文件采样# 使用内置的 RandomSampler对 /input 下所有文件采样 0.110%数据生成 2 个分区即 2 个 Reducer hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \ randomwriter -D mapreduce.randomwriter.bytespermap10000000 \ -D mapreduce.randomwriter.maps1 \ /tmp/random-input # 采样并生成 partition file2 个 reducer hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar \ terasort -D mapreduce.partition.keypartitioner.options-r 2 \ /tmp/random-input /tmp/partitions # 提取 partition file实际是 SequenceFile hadoop fs -get /tmp/partitions/part-r-00000 ./partitions步骤 2修改 WordCountDriver启用 TotalOrderPartitioner在WordCountDriver.java的main()方法中job.waitForCompletion(true)前插入// 启用 TotalOrderPartitioner job.setPartitionerClass(TotalOrderPartitioner.class); // 设置 partition file 路径必须是 HDFS 路径 TotalOrderPartitioner.setPartitionFile(job.getConfiguration(), new Path(/tmp/partitions/part-r-00000)); // 强制 Reducer 数量与 partition file 分区数一致 job.setNumReduceTasks(2);步骤 3提交任务验证输出有序hadoop jar target/hadoop-wordcount-1.0-jar-with-dependencies.jar \ -D wc.output.formatsequencefile \ /input /output-sorted # 查看两个输出文件 hadoop fs -cat /output-sorted/part-r-00000 | head -5 hadoop fs -cat /output-sorted/part-r-00001 | head -5 # 你会发现 part-r-00000 全是 a~m 开头的词part-r-00001 全是 n~z 开头的词6.2 验证 InputSplit 实际切分写一个 SplitInspector 工具类光说InputSplit按 block size 切不如直接打印出来看。下面这个工具类能精确告诉你某个文件被切成了几个InputSplit每个start、length、locations是多少。// SplitInspector.java public class SplitInspector { public static void main(String[] args) throws Exception { if (args.length ! 1) { System.err.println(Usage: hadoop jar inspector.jar hdfs_path); System.exit(1); } Configuration conf new Configuration(); Job job Job.getInstance(conf); Path inputPath new Path(args[0]); ListInputSplit splits new TextInputFormat().getSplits(job); System.out.printf(File: %s, Total Splits: %d\n, args[0], splits.size()); for (int i 0; i splits.size(); i) { FileSplit split (FileSplit) splits.get(i); System.out.printf(Split[%d]: start%d, length%d, hosts%s, path%s\n, i, split.getStart(), split.getLength(), Arrays.toString(split.getLocations()), split.getPath() ); } } }编译打包后运行hadoop jar inspector.jar /input/input.txt # 输出示例 # File: /input/input.txt, Total Splits: 1 # Split[0]: start0, length32, hosts[localhost], pathhdfs://localhost:9000/input/input.txt若你上传一个 200MB 文件dd if/dev/zero of/tmp/bigfile bs1M count200 hadoop fs -put /tmp/bigfile /input/ hadoop jar inspector.jar /input/bigfile # 输出 # File: /input/bigfile, Total Splits: 2 # Split[0]: start0, length134217728, hosts[localhost], path... # Split[1]: start134217728, length69206016, hosts[localhost], path...这证明HDFS 默认 block size 128MB200MB 文件被切成 2 个InputSplit第一个正好 128MB第二个 69MB200-128完全对齐 block 边界。6.3 一个表格词频统计各环节关键参数与推荐值环节参数名作用推荐值说明Mapperwc.regex.filter定义有效字符范围[^\\u4e00-\\u9fa5a-zA-Z0-9\\s]中文英文数字空格过滤标点Combinerwc.debug.combiner开启 Combiner 调试日志true调试时生产关闭避免 I/O 压力Reducerwc.topk输出 Top-K 高频词1000避免输出过大内存可控Outputwc.compress.outputSequenceFile 是否压缩trueBLOCK 压缩比 RECORD 更高效Jobmapreduce.job.reducesReducer 数量min(2, available_cores)伪分布式建议 1~2避免资源争抢从那以后我每次搭伪分布式环境都强制走一遍jps→hadoop fs -ls /→yarn node -list三连检每次改core-site.xml必用hadoop fs -conf验证fs.defaultFS是否生效每次写Combiner第一行就是LOG.info(COMBINER START)。这些动作不费 30 秒却能省下 3 小时查日志。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站