Map执行时的RecordWriter实现之NewOutputCollector

来源:互联网 发布:苹果电脑不能淘宝付款 编辑:程序博客网 时间:2024/06/05 00:29

          在前面的一系列文章中我不断的提到了记录写入器RecordWriter<K,V>,它是一个抽象类,在map任务执行中的根本作用就是向某个文件系统的文件中写入map任务的输出结果——key-value集。所以,本文将主要讨论MapTask对记录写入器RecordWriter<K,V>的一个具体实现类——NewOutputCollector,来好好的看看这个NewOutputCollector对map任务的输出key-value集合做了哪些事情,最后又是如何以何种方式存入某个文件系统的

          NewOutputCollector的类名我们就可以大致的知道这个类的用户,即map任务的输出结果收集器。还是先来了解一下与它相关的类吧!


       这里的NewOutputCollector实际上可以看作是直接接受来自map操作输出的key-value,而NewOutputCollector的write实现也很简单,它什么也没有做,而是直接将这个key-value交给了MapOutputCollector的实现MapOutputBuffer

来做。可以简单的看一下的write(K,V)实现

public void write(K key, V value) throws IOException, InterruptedException {
      collector.collect(key, value, partitioner.getPartition(key, value, partitions));
 }

哦,对了,这里还要稍微的提一下map输出分配器partitioner,它主要是用来决定对于map操作的一个输出应该交给那个reduce来处理。其中,partitions是Job中reduce任务的数量,partitioner可以由用户来具体指定其实现类型,对应的配置参数为:mapreduce.partitioner.class,而默认的实现为HashPartitioner。

     下面就要好好的来看看map输出收集器MapOutputCollector的重量级实现MapOutputBuffer。这个map输出收集器的作用就是按照一个map输出(key-value)应该交由哪个reduce处理来收集map的结果输出,最终被分配给相同的reduce任务的map输出被存放到同一个文件或是某一个文件中的连续区域。当然,这中间可能还存在key-value的排序操作。那么,可能有不少人想要问,对map的输出进行分类、排序,可能会占用大量的系统内存,MapOutputBuffer又是如何解决这个问题的呢?MapOutputBuffer在其内部采用了先分后合的策略来对map的大量输出进行分类和排序的,为了提高效率,这中间就少不了使用一定的缓存了。那么MapOutputBuffer是如何充分利用缓存的呢?接下来就要请大家注意了,因为我将介绍的有关内容会涉及到mapreduce的调优问题了。浏览一下与MapOutputBuffer缓存有关的内部属性:


MapOutputBuffer内部大缓存大小可以由用户在配置文件中设置,这个设置对应文件maprd-site.xml中的io.sort.mb项,值得单位是MB。MapOutputBuffer将这个缓存分为了二部分,第一部分用来存放key-value的二进制具体值,对应的属性kvbuffer,第二部分用来指定一个key-value属于哪个reduce、它们的key、value值分别在kvbuffer中的偏移位置,对应的属性为kvoffsetskvindices,它们的物理布局可以这样来表示:


那么,如何分配kvbuffer和(kvoffsets+kvindices)的大小将影响到缓存的使用效率,这个机器永远也无法知道,所以缓存的分配就需要用户根据实际的作业来估算它们大约分别占缓存的多少,这个百分比可以通过配置文件mapred-site.xml的io.sort.record.percent项来配置,同时这个值在hadoop的0.2.2版本中不能超过1.0也不能小于0.01,这个分配过程具体如下:

另外,为了提高map输出处理的整体效率,MapOutputBuffer为缓存设置了一个阈值softRecordLimit,当缓存的key-value数量超过这个值得时候,就要通知溢出处理线程spillThread开始处理已经缓存的key-value,处理完之后,它们会被保存到一个临时的本地文件中(最后会合并它们),同时释放它们占用的缓存。在这个过程中,MapOutputBuffer剩余的缓存任然可以接受map的输出key-value,在效率方面你懂的!MapOutputBuffer的缓存使用阈值可以通过配置文件mapred-site.xml的io.sort.spill.percent项来配置,这个值表示占总缓存的百分比,最后的计算结果也很简单:

softBufferLimit= (int)(kvbuffer.length * spillper);  //记录缓存阈值
softRecordLimit = (int)(kvoffsets.length * spillper);  //记录值缓存阈值

      MapOutputBuffer对缓存的溢出处理也很简单,首先它要对这一部分缓存的map输出进行排序,在这里先排序的好处一是为了紧接着的key-value合并处理,二是为了key-value的分类(key-value类别就是它分配给了哪个reduce来处理)。最后这些key-value缓存被保存到两个临时中间文件:spill*.out、spill*.out.index,其中,spill*.out保存的是key-value的值,spill*.out.index保存了每一个reduce的输出key-value在spill*.out中的起始位置、原始长度、压缩长度等索引信息。每一次的缓存溢出处理产生的结果都可以看作是最后总结过的一个段,最后在所有的map操作输出数据处理完毕之后就要开始可并所有的中间段,产生总的一个.out和对应的.out.index文件。简单的看一下这个过程的序列图:


      关于最后合并所有的map输出中间段,我在本文没有作详细的阐述,因为这个合并过程有点复杂,我可能会在以后专门介绍。
原创粉丝点击