hadoop-MapReduce源码

MapReduce计算框架重要类体系图

--环境结构

   |--------JobConf

   |--------JobClient

   |--------InputFormat

 |--------FileInputFormat

 |--------TextInputFormat

        |--------RecordReader

   |--------LineRecordReader

 

 

|--------OutPutFormat

|--------FileOutputFormat

      |--------TextOutputFormat

          |--------RecordReader

|--------LineRecordWriter

 

 

--数据处理引擎

 |--------MapperTask. run)

 |--------MapperRunner

 |--------MapOutputCollector

          |--------MapOutputBuffer

                   |--------SpillThread

 |--------MapTask.mergeParts()

 

 |--------ReduceTask.run()

          |--------MapOutPutCollector

|--------MapOutputCopier

 

_____________________________________________________________________________________

 

JobConf介绍

 JobConf是用户描述一个job的接口。下面的信息是MapReduce过程中一些较关键的定制信息:

MapTask介绍

MapTask启动和执行一个Map任务,初始化、启动map任务,调用的是run()方法。

run()方法源码骨架:

 

run方法里调用了runOldMapper()方法。这个方法是用于执行用户自定义Mapper类里的Map()方法。(M/R 2.0版本调用的是runNewMapper()方法)

runOldMapper()方法源码骨架:

InputSplit inputSplit  ……

RecordReader<INKEY,INVALUE> in  ……

MapRunnable<INKEY,INVALUE,OUTKEY,OUTVALUE> runner =ReflectionUtils.newInstance(job.getMapRunnerClass(), job);

MapOutputCollector<OUTKEY, OUTVALUE> collector ……

 

方法作用释义:

1.这个方法会解析job里的InputSplit信息,InputSplit信息里封装了一个Map任务处理的文件切片信息,如文件切片所在的节点位置信息,处理的start位置以及处理的长度length(可以类比Zebra的FileSpilt)

 

2.拿到文件切片信息之后,怎么来读取文件内容呢?——>解析job指定的RecordReader,Hadoop默认的是LineRecordReader(按行读取,把每行头位置信息的偏移量作为key,每行的内容作为Value)。

 

3.MapRunnalbe的作用是,根据反射机制,拿到用户(程序员)自定义的Mapper类,拿到这个类之后,就可以拿到用户的输出map<key,value>了。

细节说明:MapRunnable是一个接口,那实际上这里runner对象是它的实现类:MappRunner类的对象。

_____________________________________________________________________________________

 

 

MapRunner介绍

 

MapperRunner类源码骨架:

private Mapper<K1, V1, K2, V2> mapper;

public void configure(JobConf job) {

    this.mapper = ReflectionUtils.newInstance(job.getMapperClass(), job);

 }

 public void run(RecordReader<K1, V1> input, OutputCollector<K2, V2> output,Reporter reporter){

      while (input.next(key, value)) {

            mapper.map(key, value, output);

            }

      }

方法释义:MapRunner通过指定的Redcord读取器,读取input split的内容(默认是一行一行读)。每读取到一行,就调用一次map方法,并获得每次map方法的输出map<k,v>

_____________________________________________________________________________________

 

MapOutputCollector介绍

4.MapOutputCollector是一个接口,作用是收集每次调用map后得到的新的kv对,然后把他们spill到文件或者放到内存,以做进一步的处理,比如分区、排序,combine等(就是shuffle过程)。

即MapOutputCollector是处理输出map的类。

细节说明:MapOutputCollector 其中有两个子类:MapOutputBuffer和DirectMapOutputCollector。    DirectMapOutputCollector用在不需要Reduce阶段的时候。(比如在做利润排序案例的时候,没有reducer)

 

如果Mapper后续有reduce任务,系统会使用MapOutputBuffer做为实现类,MapOutputBuffer使用了一个缓冲区对输出map对进行缓存。

默认缓存大小是100mb,当缓存中数据量达到80%的时候,发生spill过程,溢写到本地磁盘上。

 

_____________________________________________________________________________________

 

MapOutputBuffer介绍

