MapTask类继承于Task类,它最主要的方法就是run(),用来执行这个Map任务。

run()首先设置一个TaskReporter并启动,然后调用JobConf的getUseNewAPI()判断是否使用New API,使用New API的设置在前面[Hadoop源码解读](三)MapReduce篇之Job类 讲到过,再调用Task继承来的initialize()方法初始化这个task,接着根据需要执行runJobCleanupTask()、runJobSetupTask()、runTaskCleanupTask()或相应的Mapper,执行Mapper时根据情况使用不同版本的MapReduce,这个版本是设置参数决定的。

   @Override
   public void run(final JobConf job, final TaskUmbilicalProtocol umbilical)
     throws IOException, ClassNotFoundException, InterruptedException {
     this.umbilical = umbilical;

     // start thread that will handle communication with parent
     TaskReporter reporter = new TaskReporter(getProgress(), umbilical,
         jvmContext);
     reporter.startCommunicationThread();
     boolean useNewApi = job.getUseNewMapper();  //是由JobConf来的,而New API 的JobContext包含一个JobConf,Job类有
     //setUseNewAPI()方法,当Job.submit()时使用它,这样,waitForCompletion()就用submit()设置了使用New API,而此时就使用它。
     initialize(job, getJobID(), reporter, useNewApi);//一个Task的初始化工作,包括jobContext,taskContext,输出路径等,
                                  //使用的是Task.initialize()方法

     // check if it is a cleanupJobTask
     if (jobCleanup) {
       runJobCleanupTask(umbilical, reporter);
       return;
     }
     if (jobSetup) {
       runJobSetupTask(umbilical, reporter);
       return;
     }
     if (taskCleanup) {
       runTaskCleanupTask(umbilical, reporter);
       return;
     }

     if (useNewApi) {//根据情况使用不同的MapReduce版本执行Mapper
       runNewMapper(job, splitMetaInfo, umbilical, reporter);
     } else {
       runOldMapper(job, splitMetaInfo, umbilical, reporter);
     }
     done(umbilical, reporter);
   }

runNewMapper对应new API的MapReduce,而runOldMapper对应旧API。

runNewMapper首先创建TaskAttemptContext对象,Mapper对象,InputFormat对象,InputSplit,RecordReader;然后根据是否有Reduce task来创建不同的输出收集器NewDirectOutputCollector[没有reducer]或NewOutputCollector[有reducer],接下来调用input.initialize()初始化RecordReader,主要是为输入做准备,设置RecordReader,输入路径等等。然后到最主要的部分:mapper.run()。这个方法就是调用前面[Hadoop源码解读](二)MapReduce篇之Mapper类讲到的Mapper.class的run()方法。然后就是一条一条的读取K/V对,这样就衔接起来了。

  @SuppressWarnings("unchecked")
   private <INKEY,INVALUE,OUTKEY,OUTVALUE>
   void runNewMapper(final JobConf job,
                     final TaskSplitIndex splitIndex,
                     final TaskUmbilicalProtocol umbilical,
                     TaskReporter reporter
                     ) throws IOException, ClassNotFoundException,
                              InterruptedException {
     // make a task context so we can get the classes
     org.apache.hadoop.mapreduce.TaskAttemptContext taskContext =
       new org.apache.hadoop.mapreduce.TaskAttemptContext(job, getTaskID());
     // make a mapper
     org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE> mapper =
       (org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>)
         ReflectionUtils.newInstance(taskContext.getMapperClass(), job);
     // make the input format
     org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE> inputFormat =
       (org.apache.hadoop.mapreduce.InputFormat<INKEY,INVALUE>)
         ReflectionUtils.newInstance(taskContext.getInputFormatClass(), job);
     // rebuild the input split
     org.apache.hadoop.mapreduce.InputSplit split = null;
     split = getSplitDetails(new Path(splitIndex.getSplitLocation()),
         splitIndex.getStartOffset());

     org.apache.hadoop.mapreduce.RecordReader<INKEY,INVALUE> input =
       new NewTrackingRecordReader<INKEY,INVALUE>
           (split, inputFormat, reporter, job, taskContext);

     job.setBoolean("mapred.skip.on", isSkipping());
     org.apache.hadoop.mapreduce.RecordWriter output = null;
     org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>.Context
          mapperContext = null;
     try {
       Constructor<org.apache.hadoop.mapreduce.Mapper.Context> contextConstructor =
         org.apache.hadoop.mapreduce.Mapper.Context.class.getConstructor
         (new Class[]{org.apache.hadoop.mapreduce.Mapper.class,
                      Configuration.class,
                      org.apache.hadoop.mapreduce.TaskAttemptID.class,
                      org.apache.hadoop.mapreduce.RecordReader.class,
                      org.apache.hadoop.mapreduce.RecordWriter.class,
                      org.apache.hadoop.mapreduce.OutputCommitter.class,  //
                      org.apache.hadoop.mapreduce.StatusReporter.class,
                      org.apache.hadoop.mapreduce.InputSplit.class});

       // get an output object
       if (job.getNumReduceTasks() == 0) {
          output =
            new NewDirectOutputCollector(taskContext, job, umbilical, reporter);
       } else {
         output = new NewOutputCollector(taskContext, job, umbilical, reporter);
       }

       mapperContext = contextConstructor.newInstance(mapper, job, getTaskID(),
                                                      input, output, committer,
                                                      reporter, split);

       input.initialize(split, mapperContext);
       mapper.run(mapperContext);
       input.close();
       output.close(mapperContext);
     } catch (NoSuchMethodException e) {
       throw new IOException("Can't find Context constructor", e);
     } catch (InstantiationException e) {
       throw new IOException("Can't create Context", e);
     } catch (InvocationTargetException e) {
       throw new IOException("Can't invoke Context constructor", e);
     } catch (IllegalAccessException e) {
       throw new IOException("Can't invoke Context constructor", e);
     }
   }

