大数据开发实战:MapReduce内部原理实践

    下面结合具体的例子详述MapReduce的工作原理和过程。

    以统计一个大文件中各个单词的出现次数为例来讲述,假设本文用到输入文件有以下两个:

    文件1:

      big data

      offline data

      online data

      offline online data

    文件2

      hello data

      hello online

      hello offline

    目标是统计这两个文件中各个单词的出现次数,很容易用肉眼算出各个词出现的次数:

    big:1

    data:5

    offline:3

    online:3

    hello:3

    但是想象一下,如果是数以百万级的文献资料,每个文献资料数以十万字或百万字计,还能用肉眼算吗?而这正是Hadoop擅长的,对应Hadoop来说只需要定义简单的Map逻辑和Reduce逻辑,然后把输入文件和处理逻辑提交

    给Hadoop即可,Hadoop将会自动完成所有的分布式计算任务。

   1、MapReduce逻辑开发

      Hadoop开发人员需要定义Map逻辑和Reduce逻辑,下面用伪代码来描述词频统计具体的Map逻辑和Reduce逻辑。

      词频统计任务的Map逻辑为:

      

      以上述实例文件1为例,上述Map逻辑执行后,将会输出:

      big:1

      data:1

      offline:1

      data:1

      online:1

      data:1

      offline:1

      online:1

      data:1

      Hadoop的shuffle过程会把Map任务的输出组织成<word,{1,1,1,1....}形式的数据并输入给Reduce任务,然后Reduce任务会对这种形式的数据执行Reduce逻辑,相应的Reduce逻辑为:

      

      至此,所有Map代码和Reduce代码都完成了,将此代码打包并提及给Hadoop执行即可。

     

   2、MapReduce任务提交详解

    从大数据开发实战:HDFS和MapReduce优缺点分析的MapReduce架构可以看出,MapReduce作业执行主要由JobTrackerTaskTracker负责完成。

      客户端编写好的MapReduce程序并配置好的MapReduce作业是一个Job,Job被提交给JobTracker后,JobTracker会给该Job一个新的ID值,接着检查该Job指定的输出目录是否存在、输入文件是否存在,

    如果不存在,则抛出错误。同时,JobTracker会根据输入文件计算输入分片(input split),这些都检查通过后,JobTracker就会配置Job需要的资源并分配资源,然后JobTracker就会初始化作业,

    也就是将Job放入一个内部的队列,让配置好的作业调度器能调度这个作业,作业调度器会初始化这个Job,初始化就是创建一个正在执行的Job对象(封装任务和记录信息),以便JobTracker  跟踪Job的状态和进程。

      该Job被作业调度器调度时,作业调度器会获取输入分片信息,每个分片创建一个Map任务,并根据TaskTracker的忙闲情况和空闲资源等分配Map任务和Reduce任务到TaskTracker,同时通过心跳机制也可以监控到TaskTracker

    的状态和进度,也能计算出整个Job的状态和进度。当JobTracker获得最后一个完成指定任务的TaskTracker操作成功通知的时候,JobTracker会把整个Job状态置为成功,然后当查询Job运行状态时(注意:这是个异步操作),客户端

    会查到Job完成的通知。如果job中途失败,MapReduce也会有相应的机制处理。一般而言,如果不是程序员程序本身有bug,MapReduce错误处理机制都能保证提交的Job能正常完成。

   3、MapReduce内部执行原理详解

    那么,MapReduce到底是如何运行的呢? 按照时间顺序,MapReduce任务执行包括:输入分片Map、Shuffle和Reduce等阶段,一个阶段的输出正好是下一个阶段的输入,上述各个阶段的关系和流程如下:

    

    下面结合上文的实例问更加深入和详细地介绍上述过程,如下图:

    

  4、各环节介绍

   4.1、输入分片

      在进行Map计算之前,MapReduce会根据输入文件计算输入分片。每个输入分片对应一个Map任务,输入分片存储的并非数据本身,而是一个分片长度和一个记录数据的位置的数组。输入分片往往和HDFS和block(块)

    关系密切,假如设定的HDFS的块的大小是64MB,如果输入只有一个150MB,那么MapReduce会把此大文件切分为三片(分别为:64MB、64MB和22MB),同样,如果输入为两个文件,其大小分别是22MB和100MB,那么

    MapReduce会把20MB文件作为一个输入分片,100MB则切分为两个即64MB和36MB的输入分片。对于上述实例文件1和文件2,由于非常小,因此分别被作为split1和split2输入Map任务1和2中(此处只为说明问题,实际处理

    中应该将小文件进行合并,否则如果输入多个文件而且文件大小均远小于块大小,会导致生成多个不必要的Map任务,这也是MapReduce优化计算的一个关键点)。

  4.2、Map阶段

    在Map阶段,各个Map任务会接收到所分配的split,并调用Map函数,逐行执行并输出键值。比如对于上面的例子,map task1 将会接收到input split1,并调用Map函数,其输出如下的键值对:

    big 1, data 1, offline  1, data 1, online 1, data 1, offline 1, online 1, data 1

  4.3、Combiner 阶段

      Combiner 阶段是可选的的,Combiner其实也是一种Reduce操作,但它是一个本地化的Reduce操作,是Map运算的本地后续操作,主要是在Map计算出中间文件前做的一个简单的合并重复键值的操作,

    例如上述文件1中data出现了4次,Map计算时如果碰到一个data的单词就会记录1,这样就重复了4次,Map任务输出就有冗余,这样后续处理和网络传输都被消耗不必要的资源,一次通过Combiner操作可以解决和

    优化次问题。但这一操作是有风险的,使用它的原则是Combiner的输出不会影响到Reduce 计算的的最终输入,例如,如果计算只是求总数、最大值及最小值,可以用Combiner操作,但是如果做平均值计算使用Combiner,

    最终Reduce计算结果就会出错。

  4.4、Shuffle阶段

      Map任务的输出必须经过一个名叫Shuffle的阶段才能交给Reduce处理。Shuffle阶段是MapReduce的核心,也是奇迹发生的地方,同时Shuffle阶段的性能直接影响整个MapReduce的性能。

      那什么是Shuffle呢?一般理解为数据从Map Task输出到Reduce Task输入的过程,它决定了Map Task的输出如何且高效第传输给Reduce Task。

      总的来说,Shuffle阶段包含在Map和Reduce两个阶段中,在Map阶段的Shuffle阶段是对Map的结果进行分区(partition)、排序(sort)和分隔(spill),然后将同一分区的输出合并在一起(merge)并写在磁盘上,同时按照不同的

    分区划分发送给对应的Reduce(Map输出的划分和Reduce任务的对应关系由JobTracker确定)的整个过程;Reduce阶段的Shuffle又会将各个Map输出的同一个分区划分的输出进行合并,然后对合并的结果进行排序,最后交给

    Reduce处理的整个过程。

    下面从Map和Reduce两端详细介绍Shuffle阶段。

    4.4.1、Map阶段Shuffle

      通常MapReduce计算的都是海量数据,而且Map输出还需要对结果进行排序,内存开销很大,因此完全在内存中完成是不可能的也是不现实的,所以Map输出时会在内存里开启一个环形内存缓存区,并且在配置文件里为

    这个缓存区设置了一个阀值(默认是80%,可以自定义修改此配置)。同时,Map还为输出操作启动了一个守护线程,如果缓存区的内存使用达到了阀值,那么这个守护线程就会把80%的内存区内容写到磁盘上,这个过程叫分隔

    (spill),另外的20%内存可以供Map输出继续使用,写入磁盘和写入内存操作是互不干扰的,如果缓存区被撑满了,那么Map就会阻塞写入内存的操作,待写入磁盘操作完成后再继续执行写入内存操作。

      缓存区内容分隔到磁盘前,会首先进行分区操作,分区的数目由Reduce的数目决定。对应本例,Reduce的数目为2个,那么分区数就是2个,然后对每个分区,后台线程还会按照键值对需要写出的数据进行排序,如果配置了

    Combiner函数,还会进行Combiner操作,以使得更少地数据被写入磁盘并发送给Reducer。

      每次的分隔操作都会生成一个分隔文件,全部的Map输出完成后,可能会有很多的分隔文件,因此在map 任务结束前,还要进行合并操作,即将这些分隔文件按照分区合并为单独的文件。在合并过程中,同样也会进行排序,

    如果定义了Combiner,也会进行Combiner操作。

      至此,Map阶段的所有工作都已经结束,最终生成的文件也会存放在TaskTracker能访问的某个本地目录内。每个Reduce Task不断地从JobTracker那里获取Map Task是否完成的信息,如果Reduce task得到通知,获知某台

    TaskTracker上的Map Task执行完成,Shuffle的后半段过程,也就是Reduce阶段的Shuffle,便开始启动。

   4.4.2、Reduce阶段Shuffle

    Shuffle 在Reduce阶段可以分为三个阶段:Copy Map输出、Merge阶段和Reduce处理。

    1、Copy Map输出:

      如上文所述,Map任务完成后,会通知TaskTracker状态已完成,TaskTracker进而通知JobTracker(这些通知一般通过心跳机制完成)。对Job来说,JobTracker记录了Map输出和TaskTracker的映射关系,同时

    Reduce也会定期向JobTracker获取Map的输出与否以及输位置,一旦拿到输出位置Reduce就会启动Copy线程,通过HTTP方式请求Mask Task所在的TaskTracker获取其输出文件。因为Map Task早已结束,这些文件就被TaskTracker

    存储在Map Task所在的本地磁盘中。

    2、Merge阶段:

      此处的合并和Map阶段的合并类似,复制过来的数据会首先放入内存缓存区中,这里的内存缓存区比Map阶段的要灵活很多,它基于JVM的heap size设置,因为Shuffle阶段Reduce task并不运行,因此大部分内存

    应该给Shuffle使用;同时此Shuffle的合并阶段根据要处理的数据量的不同,也可能会有分隔到磁盘的过程,如果设置了Combiner函数,Combiner操作也会执行。

      从Map阶段的Shuffle过程到Reduce阶段的Shuffle过程,都提到了合并,那么合并究竟是怎样的呢?如上面的例子,Map Task1对于offline的键值是2,而Map Task2的offline键值是1,那么合并就是将offline的键值合并为group,

    本例即为:<offline,{2,1}>。

    3、Reduce Task的输入:

      不到合并后,最后会生成一个最终结果(可能在内存,也可能在磁盘),至此,Reduce Task的输入准备完毕,下一步就是真正的Reduce操作。

  

  4.5、Reduce阶段

    经过Map和Reduce阶段的Shuffle过程后,Reduce任务的输入的准备完毕,相关的数据已经被合并和汇总,Reduce任务只需要调用Reduce函数即可,对于本例即对每个键,调用sum逻辑合并value并输出到HDFS即可,比如对于

    Reduce Task1的offline的键,只需要将集合{2,1}相加,输出offline 3即可。

  至此,整个MapReduce的详细流程和原理介绍完毕,从上述过程中,Shuffle是整个流程中最为核心的部分,也是最复杂的部分。

  参考资料:《离线和实时大数据开发实战》

原文地址:https://www.cnblogs.com/shaosks/p/9442802.html