map任务执行中的Spill/Meger/Combiner

       记得在以前的博文中已经比较详细地讲述了map在TaskTracker上的执行过程,但那只是我为了简化这种处理流程而假设map任务在理想情况下执行的,这种理想的假设条件是:TaskTracker是内存足够大,而能完全存储该map的任务的输出。很显然,这种情况在理想情况下是不太有可能的,但话又说回来,如果我们在TaskTracker上为Map Slot配置足够的内存,在某些作业的执行过程中上述的理想情况还是时有出现,可是这种做法是不智的,应为如果这样做的话,那么一个TaskTracker上就配置不了几个Map/Reduce Slot了,这样能在TaskTracker是同时运行的任务就很少了,从而导致其它资源的严重浪费,如cpu、网络带宽等。为了解决map任务执行过程中内部不存不足的问题,Hadoop特意设计了Spill-Sort-Merger的策略。

       在Hadoop中,每一个TaskTracker节点可以根据自己机器的硬件配置来为自己设置可以有多少Map/Reduce Slot ,不过没有限制一个Slot在运行时可使用的最大物理内存,但该TaskTracker上的所有正在运行的Slot使用的总内存不能超过它所在机器的总物理内存;另外,map的输出结果最后需要根据key 来排序(因为reduce的输入是根据key聚合)。我们知道一涉及到排序问题,就需要吃内存了,而且,对于相同的排序算法,存放在内存中的数据越多,排序的速度就越快,但是前面说过TaskTracker不可能为运行该map任务的Slot提供最够的内存,所以就采用了Spill-Sort-Merge策略。不得不说,Hadoop在这一点上设计的还是比较灵活的,第一,没有限制TaskTracker上一个Map/Reduce Slot可使用的最大内存,但显然不能超过机器的最大物理内存,第二,用户在客户端提交作业之前可以根据自己的应用的具体情况来设置map输出结果的排序可用内存的大小,配置的项为:io.sort.mb

    这个策略的具体过程如下,对于map的输出:key-value,TaskRunner首先把它存储到缓存中,当缓存的使用量超过一个阈值的时候,就会启动Sort-Spill线程,来对这一部分key-value数据进行排序,排完序之后,就把这一部分结果刷到本地磁盘,然后清空缓存来存储接下来的map输出。从这个简单的过程中我们可以看出,TaskRunner为了提高效率,并没有等到缓存慢的时候才Sort-Spill操作,而是当缓存的使用量到了一个指定的阈值时(缓存满之前)就开始启动一个后台线程来对这一部分数据进行sort-spill操作,而主线程仍然接受map的输出。这个阈值可以完全由用户自己来设置,对应的配置项为:io.sort.spill.percent

    同时,为了提高应用的性能,我们一般会尽量在map中设置combiner,而不是去依赖于reduce。这就是说,能用combiner的,全完不要用reduce,至于为什么,其实很容易就能讲明白的,所以在这里就不解释了。同时可能出现这样的一种情况,那就是map的输出结果不大,如果combine的话则最后消耗的时间会比不执行combine的消耗的时间大。这样的话设置combiner就得不偿失了,所以有时我们会根据实际情况为combine设置一个阈值,当spill的文件大于某一个阈值是才执行combine操作,这个combiner及其阈值的设置方法如下:

<property> <name>mapreduce.combiner.class</name> <value>combiner类的全限定名</value> <description>combiner类必须是org.apache.hadoop.mapreduce.Reducer<K,V,K,V>的子类</description> </property> <property> <name>min.num.spills.for.combine</name> <value>整数值</value> </property>




     map输出的Spill-Sort-Merge过程如下图:

   还要补充的一个说明就是,为了提高对所有spill的中间文件的merge效率,用户可以设置一个合并因子来确定一次合并多少个文件,这个合并因子对应的配置项为:io.sort.factor


你可能感兴趣的:(hadoop,算法,存储,任务,merge,作业)