Hadoop Map/Reduce说明
hadoop Map/Reduce是一个使用简易的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错的方式并行处理上T级别的数据集。
一个Map/Reduce作业经常讲数据集切分成独立的块,这些块通过map任务并行处理,框架对map的输出进行排序,排序结果会被reduce以输入参数进行接收。通常作业的输入和输出均存储在文件系统。框架会负责任务的调度和监控,同时也会对失败的任务重新执行。
通常,计算节点和存储节点是相同的。也就是说,Map/Reduce框架和hadoop分布式系统运行在相同的节点上。这种配置允许框架在已有数据的节点上调度任务,使集群中的网络带宽有效的利用。
Map/Reduce框架由一个单独的master节点上的JobTracker和对应的每个slave节点上的taskTracker组成,master负责对slave节点上的任务进行调度,监控他们,并且对失败的任务进行重新执行。slave负责执行master分配的任务。
应用最起码需要指明输入或者输出的路径,并且通过实现合适的接口或抽象类来提供map或reduce函数,再加上其他job执行的参数,这些合起来构成了作业的配置。接着hadoop的job client提交作业(jar或者其他可执行程序)和配置信息给jobTracker,此jobTracker负责对软件或配置分发给slave,调度和监控人物,提供状态和诊断信息给job-client。
尽管Hadoop框架是由Java实现的,Map/Reduce应用程序却并不需要用Java来编写。
- Hadoop Streaming是一个运行作业的工具集,它允许用户创建并运行所有可执行的程序(如shell)来作为mapper和reducer。
- Hadoop Pipes是一个与SWIG兼容的C++的Map/Reduce实现(没有给予JNDI)
输入与输出
<key, value>对,也就是说,框架把作业的输入看做是<key, value>键值对的集合,同样地作业的输出也是如此,唯一的区别是两个键值对的类型可能有所出入。
框架中的Key和Value类必须是Writable接口的实现;此外,为了方便排序,key类还需要实现WritableComparable接口。
一个Map/Reduce 作业的输入和输出类型如下所示:
<k1, v1>-> map-> <k2, v2>-> combine-> <k2, v2>-> reduce-> <k3, v3>
代码示例:WordCount v1.0
在深入的了解map/reduce之前,我们先了解下wordCount的示例(此示例在源码包里),以便了解map/reduce的运行过程。
wordcount是一个简单的用于统计输入文件中的单词个数的应用。
这个应用适用于单机模式、伪分布式和完全分布式三种应用模式。
wordcount代码如下:
package org.myorg;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.conf.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.*;
public class WordCount {
public static class Map extends MapReduceBase implements
Mapper<LongWritable, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value,
OutputCollector<Text, IntWritable> output, Reporter reporter)
throws IOException {
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
output.collect(word, one);
}
}
}
public static class Reduce extends MapReduceBase implements
Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterator<IntWritable> values,
OutputCollector<Text, IntWritable> output, Reporter reporter)
throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception {
JobConf conf = new JobConf(WordCount.class);
conf.setJobName("wordcount");
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setMapperClass(Map.class);
conf.setCombinerClass(Reduce.class);
conf.setReducerClass(Reduce.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);
FileInputFormat.setInputPaths(conf, new Path(args[0]));
FileOutputFormat.setOutputPath(conf, new Path(args[1]));
JobClient.runJob(conf);
}
}
用法
假设环境变量HADOOP_HOME对应安装时的根目录,HADOOP_VERSION对应Hadoop的当前安装版本,编译WordCount.java来创建jar包,可如下操作:
$ mkdir wordcount_classes
$ javac -classpath ${HADOOP_HOME}/hadoop-${HADOOP_VERSION}-core.jar -d wordcount_classes WordCount.java
$ jar -cvf /usr/joe/wordcount.jar -C wordcount_classes/ .
假设:
- /usr/joe/wordcount/input- 是HDFS中的输入路径
- /usr/joe/wordcount/output
用示例文本文件做为输入:
$ bin/hadoop dfs -ls /usr/joe/wordcount/input/
/usr/joe/wordcount/input/file01
/usr/joe/wordcount/input/file02
$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file01
Hello World Bye World
$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file02
Hello Hadoop Goodbye Hadoop
运行应用程序:
$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount /usr/joe/wordcount/input /usr/joe/wordcount/output
输出是:
$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000
Bye 1
Goodbye 1
Hadoop 2
Hello 2
World 2
应用程序能够使用-files选项来指定一个由逗号分隔的路径列表,这些路径是task的当前工作目录。使用选项-libjars可以向map和reduce的classpath中添加jar包。使用-archives选项程序可以传递档案文件做为参数,这些档案文件会被解压并且在task的当前工作目录下会创建一个指向解压生成的目录的符号链接(以压缩包的名字命名)。 有关命令行选项的更多细节请参考http://hadoop.apache.org/docs/r1.0.4/cn/commands_manual.html
使用-libjars和-files运行wordcount例子:
hadoop jar hadoop-examples.jar wordcount -files cachefile.txt -libjars mylib.jar input output
解释
WordCount应用程序非常直截了当。
mapper的相关实现在19-44行,此实现通过map方法(26-42行),通过指定的TextInputForamt处理每行的数据,然后对每行内容按照空格进行切分,之后,输出< <word>, 1>形式的键值对。
对于示例中的第一个输入,map输出是:
< Hello, 1>
< World, 1>
< Bye, 1>
< World, 1>
第二个输入,map输出是:
< Hello, 1>
< Hadoop, 1>
< Goodbye, 1>
< Hadoop, 1>
关于组成一个指定作业的map数目的确定,以及如何以更精细的方式去控制这些map,我们将在教程的后续部分学习到更多的内容。
WordCount还指定了一个combiner (46行)。因此,每次map运行之后,会对输出按照key进行排序,然后把输出传递给本地的combiner(按照作业的配置与Reducer一样),进行本地聚合。
第一个map的输出是:
< Bye, 1>
< Hello, 1>
< World, 2>
第二个map的输出是:
< Goodbye, 1>
< Hadoop, 2>
< Hello, 1>
Reducer(28-36行)中的reduce方法(29-35行) 仅是将每个key(本例中就是单词)出现的次数求和。
因此这个作业的输出就是:
< Bye, 1>
< Goodbye, 1>
< Hadoop, 2>
< Hello, 2>
< World, 2>
代码中的run方法中指定了作业的几个方面, 例如:通过命令行传递过来的输入/输出路径、key/value的类型、输入/输出的格式等等JobConf中的配置信息。随后程序调用了JobClient.runJob(55行)来提交作业并且监控它的执行。
我们将在本教程的后续部分学习更多的关于JobConf, JobClient, Tool和其他接口及类(class)。
后记
上周一直抽不开身来,本周开始继续搬运内容,因为前期了解不多,所以对官网的内容进行翻译的,希望大家不要介意,map/reduce对应的内容比较多,今天写的有点突兀,下篇会接着这个文章继续了解map/reduce