前言
当今天下,大数据组件百花齐放,满足不同应用场景的计算框架层出不穷,mapreduce似乎早已很难有一席之地,但是天下武功,殊途同归,不管怎么变化,都离不开mapreduce的基础,今天,就一起揭开下最原始的计算框架mapreduce的面纱。
核心设计理念
分布式计算,即移动计算 而不移动数据。
原理剖析
如图1,官方给出的mapreduce剑谱,一招一式,尽显奥妙无穷,整体个人觉得分为4部分,split阶段,map阶段,shuffle阶段,reduce阶段,具体如下:
图1 官方mapreduce原理图谱
Split阶段
Map阶段之前还有个准备,就是split,中文意思就是分片,顾名思义就是当一个大文件要进行mapreduce计算时,假设2T,不可能把2T文件全部加载到内存,先要对文件进行切割,就是对应的split操作,那切割成多大才合理呢?split有个原理公式:
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
protected long computeSplitSize(long blockSize, long minSize,
long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}
简而言之就是Math.max(minSize, Math.min(maxSize, blockSize)),因此默认情况下,一个split的大小为一个blocksize的大小,hadoop2.x默认为128M,一个split调用一次map函数,即对应一个map的输入,输出个数。
好的,请听题:
1.如何控制map的个数?
答:可以根据公式Math.max(minSize, Math.min(maxSize, blockSize));如在hdfs-site.xml配置文件上,修改dfs.blocksize,如下所示,但是修改完后,如果用了Hive jdbc连接,请重启hive jdbc service,不然select 1都会报错hive元数据的blocksize和文件的blocksize不一致,初步估计是hive的jdbc是冷加载只会在启动hive的时候去加载配置,后面hadoop系统配置更新,hive jdbc却傻傻的不去更新了,这就是一直不提倡用jdbc的原因,最好用cli接口;当然也可以修改MaxSplitSize和MinSplitSize,读懂公式,玩转split!
<property>
<name>dfs.blocksize</name>
<value>67108864</value>
</property>
2.文件被split成了多个分片来调用了多次map函数,请问,多次map函数调用之间的关系是并行的还是串行的?
答:取决于被split文件的存储格式和压缩格式是否可splitable,如果文件格式本身不支持splitable,如txt文件,那么多次map的调动是串行的,并不能分配到多个node并行计算,因为文件本身不能split,分散的话,会导致每个 split之间找不到上下文;相反,如果文件本身是可splitable的,如RC,ORC,Parquet等hadoop系统主流文件,本身可splitable,就可以让多个split后的文件并行的调用map,切记 ,文件格式本身可splitable,加上任何压缩格式也是可以splitable的,因为数据在map前,是会被解压的,第二,文件是否splitable,只对map阶段有影响,不影响shuffle和reduce阶段,但是科学而言,如果可以,尽量选支持splitable的文件。
Map阶段
map阶段其实非常简单,但是要注意一个思想,那就是所有的map,reduce输入输出都是key,value的形式,map阶段,就是将文件split后,每个split调用一次程序员编写的map函数来处理数据,map函数该怎么写,取决于你的功能。
好,请听题:
1.图1这个Job有几个map?
答:4个,别忘记下面的三根线。
2.图1这个Job在hadoop2.x执行,默认blocksize是128M的情况下,这个Job处理的数据量大概多大?
答:4个map,即有4个split,默认情况下每个split的大小等于blocksize的大小128M,所以,图1这个Job处理的数据量大小大概为:3128M~4128M之间,因为最后的split,就算不足128M也会生成一个单独的分片。
Shuffle阶段
Shuffle阶段是整个mapreduce的核心,也是最难理解的招式,拆招步骤如图2:
图2 Shuffle阶段详解
1.文件分成split分片后作为map的输入,如统计wordcount,输入的key为每个split的行号,value为每一行的word,map函数实现把每一行的word单个列出,统计每一行每个word的次数,key为每个word,value为word出现的次数,map的key,value输出结果首先做partition,partition的作用是决定这个map最后将被哪一个reduce所处理,默然的partition函数是对map输出的key做hash后再根据reduce的个数取模得到的值,然后map的输出结果进入环形缓冲区,这个缓冲区默认的大小是100M;
2.map的输出结果在唤醒缓冲区内面临两种结果,一种情况比较特殊,是这个文件本身足够小,不足环形缓冲区的80%,那么这些map的输出结果直接在溢写成一个文件,直接根据各自的partition值被各自对应的reduce来copy过去做reduce处理,如图3的编号3处上面的小箭头,当map结束后,输出结果最终都是要溢写 成磁盘文件的;第二种普遍的情况,map输出到环形缓冲区的文件足够大,当达到环形缓冲区的80%时,map的输出结果会开始溢写到磁盘,溢写到磁盘前这些map的输出会根据map输出的key触发sort排序,如果是wordcount程序,得到的结果是,当然,如果程序员调用了combiner函数,那么会根据combiner函数的逻辑,对同一个map的输出结果进行一次合并处理,其实combiner就是单个map的reduce。reduce就是所有map的combiner,有点拗口,理解的去记忆,原理懂了就是这么好记忆;
3.map的每次超过环形缓冲区80%的溢写,都会溢写成一个文件生成在磁盘,最终多次 的溢写有多个文件,需要对这些文件进行merge,即合并成一个大的文件,这些文件被各自的reduce来copy到reduce端,整个Shuffle阶段就完成了。
sort后:
aaa 2
bb 3
bb 5
c 4
如果有combinner后:
aaa 2
bb 8
c 4
好,请听题:
1.为什么是达到环形缓冲区的80%开始溢写,而不是100%?
答:原则上 可以设成100再溢写,但是一般没人这么做,因为你既要保证溢写额正常执行,也要保证后一个map的输出结果能顺利进入环形缓冲区,不然缓冲区内存不足,整个程序可能被迫强行挂起。
mapreduce.task.io.sort.mb 100 shuffle 的环形缓冲区大小,默认 100m
mapreduce.map.sort.spill.percent 0.8 环形缓冲区溢出的阈值,默认 80%
2.为什么溢写到磁盘要进行sort?
答:map端的sort是为了给reduce端sort降低负担,那reduce为什么要sort呢?这个reduce阶段再讲。
3.combiner一定要吗?如何正确使用combiner
答:combiner不一定需要,其作用是将同一个map的输出结果相同key的value做一下和reduce一样的算法操作,什么情况下不适合用combinner,比如求平均数,并不是说把相同key的先合并掉好,如下
求1,1,2的平均数,这里map的key毫无意义,只看value即可,
combiner前
key value
1 1
1 1
2 2
##结果应该是(1+1+2)/3
combiner后
1 1
2 2
##结果:(1+2)/2
显然不对,当然这里瞎写了combiner的算法,只是盲目的保证了和reduce一致,如果改改combiner算法,也许也是可以的,这里就不做演讲了,重在说明combiner的正确用法
Reduce阶段
图3 reduce阶段详解
reduce阶段其实也相对比较简单,shuffle的结果被reduce copy过来后,reduce之前,会根据reduce的输入结果的key做sort和group,然后每个相同的key为一个group,每个group调用一次reduce,然后输出结果存回到hdfs中,整个reduce结束,也就意味着整个mapreduce结束,注意:一定要等所有map的shuffle结束后,reduce才会开启。
好,请听题:
1.reduce为什么要sort,map又为啥要sort?
答:reduce sort是为了group,不然group会显得极其复杂,浪费计算资源,所有map端为什么要sort,就是减轻reduce端的负担。
如果不sort,所有的mapshuffle被copy到reduce端;
得到wordcount如下reduce输入:
key value
aaa 4
bb 6
aaa 5
ss 6
bb 6
……
这个情况下,再把key相同的分组group by 算法就比较复杂了,相反,
如果先sort后,得到的reduce输出如下:
key value
aaa 4
aaa 5
bb 6
bb 6
ss 6
group by时,只要参考前后两个key是否是一样即可。
2.reduce端和map的sort,用的是什么排序算法
答:归并排序,其一:归并排序的精髓,非常适合分布式计算的原理;其二:归并排序时间复杂度为:,
相比于其他经典的如插入排序,时间复杂度为 :
在大数据计算的时候,即n越大时,归并排序有优势,如图4的函数图谱:
图4 归并排序和插入排序时间复杂度对比
实例解析
整个mapreduce流程大概就是如此,以下是wordcount的实例剖析,如图5,wordcount mapreduce例子。
图5 wordcount mapreduce例子