至于运行哪个Mapper类,一般是我们用job.setMapperClass(SelectGradeMapper.class)设置的,那设置后是怎样获取的,或者默认值是什么,且看下面的追溯。

MapTask.runNewMapper()

=>       (TaskAttemptContext)taskContext.getMapperClass();     //runNewMapper生成mapper时用到。

=>       JobContext.getMapperClass()

=>       JobConf.getClass(MAP_CLASS_ATTR,Mapper.class)

=>       Configuration.getClass(name,default)

根据上面一层的调用关系,找到了默认值是Mapper.class,它的获取过程也一目了然。

再仔细看看Configuration.getClass()

   public Class<?> getClass(String name, Class<?> defaultValue) {
     String valueString = get(name);
     if (valueString == null)
       return defaultValue;
     try {
       return getClassByName(valueString);
     } catch (ClassNotFoundException e) {
       throw new RuntimeException(e);
     }
   }

它首先看是否设置了某个属性,如果设置了,就调用getClassByName获取这个属性对应的类[加载之],否则就返回默认值。
Mapper执行完后,关闭RecordReader和OutputCollector等资源就完事了。

另外我们把关注点放在上面的runNewMapper()中的mapper.run(mapperContext);前面对Mapper.class提到,这个mapperContext会被用于读取输入分片的K/V对和写出输出结果的K/V对。而由

      mapperContext = contextConstructor.newInstance(mapper, job, getTaskID(),
                                                     input, output, committer,
                                                     reporter, split);

可以看出,这个Context是由我们设置的mapper,RecordReader等进行配置的。

Mapper中的map方法不断使用context.write(K,V)进行输出,我们看这个函数是怎么进行的,先看Context类的层次关系:

write()方法是由TaskInputOutputContext来的:

  public void write(KEYOUT key, VALUEOUT value
                    ) throws IOException, InterruptedException {
    output.write(key, value);
  }

它调用了RecordWriter.write(),RecordWriter是一个抽象类,主要是规定了write方法。

public abstract class RecordWriter<K, V> {
  public abstract void write(K key, V value
                             ) throws IOException, InterruptedException;

  public abstract void close(TaskAttemptContext context
                             ) throws IOException, InterruptedException;
}

然后看RecordWriter的一个实现NewOutputCollector,它是MapTask的内部类:

   private class NewOutputCollector<K,V>
     extends org.apache.hadoop.mapreduce.RecordWriter<K,V> {
     private final MapOutputCollector<K,V> collector;
     private final org.apache.hadoop.mapreduce.Partitioner<K,V> partitioner;
     private final int partitions;

     @SuppressWarnings("unchecked")
     NewOutputCollector(org.apache.hadoop.mapreduce.JobContext jobContext,
                        JobConf job,
                        TaskUmbilicalProtocol umbilical,
                        TaskReporter reporter
                        ) throws IOException, ClassNotFoundException {
       collector = new MapOutputBuffer<K,V>(umbilical, job, reporter);
       partitions = jobContext.getNumReduceTasks();
       if (partitions > 0) {
         partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>)
           ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
       } else {
         partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() {
           @Override
           public int getPartition(K key, V value, int numPartitions) {
             return -1;
           }
         };
       }
     }

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

     @Override
     public void close(TaskAttemptContext context
                       ) throws IOException,InterruptedException {
       try {
         collector.flush();
       } catch (ClassNotFoundException cnf) {
         throw new IOException("can't find class ", cnf);
       }
       collector.close();
     }
   }

