MapReduce计算模型--简单层次Top-Down细化

Hadoop MapReduce 计算模型分析(一)

先简单说一下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

MapReduce计算模型--简单层次Top-Down细化_第1张图片

我们在之前讨论的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负责接受所有单词并统计词频。整个过程传递数据都是形式的,shuffle是按照key进行的。因此将map的输出设计成word作为key,1作为value(map的输入可以采用hadoop默认的输入:文件一行作为value,行号作为key)。Reduce输入采用map的输出在shuffle聚集的list>。Reduce输出就是key为word,value为词频。下面,Go—>:

假设输入数据为两个文件:

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>>

//这就是list>

对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官方文档等。

你可能感兴趣的:(Hadoop)