先简单说一下MapReduce计算模型:
首先这是一个分布式对大数据处理的计算模型。在多个节点上并行处理大数据。在阅读时,你要将自己的思路不断地进行单节点与全局之间的转换。
下面由简到繁,一步步细化MR框架
以上就是MR的整个计算模型。输入数据切分成第一次的(K1, V1)的key-value对。经过Map函数处理,输出intermidiate(中间值)(K2, V2)的key-value形式的对。Reduce将中间值作为输入,reduce运行后处理成(K3, V3)的key-value形式的对。
根据这个全局形式的简单模型,我们可以看到,hadoop处理的核心函数就是Map和Reduce两个函数。不过我要说的是,这并不是核心。因为map和reduce都是常规的Java程序的写法--对输入的数据进行处理就行了(按行处理,程序会自动对在文件中的每一行进行循环,不用你管)。
Input: Hadoop将输入数据切分为若干个输入分片--InputSplit。每个Split对应一个Map Task。(所以每个Split的数量会与Map Task相同)
输入数据经过map处理,映射成一个新的key-value对儿。然后程序(*)根据Reduce数,将结果分成若干个分片,此分片为partition(写到本地磁盘)。
Reduce Task从每个Map Task上都取属于自己的partition。然后使用merge and sort方法将key值相同的聚集在一起,然后调用Reduce函数合并key-value。最后产生output。
由上面的这一个简单的过程可以看到,Hadoop一共分为5个组件:
(1) InputFormat
(2) Mapper
(3) Partitioner(在后面我们并不这么叫。这里姑且这么叫)
(4) Reducer
(5) OutputFormat
Hadoop的核心就是MapReduce计算模型。但是对于我们写程序处理数据,其实Mapper和Reducer往往是比较简单的,棘手的地方在于Partition。这也是最关键的地方。因为Hadoop出现有一点:提高计算效率。Partition写不好是很影响计算效率的,而Partition往往也是优化整个程序的着眼点。所以要写好Hadoop程序,就一定要明白这里。
为了满足上述模型,除了Map()和Reduce()函数外,需要:
(1)指定输入文件格式:I. 将输入数据切分为Split;II. 将每个Split解析成一个Map()需要的key-value对儿--InputFormat。
(2)确定Map()函数产生的key-value对儿应该为哪个Reduce Task的输入--Partitioner。
(3)指定输出文件格式--OutputFormat。
(*)本文最后会在一个程序中讨论一下三次结果的key-value的不同与对应。
一: 数据预处理
(1)MR擅长处理少量大数据,而在处理大量小数据时,MR性能会很低。所以在这里,数据预处理就是--合并。----对数据本身的处理。
(2)设置map输入数据大小来调节map的能力。因为往往一个block作为一个InputSplit(将大文件分割为block后)
(3)数据过滤,可利用数据挖掘中的常用手段处理,但是这里要结合hadoop最好,不然全盘对数据处理,还用hadoop干嘛?
二:中间shuffle
我们在之前讨论的partitioner其实是shuffle中的一个部分。Map的输出会经过一个名为Shuffle的过程交给Reduce处理(当然也有Map结果经过sort-merge交给Reduce处理的)。
从Map到Reduce,往往需要一定的处理才能将key-value交给reduce。原因有很多,比如reduce的任务数少于map。再比如会考虑网络状态和本地存储状态等。
总之,其实MapReduce的核心就是Shuffle。一个好的shuffle会极大地提高计算效率。也就是说shuffle过程的性能与整个MapReduce的性能直接相关。
总体来说,shuffle过程包含在Map和Reduce两端中。在Map端的shuffle过程是对Map的结果进行划分(partitioner)、排序(sort)和分割(split),然后将属于同一个划分的输出合并在一起(merge)并写在磁盘上,同时按照不同的划分将结果发送给对应的Reduce(Map和Reduce对应关系由JobTracker指定)。Reduce端又会将各个Map送来的属于同一个划分(往往是同一个Key或者相近的Key)进行合并(Merge)(这个过程会在最后的例子中看到),然后对merge结果进行排序,最后再交给Reduce处理。
三:数据中的比较器(排序依据)
这是一个很实用的扩展。原本只是一个小步骤而已,但是比较器的书写决定了key-value的样子。Hadoop中对于数据的处理依据就是“比较”。而比较也是一个不大不小的问题。
(*本来是打算在这里讲一下这个很重要的地方,但是因为本文会侧重计算模型的分析,所以会在之后的文章中重新阐述,预计是在下一篇关于MapReduce中提及,下一篇是关于Hadoop中的config和数据类型的,不过在这之前会是与Python和ant+xml相关的几篇博客)。
例子:WordCount程序
(WordCount是Hadoop的“Hello World”。程序很简单,不过这里我会侧重于我在上面提到的几点,综合地讨论一些问题。)
WordCount程序是用MapReduce来统计一个集合的输入文档的单词的词频。代码包括三个部分:Mapper,Reducer和main函数。
根据mapreduce的并行程序设计设计原则,方案中的内容切分步骤和数据不相关,可以并行处理,每个获得原始数据的机器只要将输入数据切分成单词就可以了。这可以交给map端。然后在reduce端统计合并相同单词的词频。而中间要通过shuffle完成一些处理才能将map输出交给reduce输入。
所以呢,map阶段完成对输入数据的单词切分,shuffle完成相同单词的聚集和分发(始终记住,map和reduce的task数量不一定是相同的),聚集和分发这个过程是MapReduce默认过程,不需具体配置,reduce负责接受所有单词并统计词频。整个过程传递数据都是
假设输入数据为两个文件:
File1:
Hello world
File2:
Hello hadoop
Hello mapreduce
次数 |
来源 |
形式 |
说明 |
第一次 |
输入数据 |
<0,”hello world”> |
每次读入一行,key为行首在文件中的偏移量,value为行字符串的内容。注意,File2的第二行会变成:<14, “hello mapreduce”> |
第二次开始部分 |
Map Task的输出 |
<”hello”,1>//在File2中 |
每个map task会输出它处理的每个单词和其频次。 |
第二次中间部分 |
Combiner的处理后(map端的shuffle开始) |
文件二会由: <”hello”,1> <”hello”,1>变成: <”hello”,2> |
Combiner先将结果局部合并,这样可以降低网络压力,提高效率。 |
第二次结尾部分 |
Shuffle输出(reduce端输入) |
<”hello”,<1,2>> //这就是 |
对map的输出进行排序合并,依据reduce的数量进行分割,在reduce端将不同的map task相同的key值数据变成这种形式,交给reduce。 |
第三次 |
Reduce端输出 |
<”hello”,3> |
合并redece输入中具有相同的key值的数据就可以。 可以看出,combiner和reduce可以是一样的函数。 |
(*)说明:Mapper继承自org.apache.hadoop.mapreduce.Mapper接口。当Hadoop运行时,它会接收来自于本地输入文件的每一行作为Mapper的输入。Map函数用空格字符作为分割将每一行分割(大多数情况下,回车键或换行符作为输入文件的行分隔符并不能满足我们的需求,通常用户很有可能会输入回车键、换行符,所以通常我们会定义不可见字符(即用户无法输入的字符)为行分隔符,这种情况下,就需要新写一个InputFormat)。
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.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
public class WordCount {
public static class Map extends Mapper {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
context.write(word, one);
}
}
}
public static class Reduce extends Reducer {
public void reduce(Text key, Iterable values, Context context)
throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
context.write(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = new Job(conf, "wordcount");
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setMapperClass(Map.class);
job.setReducerClass(Reduce.class);
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
最后,再看一下:
欲运行上面实现的Mapper和Reduce,则需要生成一个Map-Reduce得任务(Job),其基本包括以下三部分:
(1) 输入的数据,也即需要处理的数据
(2) Map-Reduce程序,也即上面实现的Mapper和Reducer
(3) 此任务的配置项JobConf
欲配置JobConf,需要大致了解Hadoop运行job的基本原理:
(1) Hadoop将Job分成task进行处理,共两种task:map task和reduce task
(2) Hadoop有两类的节点控制job的运行:JobTracker和TaskTracker
(3) JobTracker协调整个job的运行,将task分配到不同的TaskTracker上
(4) TaskTracker负责运行task,并将结果返回给JobTracker
(5) Hadoop将输入数据分成固定大小的块,我们称之input split
(6) Hadoop为每一个input split创建一个task,在此task中依次处理此split中的一个个记录(record)
(7) Hadoop会尽量让输入数据块所在的DataNode和task所执行的DataNode(每个DataNode上都有一个TaskTracker)为同一个,可以提高运行效率,所以input split的大小也一般是HDFS的block的大小。
(8) Reduce task的输入一般为Map Task的输出,Reduce Task的输出为整个job的输出,保存在HDFS上。
在reduce中,相同key的所有的记录一定会到同一个TaskTracker上面运行,然而不同的key可以在不同的TaskTracker上面运行,我们称之为partition
(9) partition的规则为:(K2, V2) –> Integer, 也即根据K2,生成一个partition的id,具有相同id的K2则进入同一个partition,被同一个TaskTracker上被同一个Reducer进行处理。
这篇文章只讲计算模型。更多关于系统的支撑问题、代码结合等请期待以后的博文。
参考:《hadoop实战》
《hadoop权威指南(第三版)》
《Packtpub.Hadoop.MapReduce.Cookbook.Jan.2013》
以及相关网络资料,如wiki,hadoop官方文档等。