从它的write()方法,我们从context.write(K,V)追溯到了collector.collect(K,V,partition),注意到输出需要一个Partitioner的getPartitioner()来提供当前K/V对的所属分区,因为要对K/V对分区,不同分区输出到不同Reducer,Partitioner默认是HashPartitioner,可设置,Reduce task数量决定Partition数量;

我们可以从NewOutputCollector看出NewOutputCollector就是MapOutputBuffer的封装。MapoutputBuffer是旧API中就存在了的,它很复杂,但很关键,暂且放着先,反正就是收集输出K/V对的。它实现了MapperOutputCollector接口:

  interface MapOutputCollector<K, V> {
    public void collect(K key, V value, int partition
                        ) throws IOException, InterruptedException;
    public void close() throws IOException, InterruptedException;
    public void flush() throws IOException, InterruptedException,
                               ClassNotFoundException;
  }

这个接口告诉我们,收集器必须实现collect,close,flush方法。

看一个简单的:NewDirectOutputCollector,它在没有reduce task的时候使用,主要是从InputFormat中获取OutputFormat的RecordWriter,然后就可以用这个RecordWriter的write()方法来写出,这就与我们设置的输出格式对应起来了。

   private class NewDirectOutputCollector<K,V>
   extends org.apache.hadoop.mapreduce.RecordWriter<K,V> {
     private final org.apache.hadoop.mapreduce.RecordWriter out;

     private final TaskReporter reporter;

     private final Counters.Counter mapOutputRecordCounter;
     private final Counters.Counter fileOutputByteCounter;
     private final Statistics fsStats;

     @SuppressWarnings("unchecked")
     NewDirectOutputCollector(org.apache.hadoop.mapreduce.JobContext jobContext,
         JobConf job, TaskUmbilicalProtocol umbilical, TaskReporter reporter)
     throws IOException, ClassNotFoundException, InterruptedException {
       this.reporter = reporter;
       Statistics matchedStats = null;
       if (outputFormat instanceof org.apache.hadoop.mapreduce.lib.output.FileOutputFormat) {
         //outputFormat是Task来的,内部类访问外部类成员变量
         matchedStats = getFsStatistics(org.apache.hadoop.mapreduce.lib.output.FileOutputFormat
             .getOutputPath(jobContext), job);
       }
       fsStats = matchedStats;
       mapOutputRecordCounter =
         reporter.getCounter(MAP_OUTPUT_RECORDS);
       fileOutputByteCounter = reporter
           .getCounter(org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.Counter.BYTES_WRITTEN);

       long bytesOutPrev = getOutputBytes(fsStats);
       out = outputFormat.getRecordWriter(taskContext); //主要是这句,获取设置的OutputputFormat里的RecordWriter
       long bytesOutCurr = getOutputBytes(fsStats);
       fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);
     }

     @Override
     @SuppressWarnings("unchecked")
     public void write(K key, V value)
     throws IOException, InterruptedException {
       reporter.progress();  //报告一下进度
       long bytesOutPrev = getOutputBytes(fsStats);
       out.write(key, value);//使用out收集一条记录,out是设置的OutputFormat来的。
       long bytesOutCurr = getOutputBytes(fsStats);
       fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);  //更新输出字节数
       mapOutputRecordCounter.increment(1);      //更新输出K/V对数量
     }

     @Override
     public void close(TaskAttemptContext context)
     throws IOException,InterruptedException {
       reporter.progress();
       if (out != null) {
         long bytesOutPrev = getOutputBytes(fsStats);
         out.close(context);
         long bytesOutCurr = getOutputBytes(fsStats);
         fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);
       }
     }

     private long getOutputBytes(Statistics stats) {
       return stats == null ? 0 : stats.getBytesWritten();
     }
   }

另外还有一些以runOldMapper()为主导的旧MapReduce API那套,就不进行讨论了。

from:  http://blog.csdn.net/posa88/article/details/7956767