MapOutputBuffer骨架源码:

 public static class MapOutputBuffer<K extends Object, V extends Object> implements MapOutputCollector<K, V>, IndexedSortable {

    private int partitions;  //分区数量,reduce的数量,如果用户不设置,默认是1

    private Class<K> keyClass;//输出mapkey类型

    private Class<V> valClass;//输出mapvalue类型

    private RawComparator<K> comparator;//比较器,用于排序

    final SpillThread spillThread = new SpillThread();//是一个线程类,负责spill溢写到磁盘的过程

    final BlockingBuffer bb = new BlockingBuffer();//用于存放map输出结果的缓冲区

   

    public synchronized void collect(K key, V value, final int partition){

startSpill();//开始溢写线程,这个方法会启动SpillThread线程开始溢写。溢写的条件是缓冲区里的数据达到总大小的80%。默认是缓冲区是100mb。这个是可以在配置文件里配置的

keySerializer.serialize(key);//将输出map<K,V>Key值序列化

valSerializer.serialize(value);//将输出map<K,V>Value值序列化,keyvalue序列化后,将其数据溢写到本地文件里

 

 

     }

    //溢写具体是由SpilThread这个线程类来负责的

     protected class SpillThread extends Thread {

sortAndSpill();//需要spill时调用函数sortAndSpill,按照partitionkey做排序。默认使用的是快速排序QuickSort算法。使用的比较器,默认是WritableComparable。Hadoop的基本数据类型都是实现了这个WritableComparable接口。

 

      }

 

//用户在结束map处理后,已经没有数据再输出到缓冲区,但缓存中还有数据没有刷到磁盘上,需要将缓存中的数据   flush到磁盘上,这个动作就是由MapOutputBufferflush来完成。

     public void flush({

 

     }

}

_____________________________________________________________________________________

 

MapTask.mergeParts()方法源码骨架:

private void mergeParts() {

      final Path[] filename = new Path[numSpills];//记录了生成的Spill文件信息

  //循环得到每个Spill文件名字以及长度

      for(int i = 0; i < numSpills; i++) {

        filename[i] = mapOutputFile.getSpillFile(i);

        finalOutFileSize += rfs.getFileStatus(filename[i]).getLen();

 

         }

 //将多个Spill文件内容通过finalOut输出流写出到finalOutputFile文件,这个文件就是最后的结果文件

//此外,merge后的文件是一个已分区且已排好序的文件

        FSDataOutputStream finalOut=rfs.create(finalOutputFile, true, 4096);

        sortPhase.addPhases(partitions);

       Writer<K, V> writer = new Writer<K, V>(job, finalOut, keyClass, valClass, codec,

                               spilledRecordsCounter);

      //如果用户未指定有Combineg过程,直接执行merge合并

     //如果用户指定了有Combine过程,但是如果spill文件数量小于3,在merger阶段也不会发生combine

      if (combinerRunner == null || numSpills < minSpillsForCombine) {

            Merger.writeFile(kvIter, writer, reporter, job);

          } else {

//如果用户指定了Comibine,并spill文件数量大于3,则会发生Combine,然后进行merge

            combineCollector.setWriter(writer);

            combinerRunner.combine(kvIter, combineCollector);

          }

          sortPhase.startNextPhase();

         

        finalOut.close();

//Merge之后,把Spill文件删掉

        for(int i = 0; i < numSpills; i++) {

          rfs.delete(filename[i],true);

        }

      }

  

_____________________________________________________________________________________

 

ReduceTask介绍

ReduceTask用于初始化和执行Reduce任务。

初始化和启动reduce任务的方法入口是:run()方法

run()方法源码骨架:

void  run(final JobConf job, final TaskUmbilicalProtocol umbilical){

    //定义了reduce一共有三个阶段,分别是:copysortreduce阶段

      copyPhase = getProgress().addPhase("copy");

      sortPhase  = getProgress().addPhase("sort");

      reducePhase = getProgress().addPhase("reduce");

    

      TaskReporter reporter = startReporter(umbilical);//设置并启动report进程以便和TaskTracker进程通信,汇报reudce任务的执行情况

      Class combinerClass = conf.getCombinerClass();//获取CombinerClass   

 

      shuffleConsumerPlugin.init(shuffleContext);// shuffleConsumerPlugin.init()方法用于初始化copy阶段所依赖的运行环境,以及创建Merge管理器。copy阶段就是从各个Map任务服务器那里,去拷贝map的输出文件自己分区的数据到reduce任务节点上。这一过程,我们也称之为Fetch。当reduce节点拿到所有的输出文件之后,如果文件数量太多,(hadoop默认是的数量时10)会进行文件的合并,这个过程称为merge。并且在文件合并时,会按key进行排序。

      rIter = shuffleConsumerPlugin.run();//shuffleConsumerPlugin.run()方法就是执行fetch过程、merge过程、以及sort过程。并且,把最后的结果封装成:  [key1Iterable<>] [key2,Iterable<>]这样的形式,封装到rIter 对象里。

      mapOutputFilesOnDisk.clear();//reduce把各个map任务节点上的文件merger完后之后会生成新文件,则原来的那些就没用了,所以删掉

     sortPhase.complete();    //sortpahse完成,copyphase阶段是在先于sortphase完成

      setPhase(TaskStatus.Phase.REDUCE); //更新当前状态,进入reduce阶段。

      statusUpdate(umbilical);//通过rpc告知JobTracker当前reduce任务的执行阶段。

 

      Class keyClass = job.getMapOutputKeyClass();//获取输出key值类型

      Class valueClass = job.getMapOutputValueClass(); //获取reduce输出value类型

      RawComparator comparator = job.getOutputValueGroupingComparator();//获取比较器

     

     //这个是执行reduce 合并的方法入口

     runOldReducer(job, umbilical, reporter, rIter, comparator, keyClass, valueClass);

 

    done(umbilical, reporter);//以上三个阶段都完成后,reduce任务结束,做一些资源清理工作,并最后向JobTracker发送一次统计报告,然后结束Reporter通信线程。

 

 

}

 

执行Reduce任务的方法入口是:runOldReducer()方法 (M/R  1.x版本API)

_____________________________________________________________________________________

 

runOldReducer()方法源码骨架:

 

void runOldReducer(){

 

 Reducer<INKEY,INVALUE,OUTKEY,OUTVALUE> reducer = ReflectionUtils.newInstance(job.getReducerClass(), job);//得到用户定义的Reduce实现类

String finalName = getOutputName(getPartition());//得到分区数量,hadoop默认分区是1

RecordWriter<OUTKEY, OUTVALUE> finalOut =……//确定reduce结果输出器,Hadoop默认是LineRecordWriter,输出形式:reduce输出map.key  Tab   reduce输出map.value

reduce输出map.key  Tab   reduce输出map.value ……

 

 OutputCollector<OUTKEY,OUTVALUE> collector =  new OutputCollector<OUTKEY,OUTVALUE>() {

        public void collect(OUTKEY key, OUTVALUE value)

        {

          finalOut.write(key, value);//

         }

      };

ReduceValuesIterator<INKEY,INVALUE> values=rIter//rlter里的数据拿出来。

while (values.more()) {

         reducer.reduce(values.getKey(), values, collector, reporter);

         values.nextKey();

          }//循环执行reduce.reduce方法,并把每次reduce的输出map结果通过Collector,写出到结果文件里。直到所有遍历完所有的key值后,退出reduce方法。

         reducer.close();//reduce阶段结束

}

 

 

复制线程(MapOutputCopier)

    Map输出复制器ReduceCopier在其内部采用多线程的方式来从其它的TaskTracker上通过http协议请求的复制属于自己的Map任务输出结果。至于复制线程的个数可在配置文件mapred-site.xml中配置,对应的配置项:mapred.reduce.parallel.copies,为了提高reduce及整个Hadoop的效率,这个值应该设置和该作业的Map任务数差不多。每一个复制线程都从scheduledCopies 中获取一个任务来执行,在接受目标TaskTracker传过来的Map输出数据时,它会根据当前内存情况决定将该数据存放在村内还是磁盘。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

任错错

如果对您有帮助我很开心

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值