开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
MapReduce 是Hadoop 的计算框架,以一种可靠的,具有容错能力的方式并行地处理上TB级别的海量数据集。MapReduce 分为 Map 阶段和 Reduce阶段,其中 Map 阶段用于处理数据,Reduce 阶段用于聚合结果。
MapReduce框架组成
- JobTracker负责调度构成一个作业的所有任务,这些任务分布在不同的TaskTracker
- TaskTracker负责执行由JobTracker指派的任务
- 作业被分成多个任务,然后运行到集群中的多个数据节点。
- JobTracker的责任是协调活动调度任务来在不同的数据节点上运行。
- 单个任务的执行,然后由 TaskTracker 处理,它位于执行工作的一部分,在每个数据节点上。
- TaskTracker 的责任是发送进度报告到JobTracker。
- 此外,TaskTracker 周期性地发送“心跳”信号信息给 JobTracker 以便通知系统它的当前状态。
- 这样 JobTracker 就可以跟踪每项工作的总体进度。在任务失败的情况下,JobTracker 可以在不同的 TaskTracker 重新调度它
MapReduce输入输出
MapReduce框架运转在 <key,value> 键值对上,也就是说,框架把作业的输入看成是一组<key,value>键值对,同样也产生一组<key,value>键值对作为作业的输出。map函数和reduce函数的输入和输出都遵循<key,value>键值对的格式,简单的用符号表示就是:
Map:(K1,V1)——> list(K2,V2)
Reduce:(K2,list< V2>)——> list<K3,V3>
WordCount实例
下面通过实例来对MapReduce的过程进行说明,介绍map和reduce两个阶段需要进行哪些处理。WordCount是Hadoop自带的一个例子,单词计数主要完成的功能是:统计一系列文本文件中每个单词出现的次数,如图所示:
Map输入
MapReduce针对文本文件缺省使用LineRecordReader类来实现读取,一行一个key/value对。因此,对于给出的文件,假设每个文件正好是一个分片,那么会有四个Map任务,MapReduce会将其映射为如下所示的键值对作为Map过程的输入。
| map任务 | value |
|---|---|
| map1 | the weather is good |
| map2 | today is good |
| map3 | good weather is good |
| map4 | today has good weather |
Map输出
用户通过定义map函数对输入的键值对进行处理,目标是统计每个单词的个数,这相当于一个数据预处理的过程,经过处理后,会输出一系列的键值对,key是每个单词,而value是个数。
| map任务 | key | value |
|---|---|---|
| map1 | the | 1 |
| map1 | weather | 1 |
| map1 | is | 1 |
| map1 | good | 1 |
| map2 | today | 1 |
| map2 | is | 1 |
| map2 | good | 1 |
| map3 | good | 1 |
| map3 | weather | 1 |
| map3 | is | 1 |
| map3 | good | 1 |
| map4 | today | 1 |
| map4 | has | 1 |
| map4 | weather | 1 |
| map4 | good | 1 |
Reduce输入
Map会经历一个shuffle的过程,这个过程将key相同的数据进行合并,并按照字符顺序进行排序。
| key | value |
|---|---|
| good | [5] |
| the | [1] |
| is | [3] |
| has | [1] |
| toady | [2] |
| weather | [3] |
Reduce的输出
最后,Reducer实现将相同key的值合并起来,得到最后的结果。
在eclipse中实现WordCount
环境配置
安装eclipse www.eclipse.org/downloads/p…
创建启动器在/usr/share/applications目录新建文件
sudo vim /usr/share/applications/eclipse.desktop
添加如下内容
[Desktop Entry]
Encoding=UTF-8
Type=Application
Name=eclipse
Exec=/opt/module/eclipse/eclipse
GenericName=eclipse
Comment=Java development tools
Icon=/opt/module/eclipse/icon.xpm
Categories=Application;Development;
Terminal=false
安装hadoop-eclipse-plugins插件 gitcode.net/mirrors/win…
将hadoop-eclipse-kepler-plugin-2.6.0.jar放到eclipse解压文件夹的plugins文件夹下,重启eclipse,就能看到对应的Map/Reduce小象图标。
点击window-perference-Hadoop Map/Reduce,配置Hadoop 的安装路径
建立与hadoop的连接
在左侧DFS中就可以看到节点的文件路径
Eclipse运行Wordcount实例
创建mapreduce项目
创建WordCount类
在运行 MapReduce 程序前,还需要执行一项重要操作(也就是上面提到的通过复制配置文件解决参数设置问题):将 /usr/local/hadoop/etc/hadoop 中将有修改过的配置文件(如伪分布式需要 core-site.xml 和 hdfs-site.xml),以及 log4j.properties 复制到 WordCount 项目下的 src 文件夹(/eclipse-workspace/WordCount/src)中:
cp /opt/module/hadoop/etc/hadoop/core-site.xml wordcount/src
cp /opt/module/hadoop/etc/hadoop/hdfs-site.xml wordcount/src
cp /opt/module/hadoop/etc/hadoop/log4j.properties wordcount/src
创建输入文件
本地创建一个文本文件 touch test.txt
首先先在hdfs的根目录下创建一个input目录hdfs dfs -mkdir /input
然后上传到hdfs上 hdfs dfs -put ./test.txt /input
查看是否成功上传 hdfs dfs -ls /input
程序编写
在Hadoop 中, map 函数位于内置类org.apache.hadoop.mapreduce.Mapper<KEYIN,VALUEIN, KEYOUT, VALUEOUT>中,reduce 函数位于内置类org.apache.hadoop. mapreduce.Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>中。 我们要做的就是覆盖map 函数和reduce 函数。
重写Map函数
public void map(Object key, Text value,
Mapper<Object, Text, Text, IntWritable>.Context context)
throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while(itr.hasMoreTokens()) {
this.word.set(itr.nextToken());
context.write(this.word, one);
}
}
Mapper 类,有四个泛型,分别是KEYIN、VALUEIN、KEYOUT、VALUEOUT,前面两个KEYIN、VALUEIN 指的是map 函数输入的参数key、value 的类型;后面两个KEYOUT、VALUEOUT 指的是map 函数输出的key、value 的类型; 重写Reduce函数
public void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
IntWritable val;
for(Iterator i$ = values.iterator(); i$.hasNext(); sum += val.get()) {
val = (IntWritable)i$.next();
}
this.result.set(sum);
context.write(key, this.result);
}
}
Reducer 类,也有四个泛型,同理,分别指的是reduce 函数输入的key、value类型(这里输入的key、value类型通常和map的输出key、value类型保持一致)和输出的key、value 类型。 这里的reduce函数主要是将传入的<k2,v2>进行最后的合并统计,形成最后的统计结果。
主函数
public static void main(String[] args) throws Exception {
//配置类,设置输入输出路径
Configuration conf = new Configuration();
String[] otherArgs = (new GenericOptionsParser(conf, args)).getRemainingArgs();
if(otherArgs.length < 2) {
System.err.println("Usage: wordcount <in> [<in>...] <out>");
System.exit(2);
}
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
// 指定自定义的Mapper类
job.setMapperClass(WordCount.TokenizerMapper.class);
//
job.setCombinerClass(WordCount.IntSumReducer.class);
//指定自定义的Reducer类
job.setReducerClass(WordCount.IntSumReducer.class);
//指定map输出的<K,V>类型(如果<k3,v3>的类型与<k2,v2>的类型一致则可以省略)
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定输出目录
for(int i = 0; i < otherArgs.length - 1; ++i) {
FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
}
FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1]));
//提交任务
System.exit(job.waitForCompletion(true)?0:1);
}
在Main函数中,主要做了三件事:
- 指定输入、输出目录;
- 指定自定义的Mapper类和Reducer类;
- 提交作业。
运行程序
点击工具栏中的 Run 图标,或者右键点击 Project Explorer 中的 WordCount.java,选择 Run As -> Run on Hadoop,就可以运行 MapReduce 程序了。不过由于没有指定参数,运行时会提示 "Usage: wordcount ",需要通过Eclipse设定一下运行参数。右键点击刚创建的 WordCount.java,选择 Run As -> Run Configurations,在此处可以设置运行时的相关参数(如果 Java Application 下面没有 WordCount,那么需要先双击 Java Application)。切换到 "Arguments" 栏,在 Program arguments 处填写 "input output" 就可以了。
运行成功
文件保存到/user/hadoop01/output