MapReduce教程
- 目的
- 环境条件
- 总览
- 输入输出
- Example: WordCount v1.0
- 源码
- 用法
- Walk-through
- MapReduce - User Interfaces
目的
本文全面描述了所有面向用户方面Hadoop mapReduce框架和服务作为一个教程。
确保Hadoop被安装、配合、并且运行。 更多:
- Single Node Setup 新用户单机节点。
- Cluster Setup 集群配置。
Hadoop MapReduce 是一个方便应用在集群(上千节点)同时并行处理大量数据(TB级别的数据源)并具有可靠、容灾性的软件框架。
一个MapReduce任务通常把输入数据分割成独立的数据块并有map tasks并行处理。框架分类输出maps,然后这些maps被传至reduce tasks。通常任务的输入和输出都存在一个文件系统中。框架负责计划、监控这些任务,并且重新执行失败的任务。
一般来说,运行和存储数据是在相同的节点上,换句话说,MapReduce框架和Hadoop分布式文件系统是运行在相同的节点上的。这样配置能有效解决因集群传输数据产生的带宽压力。
MapReduce框架包含单一主要的ResourceManager,一个NodeManager 在每个集群中,在每个应用中包含一个MRAppMaster 。
一个job的最低配置包含,输入输出路径,map和reduce类,以及其他别的一些配置。
Hadoop job客户端配置提交任务给ResourceManager ,ResourceManager 负责把配置和程序分发给各个节点,计划、监控任务,并且提供运行状态返回给客户端诊断信息。
虽然Hadoop框架是由JAVA实现的,但是MapReduce任务并不一定需要由JAVA编写。
- Hadoop Streaming is a utility which allows users to create and run jobs with any executables (e.g. shell utilities) as the mapper and/or the reducer.
- Hadoop Pipes is a SWIG-compatible C++ API to implement MapReduce applications (non JNI™ based).
MapReduce框架以<key,value>的形式处理输入和输出数据。
Key和Value必须又框架序列化。因此需要实现 Writable接口。此外key 类还必须实现WritableComparable 来促进框架分类。
Input and Output types of a MapReduce job:
(input) <k1, v1>-> map -><k2, v2> -> combine -> <k2, v2> -> reduce -><k3, v3> (output)
在深入认识之前,先通过一个简单的MapRecude例子来了解它们是如何通过的。
WordCount 程序通过给定的输入数据计算其中每个出现单词的频数。
适用于单机模式、伪分布式、完全分布式模式。 (Single Node Setup).
import java.io.IOException;
import java.util.StringTokenizer;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
importorg.apache.hadoop.mapreduce.lib.input.FileInputFormat;
importorg.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCount {
public staticclass TokenizerMapper
extendsMapper<Object, Text, Text, IntWritable>{
private finalstatic IntWritable one = new IntWritable(1);
private Textword = new Text();
public voidmap(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while(itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public staticclass IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable>{
privateIntWritable result = new IntWritable();
public voidreduce(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);
}
}
public staticvoid main(String[] args) throws Exception {
Configurationconf = new Configuration();
Job job =Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.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);
}
}
确保如下环境变量:
export JAVA_HOME=/usr/java/default
export PATH=${JAVA_HOME}/bin:${PATH}
export HADOOP_CLASSPATH=${JAVA_HOME}/lib/tools.jar
Compile WordCount.java and create a jar:
$ bin/hadoop com.sun.tools.javac.Main WordCount.java
$ jar cf wc.jar WordCount*.class
Assuming that:
/user/joe/wordcount/input - input directory in HDFS
/user/joe/wordcount/output - output directory in HDFS
Sample text-filesas input:
$ bin/hadoop fs -ls /user/joe/wordcount/input/ /user/joe/wordcount/input/file01/user/joe/wordcount/input/file02
$ bin/hadoop fs -cat /user/joe/wordcount/input/file01
Hello World Bye World
$ bin/hadoop fs -cat /user/joe/wordcount/input/file02
Hello Hadoop Goodbye Hadoop
Run theapplication:
$ bin/hadoop jar wc.jar WordCount/user/joe/wordcount/input /user/joe/wordcount/output
Output:
$ bin/hadoop fs -cat/user/joe/wordcount/output/part-r-00000`
Bye 1
Goodbye 1
Hadoop 2
Hello 2
World 2`
你可以通过 –files选项来使应用根据空格分割路径使其存储在当前路径下。
你还可以通过 –libjars选项来添加jars。
-archives选项能够允许根据空格分割来传递参数,使其作为变量。
更多细节可以参考Commands Guide.
Running wordcount example with -libjars, -files and -archives:
bin/hadoop jar hadoop-mapreduce-examples-<ver>.jarwordcount -files cachefile.txt -libjars mylib.jar -archives myarchive.zip inputoutput
这里的myarchive.zip需要放置并且解压缩。
用户可以为files和archives指定不同的名字,通过使用#符号。
For example,
bin/hadoop jar hadoop-mapreduce-examples-<ver>.jarwordcount -files dir1/dict.txt#dict1,dir2/dict.txt#dict2 -archivesmytar.tgz#tgzdir input output
The WordCount application is quitestraight-forward.
public void map(Object key, Text value, Context context
)throws IOException, InterruptedException {
StringTokenizeritr = new StringTokenizer(value.toString());
while(itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
Mapper 通过map方法来实现,每一行记录运行一次,格式是特殊指定的TextInputFormat,
靠空格来分割行,通过StringTokenizer类,并且传递键值对,比如< <word>, 1>.
给定的输入,经过第一个map处理,输出如下:
< Hello, 1>
< World, 1>
< Bye, 1>
< World, 1>
经过第二个map处理,输出如下:
< Hello, 1>
< Hadoop, 1>
< Goodbye, 1>
< Hadoop, 1>
我们将下接下来的教程中学习更多关于给定任务的map数量,并且如何控制这些map的数量。
job.setCombinerClass(IntSumReducer.class);
WordCount程序里面每个map都会进行根据key来聚合。
The output of the firstmap:
< Bye, 1>
< Hello, 1>
< World, 2>`
The output of thesecond map:
< Goodbye, 1>
< Hadoop, 2>
< Hello, 1>`
public void reduce(Text key, Iterable<IntWritable>values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritableval : values) {
sum +=val.get();
}
result.set(sum);
context.write(key, result);
}
The Reducer implementation, via the reduce method just sums up the values,which are the occurence counts for each key (i.e. words in this example).
Thus the output ofthe job is:
< Bye, 1>
< Goodbye, 1>
< Hadoop, 2>
< Hello, 2>
< World, 2>`
Main方法指定各种参数,比如输入,输出路径(通过命令),键值对类型,输入输出格式。在任务重,会调用job.waitForCompletion来确认任务和监控进程。
首先来看看Mapper和Reducer接口,应用通过实现他们来提供map和reduce方法。
然后再来讨论别的接口,暴扣 Job, Partitioner, InputFormat, OutputFormat, and others.
最后,再来讨论框架的一些有用的特性,包括DistributedCache, IsolationRunner等
Applicationstypically implement the Mapper and Reducer interfaces to provide the map and reduce methods. These form the core of thejob.
Mapper 使输入的键值对变成中间(处理过的)键值对。
Maps使用单独的Tasks来转化输入的记录变成中间(临时)记录,转化并不需要和输入记录一个格式,而且1个键值对可以被转化成0个或多个键值对。
Hadoop MapReduce框架通过InputSplit 来分每个map,并通过InputFormat 来生成。
总得来说,Mapper通过Job.setMapperClass(Class) 方法来实现任务。框架会为task 在InputSplit 调用map(WritableComparable, Writable,Context) 来处理每个键值对。应用重写cleanup方法去执行任何需要的cleanup。
输出的键值对也不需要和输入的键值对相同,可以是0个也可以是多个。通过调用context.write(WritableComparable, Writable)来搜集。
应用可以使用计数器来统计。
所有中间值都被框架分组,并经过Reduces,决定最后的输出,用户可以通过设置Job.setGroupingComparatorClass(Class).来控制分组Comparator 。
the Mapper的输出被分组并被分配给每个Reducer。分组的数量和reduce tasks数量一致。用户可以通过实现Partitioner来控制那些key去到哪些Reducer.
用户通过设置 Job.setCombinerClass(Class)来帮助切割从Mapper to the Reducer.传输的数据大小
中间结果通常是简答的(key-len, key, value-len, value)格式,程序可以通过 CompressionCodec设置控制这些中间的数据是否以及以什么方式压缩
通常来说,maps的数量由输入文件的大小所决定,或者说是输入文件的块的数量。
推荐的并行maps的数量每节点大概在10-100个,在低cpu占用的可以设置到300个。Task的设置需要一些时间,所以最好最少花费1分钟的时间去执行。
所以,如果你有10TB的数据,然后设置的数据块的大小是128MB,那么你将会有82000个maps。除非你自己通过Configuration.set(MRJobConfig.NUM_MAPS, int)来设置maps到更多得数量。
Reducer
Reducer通过公共key来减少中间数据的量。
可以通过Job.setNumReduceTasks(int)来设置任务的数量。
Overall, Reducer implementations are passed the Job for the job via the Job.setReducerClass(Class) method and canoverride it to initialize themselves. The framework then calls reduce(WritableComparable, Iterable<Writable>,Context) method for each <key, (list of values)> pair in thegrouped inputs. Applications can then override the cleanup(Context) method toperform any required cleanup.
Reducer 有3个主要的阶段,shuffle, sort and reduce.
Reducer 的输入,是经过分类的mappers的输出。在这个阶段,框架通过Http取得所有mappers输出的相关分区。
在这个阶段,框架分组通过key来分来所有的输入(因此不同的mapper可能会输出相同的key)。
Shuffle和sort阶段是一起发生的。所以map-outputs得到的是他们融合后的结果。
在reduction之前的分组规则,如果是有必须和中间数据的分组规则不一样的话,那么一种可以通过
Job.setSortComparatorClass(Class).来指定以个Comparator ,Job.setGroupingComparatorClass(Class)可以用来指定如何控制中间数据的分割。
这些可以在用在模拟第二次排序。
Reduce
在这个阶段,reduce(WritableComparable, Iterable<Writable>, Context)方法被每个 <key, (list ofvalues)> 调用。
reduce task输出通过Context.write(WritableComparable,Writable)被写进 FileSystem
应用程序可以使用计数器来统计。
Reducer 的输出没有被排序。
正确的reduces的数量似乎介于0.95或者1.75乘以(节点的数量,每个节点最大容器的数量)
0.95的话,所有的reduces能在map完成的时候立即运行并开始转换map的输出。1.75的话,不仅完成第一轮的reduces,并且会运行第二轮的reduces来确保job的负载。
当Reduce任务的数量是任务槽0.95倍的时候,如何一个Reduce任务失败,Hadoop可以很快地找到一台空闲的机器重新执行这个任务。当Reduce任务的数量是任务槽的1.75倍时,执行速度快的机器可以获得更多的Reduce任务,因此使负载更加均衡,以提高任务的处理速度。
增加reduces的数量会额外增加框架的开销,但是增加负载会减少故障率。
The scaling factors above are slightly less than whole numbers to reservea few reduce slots in the framework for speculative-tasks and failed tasks.
如果没有得到reduction,那么设置reduce-tasks成0是合法的。
在这种情况下,map-tasks的输出,直接到文件系统,通过FileOutputFormat.setOutputPath(Job, Path).设置输出路径。在把他们写入文件系统之前,框架不对map-outputs进行排序。
Partitioner partitions the key space.
Partitioner controls the partitioning of the keys of the intermediatemap-outputs. The key (or a subset of the key) is used to derive the partition,typically by a hash function. The total number of partitions is thesame as the number of reduce tasks for the job. Hence this controls which ofthe m reduce tasks the intermediate key (and hence the record) is sent tofor reduction.
HashPartitioner is the default Partitioner.
Counter is a facility for MapReduceapplications to report its statistics.
Mapper and Reducer implementationscan use the Counter to report statistics.
Hadoop MapReduce comes bundled with a library of generally useful mappers,reducers, and partitioners.