MapReduce原理总结
一.什么是MapReduce?
MapReduce是面向大数据并行处理的计算模型、框架和平台,它隐含了以下三层含义:
- MapReduce是一个基于集群的高性能并行计算平台(Cluster Infrastructure)。它允许用市场上普通的商用服务器构成一个包含数十、数百至数千个节点的分布和并行计算集群。
- MapReduce是一个并行计算与运行软件框架(Software Framework)。它提供了一个庞大但设计精良的并行计算软件框架,能自动完成计算任务的并行化处理,自动划分计算数据和计算任务,在集群节点上自动分配和执行任务以及收集计算结果,将数据分布存储、数据通信、容错处理等并行计算涉及到的很多系统底层的复杂细节交由系统负责处理,大大减少了软件开发人员的负担。
- MapReduce是一个并行程序设计模型与方法(Programming Model & Methodology)。它借助于函数式程序设计语言Lisp的设计思想,提供了一种简便的并行程序设计方法,用Map和Reduce两个函数编程实现基本的并行计算任务,提供了抽象的操作和并行编程接口,以简单方便地完成大规模数据的编程和计算处理
MapReduce的主要思想:分久必合
MapReduce的核心思想:“相同”的key为一组,调用一次reduce方法,方法内迭代这一组数据进行计算。
二.MapReduce做什么?
MapReduce擅长处理大数据,它为什么具有这种能力呢?这可由MapReduce的设计思想发觉。MapReduce的思想就是“分而治之”。MapReduce的核心思想:“相同”的key为一组,调用一次reduce方法,方法内迭代这一组数据的计算。
-
Mapper负责“分”,即把复杂的任务分解为若干个“简单的任务”来处理。“简单的任务”包含三层含义:
一是数据或计算的规模相对原任务要大大缩小; 二是就近计算原则,即任务会分配到存放着所需数据的节点上进行计算;(减少传输) 三是这些小任务可以并行计算,彼此间几乎没有依赖关系。
-
Reducer负责对map阶段的结果进行汇总。至于需要多少个Reducer,用户可以根据具体问题,通过在mapred-site.xml配置文件里设置参数mapred.reduce.tasks的值,缺省值为1。
三.MapReduce由Map端和Reduce端两个阶段组成。
.
流程分析:
.
整个MapReduce的过程大致分为 Map–>Shuffle{包含Combine(手动开启)}–>Reduce
1.首先是数据的切片
- 一个Map Task一次处理一个Split切片,一般来说Spilt≈block块=128M
- 因为block块中数据以字节形式存放,当放UTF-8形式的中文时(3字节),block块的最后可能会切割一个文字,造成乱码
- 所以为了解决它,split切片取比block多一条数据或者比block块少一条数据。
下面通过一个单词计数案例来理解各个过程,可以通过代码设置输入格式化类
job.setInputFormatClass(KeyValueTextInputFormat.class); 或者
job.setInputFormatClass(TextInputFormat.class);
- 因为这里TextInputFormat.class是默认值,所以我们先讨论它。
将文件拆分成splits(片),并将每个split按行分割形成<key,value>对,如图所示。这一步由MapReduce框架自动完成,其中偏移量即key值,数据即为Value值。
- KeyValueTextInputFormat格式化类,把HDFS数据中的第一列作为key,剩余的作为Value。
2.Map的处理
MapTask读到的数据是上述key-value形式的数据,默认情况下key是数据在block块中的偏移量,value就是数据,在Map的处理时,会对key-value进行自定义的代码操作意为对HDFS的数据进行格式的修改,以便形成新的key-value 对,所以此时的key有可能不是偏移量了。
将以TextInputFormat分割好的<key,value>对交给用户定义的map方法进行处理,生成新的<key,value>对,如下图所示。。
3.shuffle write的三个处理
什么是shuffle?
走网络传输的过程,把相同的数据放入一类,即shuffle洗牌的功能。
3.1为Map Task产生的数据打标签
- 打标签的目的就是为了分区,让着一条数据知道将来被送往哪个Reduce Task处理。
- 由默认的分区器HashPartitioner进行分区,它的策略是根据Map输出的key的HashCode值%Reduce Task的个数的值来确定每个key-value键值对所在的分区。
- 也可以通过在代码中使用 job.setPartitionerClass(MyPartitioner.class)自定义分区器,不过需要继承默认的分区器HashPartitioner.
假设分区后是这样的
3.2打过标签的数据一条条写入Buffer in memory
打过标签的数据会一条条读入到内存缓冲区,内存缓冲区大小为100M,会分为两个部分,一部分是80M,另一部分是20M;
- map task是一条一条的往buffer中去写,一旦写到80M,此时它会将这80M内存封锁。当它被封锁住时,数据被读入20M的内存中。
- 进入buffer内存缓冲区之后的每一条记录是由三部分组成的:分区号、key、value
- HDFS block块按照字节来切割,在存储的时候极有可能会乱码,为防止此情况的发生,一般会按完整记录 区划分,所有split一般会比block大几kb或者小几kb
3.3在内存被封锁后对数据进行combiner
根据代码job.setCombinerClass(MyTQReducer.class来手动开启combiner,但是不是所有的时候都可以用combiner,例如求平均值的时候不可以使用combiner。
- 需要注意的是MyTQReducer这个类需要继承Reducer类,实现它的Reduce方法
- 一般job.setCombinerClass()与job.setReducerClass();的参数是一致的,因为他们都对数据进行聚合,不过是小聚合和大聚和。
如果设置了combiner之后流程图会变成这个样子
3.4排序
将内存80M的数据根据分区号排好序,再在分区内根据key大小排序,将相同分区的数据放在一起,并且分区内部的数据也是有序的 。,此时,为了不造成阻塞,其他的数据写入内存的剩余的20M中。
3.5溢写
当当内存中的combiner和sort过程结束后就开始溢写数据到磁盘上,此时磁盘就是一个根据分区号分好区的,并且内部有序的磁盘小文件,每进行一次溢写就会产生一个磁盘小文件。
如下图:
3.6合并
当读完所有的数据之后,内存也将所有的数据溢写到本地磁盘形成很多个小文件之后,会将磁盘上的小文件合并成一个大文件,在合并的时候会使用归并排序的算法,将各个小文件合并成一个有序的大文件,大文件会根据分区号将内部不同分区文件提交给Reducce端;每一个map task都会经过刚才所说的所有过程,也就是每一个map task都会产生一个有分区的并且分区内部有序的大文件。
流程如下图:
注意,当很多个磁盘小文件进行合并时(一般指大于三个以上)会再次进行combiner,是shuffle的网络传输,
但是磁盘文件很少时不会进行,因为传输的数据本来就不大,若进行combiner可能效率会比直接传输更低。
.
Reduce task流程
1)磁盘大文件交给Reduce Task处理,按分区拉取,把分区数据写入到内存中,分区数据内部是有序的,内存满了就会溢写,溢写之前会排序,溢写到磁盘的过程与Map端类似;当所有的数据拉取过来后,会将溢写产生的磁盘小文件合并和排序成有序的大文件,大文件按Key分组,相同的key为一组;Reduce Task只有一个结果文件,输出的内容是追加到文件的。
注意点
1)产生一个有序的大文件的目的是为了提高分组的分组效率;归根结底,四次排序都是为了提高分组的效率。
2)整个过程中,Map计算与Reduce计算之间的过程称为shuffle,即将数据进行分区并分发到相对应的RedudeTask;在MapTask的shuffle称为shuffleWrite,在ReduceTask的shuffle称为shuffleRead。