MapReduce是一个分布式运算程序的编程框架,它的核心功能是将用户编写的业务逻辑代码和自带的默认组件整合成一个完整的分布式运算程序,并行运行在hadoop集群上。
作为一个计算的编程框架,自然要有输入值和输出值,通过MapReduce本身定义的计算模型,将输入值转化为输出值,对于我们开发者来说,自己独立实现一个并行的计算程序,难度还是很大的,所以MapReduce就是一种简化的并行计算的编程模型,降低了开发并行应用的入门门槛。
Hadoop MapReduce的构思体现在下述的三个方面
1,如何应对大数据的处理,分而治之
对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系的数据无法进行并行计算!
2,构建抽象模型
MapReduce借用了函数编程的思想,用map和reduce两个函数提供了高层的并发编程抽象模型
map:对一组数据元素进行重复性的处理。
reduce:对map的中间结果进行某种进一步的结果整理。
MapReduce 中定义了如下的 Map 和 Reduce 两个抽象的编程接口,由用户去编程实现:
map: (k1; v1) → [(k2; v2)]
reduce: (k2; [v2]) → [(k3; v3)]
Map 和 Reduce 为程序员提供了一个清晰的操作接口抽象描述。通过以上两个编程接口,大家可以看出 MapReduce 处理的数据类型是
(1)读取数据组件Inputformat会通过getsplits()方法对传来的文件进行逻辑切片规划,有多少个spilt就对应多少个maptask,切片的大小和block的大小保持一致,在配置文件中可以修改,默认是128M,切片数量和文件数量,文件大小和切片大小有关,spilt和block的对应关系默认是一对一。
(2)将文件切片后,有recordreader读取,以/n(回车)为分隔符,进行每行的读取,返回键值对以偏移量和每行的内容为key和value,
(3)读取返回的的key value,进入用户自己继承的mapper函数中,recordreader每读取一行,这里就调用一次。
(4)map处理完成后,会通过context.write将数据收集起来,在进入缓冲区之前,会进行分区的操作。分区数可由用户通过代码设定,job.setreducetask.默认的是HashPartitioner来进行分区,mapreduce提供partition的接口,他的作用是通过key或value以及reduceTask的数量进行分区,来决定通过那个reduceTask处理。
默认对 key hash 后再以reduce task 数量取模。默认的取模方式只是为了平均 reduce 的处理能力,如果用户自己
对 Partitioner 有需求,可以订制并设置到 job 上。
(5)接下来,将数据放进内存,内存中的这片区域叫做环形缓冲区,缓冲区的作用是用来收集map的结果,减少磁盘io的影响,环形缓冲区其实是一个数组,数组中存放着 key、value 的序列化数据和 key、value 的元数据信息,包括 partition、key 的起始位置、value 的起始位置以及 value 的长度。环形结构是一个抽象概念。
缓冲区是有大小限制,默认是 100MB。当 map task 的输出结果很多时,就可能会撑爆内存,所以需要在一定条件下将缓冲区中的数据临时写入磁盘,然后重新利用这块缓冲区。这个从内存往磁盘写数据的过程被称为 Spill,中文可译为溢写。这个溢写是由单独线程来完成,不影响往缓冲区写 map 结果的线程。溢写线程启动时不应该阻止 map 的结果输出,所以整个缓冲区有个溢写的比例 spill.percent。这个比例默认是 0.8,也就是当缓冲区的数据已经达到阈值(buffer size * spill percent = 100MB * 0.8 = 80MB),溢写线程启动,锁定这 80MB 的内存,执行溢写过程。Map task 的输出结果还可以往剩下的 20MB 内存中写,互不影响
(6)当溢写线程启动后,需要对这 80MB 空间内的 key 做排序(Sort)。排序是MapReduce 模型默认的行为,这里的排序也是对序列化的字节做的排序。如果 job 设置过 Combiner,那么现在就是使用 Combiner 的时候了。将有相同 key 的 key/value 对的 value 加起来,减少溢写到磁盘的数据量。Combiner 会优化 MapReduce 的中间结果,所以它在整个模型中会多次使用。
(7)每次溢写会在磁盘上生成一个临时文件(写之前判断是否有 combiner),如果 map 的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个临时文件存在。当整个数据处理结束之后开始对磁盘中的临时文件进行merge 合并,因为最终的文件只有一个,写入磁盘,并且为这个文件提供了一个索引文件,以记录每个 reduce 对应数据的偏移量
(8)Copy 阶段,简单地拉取数据。Reduce 进程启动一些数据 copy 线程(Fetcher),通过 HTTP 方式请求 maptask 获取属于自己的文件。
(9) Merge 阶段。这里的 merge 如 map 端的 merge 动作,只是数组中存放的是不同 map 端 copy 来的数值。Copy 过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比 map 端的更为灵活。merge 有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的 merge。与 map 端类似,这也是溢写的过程,这个过程中如果你设置有 Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种 merge 方式一直在运行,直到没有 map 端的数据时才结束,然后启动第三种磁盘到磁盘的 merge 方式生成最终的文件。
(10) 把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。
(11) 对排序后的键值对调用 reduce 方法,键相等的键值对调用一次 reduce 方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到 HDFS文件中。