从零跑通MapReduce WordCount:Hadoop集群提交完整指南

发布时间:2026/10/17 17:22:38
从零跑通MapReduce WordCount:Hadoop集群提交完整指南 先说说我为什么写这篇。前阵子带几个零基础的同学搭Hadoop环境发现大家普遍卡在一个很尴尬的位置MapReduce的逻辑能听懂WordCount的代码能看懂但是一旦要求“把代码打包传到Linux服务器上跑起来”问题就成堆地冒出来——有人不知道jar包怎么打有人不知道命令里该写主类还是写jar名有人把本地文件路径当成HDFS路径还有人一运行就报ClassNotFoundException。这篇就把MapReduce基础编程里最经典的一个例子从写代码、打jar包、上传服务器、启动集群、提交任务到看结果完整讲一遍顺便把那些常规文档不会告诉你的坑都标出来。1. 先搞清楚WordCount到底在演示什么1.1 为什么大数据入门都用WordCount开刀WordCount的意思是“单词计数”给一堆文本统计每个单词出现了多少次。这个需求本身非常简单任何一个会写循环的程序员都能用十几行Java或Python实现。但它在Hadoop生态里意义不一样。MapReduce把“统计单词频次”拆成了两个阶段Map负责把每一行文本拆成一个个单词并给每个单词标上一个“1”作为计数Reduce负责把所有相同单词的“1”收集起来做加法。你会发现这个例子里包含了MapReduce的全部核心概念输入分片、Map阶段、Shuffle与排序、Reduce阶段、输出落盘。真正把代码跑通等于亲手验证了一遍分布式计算里“数据在哪里算”的基本模型。这也是我希望你认真对待这个例子的原因。它不是一道闭卷题而是一块敲门砖。搞清楚WordCount的完整执行链路后续再去看Hive的编译原理、Spark的DAG调度都会有代入感。1.2 用三行文本推演一遍数据流转拿一个最简单的输入来说明假设HDFS上有个文件内容是hello world hadoop hello mapreduce hadoop hadoop mapreduceMap阶段会把每一行作为输入按空格拆词。第一行产出三条中间结果(hello, 1) (world, 1) (hadoop, 1)第二行、第三行同理。但这些中间结果不会直接发给Reduce框架会先做一次Shuffle把key相同的记录汇聚到一起并且按key排好序。于是Reduce收到的数据大致是hello - [1, 1] world - [1] hadoop - [1, 1, 1] mapreduce - [1, 1]Reduce拿到迭代器之后把value累加写出的最终结果就是hello 2 world 1 hadoop 3 mapreduce 2很多人第一次看这里会问Shuffle是代码写出来的吗不是。它是MapReduce框架自带的机制你在代码里只需要声明Mapper和Reducer框架负责在中间完成分区、排序、合并、归并这些脏活累活。理解这一点你才能明白为什么写MapReduce比写多线程并发要省心得多。1.3 代码里真正需要你写的只有三个部分一个完整的MapReduce作业在代码层面至少要包含三样继承Mapper的类、继承Reducer的类、以及用来组装作业的Driver也就是main方法所在的地方。Mapper决定“输入的一行被拆成什么中间结果”Reducer决定“相同key的中间结果如何聚合”Driver决定“输入路径在哪、输出路径在哪、用哪个Mapper和Reducer、用哪种输入输出格式”。三条线各司其职没有魔法也没有复杂的线程同步。这是MapReduce基础编程最重要的思维方式你要定义的是数据的变换规则而不是数据怎么并行。并行度由输入分片数量决定Reduce数量由你配置或框架推算这些在跑真正的集群作业前都不需要你去手动管理线程。2. 动手前把环境盘点好版本匹配是第一道门槛2.1 JDK、Hadoop、Maven该怎么选我见过太多人跑不起来不是代码问题而是环境版本互相打架。这里直接给出我验证过比较稳的组合。组件建议版本说明JDK1.8Hadoop对JDK17的兼容性在部分发行版仍有坑入门阶段用1.8最稳Hadoop2.10.x经典稳定版本教程最多遇到问题最容易搜到答案Maven3.6用于打包Java代码3.6以上都行Linux服务器CentOS 7 / Ubuntu 20.04均可关键是hostname和SSH要通如果你是拿一个已经装好的Hadoop环境来练手建议先执行hadoop version看一眼版本号再决定Maven依赖里写哪个版本。Hadoop 3.x也完全可以跑WordCount只是个别日志格式和配置项名字不同逻辑完全一致。我的经验是如果服务器上是Hadoop 2.x本地打包依赖就别用3.x的hadoop-client否则提交作业时大概率出现依赖冲突或者序列化异常。2.2 Linux服务器上要准备什么先说结论至少要能启动HDFS和YARN也就是start-dfs.sh和start-yarn.sh之后jps能看到五个进程NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager。拿到一台新服务器很多人上来就敲hadoop jar结果报Connection refused。原因很简单集群根本没启动。所以准备阶段第一件事是确认进程第二件事是检查HDFS根目录能不能用。在服务器上创建我们自己的工作目录mkdir -p /home/hadoop-test然后在HDFS上准备输入目录。这里必须先给你打个预防针HDFS路径和Linux本地路径完全是两码事/input这个东西在Linux根目录下根本不存在它是HDFS命名空间里的路径。hdfs dfs -mkdir -p /user/hadoop-test/input如果当前执行命令的Linux用户和HDFS超级用户不一致上面这步可能Permission denied可以先用超级用户授权或者直接切到启动Hadoop的那个用户去操作。我在实验环境里习惯把所有练习统一放在/user/hadoop-test/下面分类清楚后面清理也方便。3. 写一份真正完整的WordCount代码3.1 Mapper把一行文本拆成单词并打上标记直接看代码我建议把三个类写在一个Java文件里就是WordCountDriver.java文件里面包含静态内部类形式的Mapper和Reducer。这样讲课、打包、分发都方便。package com.example; import java.io.IOException; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; public class WordCountDriver { public static class TokenizerMapper extends MapperObject, Text, Text, IntWritable { private final static IntWritable one new IntWritable(1); private Text word new Text(); Override public void map(Object key, Text value, Context context) throws IOException, InterruptedException { String line value.toString(); String[] words line.split(\\s); for (String w : words) { if (w.length() 0) { continue; } word.set(w); context.write(word, one); } } } // 后续的Reducer和main继续写在下面 }注意这里的关键细节第一个泛型参数Object是输入key默认是行偏移量第二个Text是输入value即一行文本第三个Text是输出key即单词第四个IntWritable是输出value即计数1。很多人刚学Hadoop会问为什么不用Java自带的String和Integer因为Hadoop为分布式序列化设计了专用类型Text对应StringIntWritable对应Integer它们在网络传输和磁盘落盘时更高效。代码里用value.toString()拿到原始字符串用word.set(w)复用对象而不是频繁new这是一种性能习惯虽然在小数据量下感知不到但在海量输入下能减少GC压力。3.2 Reducer把同一个单词的计数累加Reducer的输入key就是单词value就是一堆1只需要遍历累加。但这里有个新手很容易忽略的细节IterableIntWritable里的对象是框架复用的不能直接存进List等循环结束后再用必须立刻处理或者深拷贝。累加这种操作正好适合边遍历边处理不涉及这个坑。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); } }这段逻辑没多少好讲的累加就完事了。但它的输入数据是有讲究的不同Map任务产生的相同单词会被Shuffle阶段自动汇聚到同一个Reduce任务里。如果没有Shuffle机制你得自己写全局HashMap或者引入分布式锁复杂度完全不是一个量级。3.3 Driver告诉框架“作业怎么跑”Driver是整个作业的组装车间。它要做的事包括设置作业名、指定jar入口类、指定Mapper和Reducer类、指定输出key/value类型、设置输入输出路径。public static void main(String[] args) throws Exception { if (args.length ! 2) { System.err.println(Usage: wordcount inputPath outputPath); System.exit(-1); } org.apache.hadoop.conf.Configuration conf new org.apache.hadoop.conf.Configuration(); org.apache.hadoop.mapreduce.Job job org.apache.hadoop.mapreduce.Job.getInstance(conf, word count); job.setJarByClass(WordCountDriver.class); job.setMapperClass(TokenizerMapper.class); job.setReducerClass(IntSumReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); org.apache.hadoop.fs.Path inputPath new org.apache.hadoop.fs.Path(args[0]); org.apache.hadoop.fs.Path outputPath new org.apache.hadoop.fs.Path(args[1]); org.apache.hadoop.mapreduce.lib.input.FileInputFormat.addInputPath(job, inputPath); org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.setOutputPath(job, outputPath); System.exit(job.waitForCompletion(true) ? 0 : 1); } }这里有几个务实建议。job.setJarByClass(WordCountDriver.class)非常重要。它让框架知道去哪个jar包里找类。如果你不设置本地模式跑无所谓但丢到集群上YARN会把jar分发到NodeManager上去执行不写这个字段很可能报ClassNotFoundException。job.setOutputKeyClass和job.setOutputValueClass设置的是Reduce输出的类型。由于Map和Reduce的输出类型一致框架可以推断Map输出类型通常不用再显式setMapOutputKeyClass。但如果你后续写Map和Reduce输出类型不一致的作业一定要分别设置Map输出类型和Reduce输出类型。waitForCompletion(true)里的true表示打印进度信息。跑小作业时你能看到map和reduce的进度百分比这也是排错的关键来源。我给这段代码做一下运行主类的说明打包时MANIFEST里的Main-Class会指向com.example.WordCountDriver所以运行时用hadoop jar wordcount-1.0.jar com.example.WordCountDriver /input /output既可以看到类名也可以在jar包MANIFEST缺失时兜底。4. 用Maven打包别把Hadoop依赖全塞进jar里4.1 pom.xml怎么配新建一个Maven工程把pom.xml写成下面这样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 hadoop.version2.10.2/hadoop.version /properties dependencies dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version${hadoop.version}/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId configuration archive manifest mainClasscom.example.WordCountDriver/mainClass /manifest /archive /configuration /plugin /plugins /build /project这里有个很多人踩过的坑看到网上教程用maven-shade-plugin把依赖打成一个“fat jar”也跟着配。结果提交到集群上报了一堆版本冲突或者找不到类。原因很简单Hadoop运行环境本身已经带了自己那套依赖你再塞一份进jar里相当于让框架加载两份不同的版本不冲突才怪。我的建议是项目依赖只在编译期需要运行期YARN会从Hadoop安装目录里拿。所以就用普通的maven-jar-plugin只打自己写的代码干干净净。这也是最接近企业开发的做法。4.2 执行打包并且验证jar内容在项目根目录执行mvn clean package -DskipTests-DskipTests跳过单元测试免得因为环境问题卡在测试阶段。打包完成后target目录下会出现wordcount-1.0.jar。此时别急着上传先看两眼jar包里有没有我们的主类jar tf target/wordcount-1.0.jar | grep WordCount能看到com/example/WordCountDriver.class就说明类打进去了。再确认MANIFESTunzip -p target/wordcount-1.0.jar META-INF/MANIFEST.MF能看到Main-Class: com.example.WordCountDriver这一行。这两步检查花不了十秒钟但能帮你区分“打包失败”和“提交失败”两类完全不同的问题划清边界。5. 上传到Linux服务器把WordCount真正跑起来5.1 用scp上传jar包和输入数据本地执行上传命令scp target/wordcount-1.0.jar 用户服务器IP:/home/hadoop-test/如果提示Permission denied检查Linux用户的目录写权限或者先传到/tmp再mv到工作目录。我第一次操作时就踩过这个/home/hadoop-test属于另一个用户自己根本没写权限当时传了半天还以为网络有问题。输入数据也要准备好本地先建一个input.txt内容随意比如hello world hadoop hello mapreduce hadoop hadoop mapreduce同步传到服务器再放进HDFSscp input.txt 用户服务器IP:/home/hadoop-test/ # 下面在服务器上执行 hdfs dfs -put /home/hadoop-test/input.txt /user/hadoop-test/input/验证一下文件是否真的进去了hdfs dfs -ls /user/hadoop-test/input/这里多说一句hdfs dfs -put是上传到HDFS分布式文件系统不是Linux本地。如果你直接写/home/hadoop-test/input.txt作为MapReduce的输入路径框架会在HDFS里找这个路径通常会报Input path does not exist。5.2 确认集群状态然后提交作业在服务器上先看一眼进程jps如果NameNode、DataNode、ResourceManager、NodeManager都在就可以提交了。cd /home/hadoop-test hadoop jar wordcount-1.0.jar com.example.WordCountDriver \ /user/hadoop-test/input \ /user/hadoop-test/output_wordcount注意输出目录这个参数MapReduce要求输出目录事先不存在这是框架的自我保护机制防止你把上一次的结果覆盖掉。所以第二次跑之前要先删掉上一次的输出hdfs dfs -rm -r /user/hadoop-test/output_wordcount如果你懒得每次手动删也可以写个小脚本运行前先判断并删除输出目录。这个脚本我后面给出示例。作业提交后终端会打出一堆日志。以map 100% reduce 100%结束旁边还会有耗时、计数器等统计信息。如果看到Job complete或者Successfully字样说明作业跑通了。5.3 查看结果和任务日志输出目录下通常有两个关键条目_SUCCESS空文件和part-r-00000结果文件。查看结果用hdfs dfs -cat /user/hadoop-test/output_wordcount/part-r-00000如果输入文本是之前那三行你会看到类似输出hadoop 3 hello 2 mapreduce 2 world 1这里的顺序不一定和输入顺序一致因为Reduce输出会按key排序这是Hadoop默认行为。我见过有人拼命想让它保持输入顺序但这与MapReduce的设计思想是相悖的。如果你对顺序有强需求要么在Reduce之后再做一次全局排序要么换个计算框架比如Spark。要看Map和Reduce过程中输出了什么细节日志可以用yarn logs -applicationId application_xxxx这个命令会把作业的Container日志拉出来如果代码里写了System.out.println输出会显示在这里面。本地跑小作业时也可以直接去$HADOOP_HOME/logs/userlogs/目录里翻按applicationId找文件夹。6. 最常见的5个坑和排查清单6.1 第一次跑WordCount的报错速查我把带新人时反复出现的报错整理成一张表照着查比自己瞎猜快得多。报错关键词常见原因解决动作Input path does not exist输入路径没写对或者文件没传到HDFS先hdfs dfs -ls /user/hadoop-test/input确认Output directory already exists输出路径被上次的数据占用了hdfs dfs -rm -r /user/hadoop-test/output_wordcountClassNotFoundException主类名拼错或jar里没打进去jar tf检查命令里写包名类名全称Connection refusedNameNode或ResourceManager没启动jps看进程缺哪个启动哪个Permission denied当前用户对HDFS目录无权限切换到HDFS超级用户或hdfs dfs -chmod -R 777 /user/hadoop-test6.2 你以为卡住了其实没卡住有个特别容易误判的情况作业跑完后终端显示map 100% reduce 0%持续很久没有变化。新手一看就觉得死机了直接CtrlC。其实在shuffle阶段Map端还在把中间结果拉给Reduce端日志上reduce进度没动很正常。尤其是小数据集整个shuffle可能就几秒钟你多等一会儿就能看到reduce开始动。真正卡死的情况通常是某个NodeManager挂了或者内存不够导致Container反复被kill。这时候去看ResourceManager页面端口默认8088看application的状态比看终端日志更直观。6.3 把重复命令封装成脚本跑一次WordCount要敲好几条命令练习多了就烦。我会在服务器上放一个run_wordcount.sh#!/bin/bash INPUT/user/hadoop-test/input OUTPUT/user/hadoop-test/output_wordcount hdfs dfs -rm -r $OUTPUT hadoop jar /home/hadoop-test/wordcount-1.0.jar \ com.example.WordCountDriver \ $INPUT \ $OUTPUT hdfs dfs -cat $OUTPUT/part-r-00000保存后chmod x run_wordcount.sh以后执行./run_wordcount.sh一条龙完成删旧输出、提交作业、打印结果。这个脚本不是必须的但你会在后面练习其他MapReduce作业时体会到它的价值——把注意力放在业务逻辑上而不是重复敲命令上。6.4 本地快速验证的一个实用小技巧如果你只是想验证代码逻辑对不对不想开整套Hadoop集群可以临时把输入输出路径改成Linux本地路径hadoop jar wordcount-1.0.jar com.example.WordCountDriver \ file:///home/hadoop-test/input_local \ file:///home/hadoop-test/output_localMapReduce在本地模式下会直接在本机文件系统上跑不经过HDFS速度极快。这个技巧特别适合本地开发试错等逻辑没问题了再上传到服务器跑分布式。不过要注意本地模式用的是本地的classpath你打包出来的jar里有没有Hadoop依赖都不影响因为它读取的是HADOOP_HOME自带的那一套环境。我自己带练习时也会让人先用本地模式跑一遍再上集群能过滤掉一大半低级错误。等集群上跑通了再回过头来看HDFS路径和权限体系理解会扎实很多。最后说一个我自己的习惯跑通WordCount之后别急着往下学新框架可以试着改动这个小作业比如统计每个文件里出现次数最多的前3个单词或者统计字符总数。改一次Mapper和Reducer你对MapReduce基础编程的理解就会深一层。毕竟技术文档到处都有但“亲手把一份代码从本地跑到分布式集群”的完整链路才是别人拿不走的东西。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询