[Hadoop源码解读](六)MapReduce篇之MapTask类的更多相关文章

  1. Hadoop源码解读系列目录

    Hadoop源码解读系列 1.hadoop源码|common模块-configuration详解2.hadoop源码|core模块-序列化与压缩详解3.hadoop源码|core模块-远程调用与NIO ...

  2. Hadoop2源码分析-MapReduce篇

    1.概述 前面我们已经对Hadoop有了一个初步认识,接下来我们开始学习Hadoop的一些核心的功能,其中包含mapreduce,fs,hdfs,ipc,io,yarn,今天为大家分享的是mapred ...

  3. [Hadoop源码解读](一)MapReduce篇之InputFormat

    平时我们写MapReduce程序的时候,在设置输入格式的时候,总会调用形如job.setInputFormatClass(KeyValueTextInputFormat.class);来保证输入文件按 ...

  4. [Hadoop源码解读](五)MapReduce篇之Writable相关类

    前面讲了InputFormat,就顺便讲一下Writable的东西吧,本来应当是放在HDFS中的. 当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节 ...

  5. spring beans源码解读之--总结篇

    spring beans下面有如下源文件包: org.springframework.beans, 包含了操作java bean的接口和类.org.springframework.beans.anno ...

  6. Vue.js 源码分析(六) 基础篇 计算属性 computed 属性详解

    模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的.在模板中放入太多的逻辑会让模板过重且难以维护,比如: <div id="example">{{ messag ...

  7. [Hadoop源码解读](二)MapReduce篇之Mapper类

    前面在讲InputFormat的时候,讲到了Mapper类是如何利用RecordReader来读取InputSplit中的K-V对的. 这一篇里,开始对Mapper.class的子类进行解读. 先回忆 ...

  8. [Hadoop源码解读](三)MapReduce篇之Job类

    下面,我们只涉及MapReduce 1,而不涉及YARN. 当我们在写MapReduce程序的时候,通常,在main函数里,我们会像下面这样做.建立一个Job对象,设置它的JobName,然后配置输入 ...

  9. [Hadoop源码解读](四)MapReduce篇之Counter相关类

    当我们定义一个Counter时,我们首先要定义一枚举类型: public static enum MY_COUNTER{ CORRUPTED_DATA_COUNTER, NORMAL_DATA_COU ...

随机推荐

  1. IOS-7步学会用代理

    代理:又叫委托 自己不能去办的事委托给别人去办 之前学过的 UIAlertView UITextField都是使用了代理 反向传值代理 代理Block 写代理的步骤 需要帮忙的人(请求帮代饭的人) 1 ...

  2. bat里如何用相对路径

    在bat中直接使用绝对路径没有问题,但是文件传到其他地方时,绝对路径会发生改变,因此想通过使用相对路径来解决. 可以通过在bat获取当前bat所在的目录,然后cd 该目录来解决该问题 在bat前面增加 ...

  3. OpenJudge / Poj 1044 Date bugs C++

    链接地址: Poj:http://poj.org/problem?id=1044 OpenJudge:http://bailian.openjudge.cn/practice/1044/ 题目: 总时 ...

  4. Apache Shiro入门实例

    Shiro是一个强大灵活的开源安全框架,提供身份验证.授权.会话管理.密码体系. 1.先创建一个Maven项目 2.配置pom <project xmlns="http://maven ...

  5. VS2010类似Eclipse文件查找功能-定位到

    快捷键:Ctrl + , 打开定位到窗口,可以在文件或类文件中查找内容.

  6. 跟我一起学Vim补全神级插件--YouCompleteMe

    最近重拾Vim,编译部署来补全插件YCM,这个插件的补全效果和在写C代码的时候的自动提示错误信息等还是十分棒的,写点心得下来,也算给自己做个备忘. 快速安装: 首先参考我的.vimrc配置,用Vund ...

  7. linux svn authorization failed错误

    authorization failed错误主要是conf/auth文件配置错误,可以参考如下配置: [aliases] # joe = /C=XZ/ST=Dessert/L=Snake City/O ...

  8. Linux作业控制

    在Linux中,利用Shell的作业控制是比较常用的操作,在这一节中我们将探究作业控制相关的操作.为了方便我们查看区分不同的进行,我们编写如下程序,其功能是每间隔2秒输出一次自己的编号. /* ** ...

  9. OpenSUSE共享网络

    因为想要使用Arduino Ethernet扩展版,想要搭建一个局域网供其使用有线,无奈路由器离我太远.遂有本文. 实验器材: 装有OpenSUSE.有线网卡.无线网卡的笔记本. 路由器一台. 实验步 ...

  10. SQL中约束和触发器的停用与启用

    如何对SQL中的约束和触发器进行停用与启用,如果有外键约束则相关联表都要进行相应操作. ALTER TABLE TableName CHECK CONSTRAIT ALL --检查约束 ALTER T ...