简介
本文先介绍Google 开源项目mapreduce1.0版。mapreduce的设计是为实现一个抽象的计算框架,使用者不用关心底层如何实现,只需要关注业务逻辑从而计算海量、复杂的原始数据。
编程模型
设计理念是实现“计算向数据靠拢”,“数据向计算靠拢”会造成大量的I/O消耗,计算过程分为map和reduce阶段。
存储在HDFS中的数据被切分成很多块,这些块可以分发到多个map任务并行处理,生成一个内部有序外部无序的key/value数据集作为reduce任务的输入,由reduce执行最后的合并计算,最终形成一个按照key规则排序的计算结果。
Map
- 输入数据被切分成逻辑数据集合,该切分并不是物理意义的切分,只是记录了数据的开始和结束的长度,切分规则由blocksize,maxsize,minsize这三个属性的中间值决定,一般一个block就是一个map任务。
- map任务被分发到多台机器执行,每个map任务最后生成一个“外部无序内部有序”的key/value集合,并缓存在内存中
- 为了减少磁盘I/O带来的消耗,在2.0版本中,map计算结果不再落地写入本地,直接从内存通过网络传给执行reduce任务的计算机
从上得知,map最主要的任务就是完成数据的分发并行计算。
Reduce
- 当所有map任务完成后,如果map没有进行combiner(合并),将会输出<key,value(v1,v2,v3)>的结果作为reduce的输入
- 同一个key/value集合调用一次reduce函数,进行最后的合并
reduce最主要的任务是完成各个并行计算结果的合并处理。
shuffle
mapreduce工作的核心环节,包含排序(sort),合并(combiner)以及分区(partition)。
map端的shuffle
- 输入数据和执行map任务
接受key/value作为输入后,根据map函数规则转换成一批key/value输出 - 写入缓存
每个map任务分配固定缓存,map输出结果不直接写入磁盘,积累一定数量后(常见80%)再一次性写入磁盘,可以大大降低IO消耗 - 溢写(分区、排序和合并)
溢写就是执行上面写入的过程,但是在写入磁盘之前,会执行数据分区来指定这些键值对最终会发送给哪个reduce处理,默认采用哈希函数对key进行哈希后再用reduce的任务数进行取模。
分区完成后,在一个key/value集合里面进行排序。合并的意思就是在同一个集合里面将相同key的value相加,得到一个key/value的最终结果,相对于不合并产生的<key,1><key,1>,<key,2>可以减少很多的磁盘消耗 - 归并
在所有map任务执行结束后,所有的key/value集合重新形成一个key/value集合,将<key,1><key,3><key,7>,集合成<key,value(1,3,7)>的形式
reduce端的shuffle
- reduce端只需要读取map结果,然后执行合并,计算1+3+7的结果输出<key,11>的键值对
wordcount实例分析
有一文件内容如下:
I love Hongkong I love beijing
I love China I love shanghai
经过map后得到
<“I”,1>,<“love”,1>,<“Hongkong”,1>,<“I”,1>,<“love”,1><“beijing”,1>
<“I”,1>,<“love”,1>,<“China”,1>,<“I”,1>,<“love”,1><“shanghai”,1>
然后在map端shuffle后(分区,排序,合并)
<“beijing”,1>,<“Hongkong”,1>,<“I”,(1,1)>,<“love”,(1,1)>
<“China”,1>,<“I”,(1,1)>,<“love”,(1,1)><“shanghai”,1>
如果定义了combiner则结果变为
<“beijing”,1>,<“Hongkong”,1>,<“I”,(2)>,<“love”,(2)>
<“China”,1>,<“I”,(2)>,<“love”,(2)><“shanghai”,1>
提交reduce合并后
<“beijing”,1>,<“China”,1>,<“Hongkong”,1>,<“I”,4>,<“love”,4>,<“shanghai”,1>