mapreduce原理完全剖析与shuffle机制

来源:互联网 发布:刮骨疗伤是真的吗 知乎 编辑:程序博客网 时间:2024/05/22 03:47

在前面几篇文章都都大致介绍了mapreduce的一些过程和原理,由于没学那么多所以有些表达的都很有欠缺,这里给出了mapreduce原理的完全解析,shuffle机制,属于纯原理。
流程图:
这里写图片描述

过程分析:
从maptask开始,它会调用InputFormat组件(默认是TextInputFormat),该组件会接着调用组件RecordReader组件,该组件会读取maptask需要处理的文件信息,以kv的形式返回,返回打kv对会传到mapper(自定义mapper继承mapper重写map方法)的map方法中,接着执行完我们在map()中写的逻辑后,执行代码context.write(k,v)将kv写到了输出收集器(OutputCollector)中,而收集器又接着会将kv写到环形缓冲区中(对应一块缓存,里面存储的是数组,每个kv都写到该数组中,当写入的量达到容量的80%的时候,为了防止溢出会做清除工作,逐渐将前面的kv溢出一直往后推,kv则接着写到前面腾出来的区域,形成了环状),缓冲区中每溢出部分kv会调用spiller组件对溢出的kv进行分区和排序(自定义排序和分区都在前面的文章有介绍,比如排序需要组件实现WritableComparable接口,分区则需要自定义分区组件继承Partitioner类重写getPartition方法),溢出到文件中。最后所有的文件都会合并为一个文件同时会重新进行排序(当然还有分区,不过分区是固定了在哪里区都是一样,图中是2个分区partition0和partition1)形成maptask的最终结果文件。

就假设如图中所示,只有两台机器对应两个map task,生成了2个最终文件,每个文件都是2个分区partition0和partition1(对应两个reduce task)。生成了最终结果文件后,将两个最终文件的各自partition0分区的kv下载到reduce task0的本地磁盘工作目录,接着将这两个partition0分区合并成一个新文件并且排序,排序结果如图中所有,它会使用组件进行比较排序,使得相同key的kv连续排列。接着会以 k 迭代器(与该key对应的多个value)传到Reducer(通常是自定义Reducer继承Reducer,重写reduce()方法)的reduce()方法的形参中,然后执行自己的逻辑,最后使用context.write(k,v)一次写一行的写到OutPutFormat中(默认是TextOutPutForamat),该组件会调用RecordWrite的write()方法接kv输出到hdfs中(或者本地文件)。另一个reduce task当然也是同样的处理,处理的是两个partition1。
注:溢出和合并的时候会调用Combiner,其他什么时候自己也不清楚,由于图中执行的是wordcount程序,可以使用Combiner每次都进行归并,比如溢出的本来是a,1 a,1 a,1这样是不是麻烦呢?合并文件的时候也会麻烦占用空间效率低等问题,但是可以使用自定义Combiner改写成a,3就方便多了。而自定义的reduce程序就满足这种特性,所以我们在客户端程序中指定Combiner类job.setCombinerClass(WordcountReducer.class),即可。但是还是慎用,因为很多时候一旦Combiner会影响实际结果的。下面也给了个Combiner的代码

public class WordcountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{    @Override    protected void reduce(Text key, Iterable<IntWritable> values,            Context context) throws IOException, InterruptedException {        int count = 0;        for (IntWritable value : values) {            count += value.get();        }         context.write(key, new IntWritable(count));    }}

2、shuffle机制
mapreduce中,map阶段处理的数据如何传递给reduce阶段,是mapreduce框架中最关键的一个流程,这个流程就叫shuffle;shuffle: 洗牌、发牌——(核心机制:数据分区,排序,缓存);具体来说:就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序;
shuffle是MR处理流程中的一个过程,它的每一个处理步骤是分散在各个map task和reduce task节点上完成的,整体来看,分为3个操作:
1、分区partition
2、Sort根据key排序
3、Combiner进行局部value的合并
流程
其实就是图中的黄色区域标注出来的,解释已经写在了上面,下面按照步骤给出shuffle过程。
1、maptask收集我们的map()方法输出的kv对,放到内存缓冲区中
2、从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
3、多个溢出文件会被合并成大的溢出文件
4、在溢出过程中,及合并的过程中,都要调用partitoner进行分组和针对key进行排序
5、reducetask根据自己的分区号,去各个maptask机器上取相应的结果分区数据
6、reducetask会取到同一个分区的来自不同maptask的结果文件,reducetask会将这些文件再进行合并(归并排序)
合并成大文件后,shuffle的过程也就结束了,后面进入reducetask的逻辑运算过程(从文件中取出一个一个的键值对group,调用用户自定义的reduce()方法)

3、大量小文件的优化策略
问题:默认情况下,TextInputFormat(两个功能一个是读数据一个是切片)对任务进行切片机制是根据文件规划切片(比如hdfs默认block是128M那么切片也是128M),不管文件多小,都会是一个单独的切片,都会交给一个map task这样,如果有大量小文件,就会产生大量的map task,导致处理效率及其低下。

优化:
1、最好是在执行程序之前将多个小文件自己先合成个大文件再交给map task去处理。
2、如果已经是很多小文件存在于hdfs中了,那么可以使用另外一种InputFormat(CombinerInputFormat)来进行切片,它的切片逻辑跟FileInputFormat不同,它会将多个小文件从逻辑上规划到一个切片中,这样就能将多个小文件交给一个maptask去处理了。

原创粉丝点击