最近花了一段时间去了解MapReduce 的工作原理:Map对数据集上的独立元素进行指定的操作,生成键值对形式中间结果;shuffle是MapReduce的心脏,对中间结果进行数据整合分区排序处理,有助于编写效率更高的mapreduce程序和hadoop调优;Reduce则对中间结果中相同“键”的所有“值”进行规约,以得到最终结果。自己画了一张流程图如下图,希望能对MapReduce感兴趣/想了解的同学有所帮助。 同时也希望有发现内容不正确或者有疑问的地方,望指明,一起探讨,学习,进步。
MapReduce工作原理
通俗易懂理解MapReduce
现实生活中,比如期末考试完了,那考卷由不同的老师批改,完成后如果想知道全年级最高分,那么可以这么做:
1)各个老师根据自己批改过的所有试卷分数整理出来(map):
=>(course,[score1,score2,...])
2)各个老师把最高分汇报给系主任(shuffle)
=>(courese, [highest_score1,highest_score2,...])
3)系主任统计最高分(reduce)
=>(courese, highest_score)
当然,如果要多门课程混在一起,系主任工作量太大,于是副主任也上(相当于2个reduce),则老师在汇报最高分的时候,相同课程要汇报给同一个人(相同key传输给同一个reduce),例如数学英语汇报给主任,政治汇报给副主任。
从上述的例子我们可以总结出以下结论:
MapReduce是一种分布式计算模型,用以进行大数据量的计算。它屏蔽了分布式计算框架细节,将计算抽象成map、shuffle和reduce三部分,Map负责将数据打散,进行脏数据处理和过滤;shuffle是对map处理数据的结果进行分区和排序过程聚集整合;Reduce负责对shuffle处理的数据结果进行最终的计算处理和聚集。
map、shuffle和reduce三部分的任务分工如下:
最上面的第一张图是比较全的描述出MapReduce的过程,下面用一张粗略的简图来描述map、shuffle和reduce三种的关系:
Input hdfs文件切割
第一张大图中Input hdfs文件切割的过程为①②③④步骤,整个过程是通过TextInputFormat(->RecordReader --> read())
一次读一行,返回(key,value)结果。
在hadoop源码中TextInputFormat是继承InputFormat这个抽象父类的,TextInputFormat本质上就是java的String,hadoop对其及java基本类型(比如int–>IntWritable,…)进行了封装而已。那么InputFormat是什么?
InputFormat
hadoop InputFormat 源码:
public abstract class InputFormat<K, V> {
public InputFormat() {
}
public abstract List<InputSplit> getSplits(JobContext var1) throws IOException, InterruptedException;
public abstract RecordReader<K, V> createRecordReader(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
}
hadoop InputFormat有两大核心方法getSplits
逻辑分片和createRecordReader
创建读取记录。
getSplits文件切割
关于getSplits逻辑分片,实际上我们最常用的就是FileInputFormat中的文件切割方法(重写InputFormat的getSplits),源码如下:
if ((length != 0) && isSplitable(fs, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(new FileSplit(path, length-bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining, blkLocations[blkLocations.length-1].getHosts())); }
} else if(length!=0) {
splits.add