Mapper中map方法下context.write的流程与代码详解

本文的分析基于Hadoop 2.4.0版本

任何Map任务在Hadoop中都会被一个MapTask对象所详细描述,MapTask会最终调用其run方法来执行它对应的Map任务,需要执行任务就必须要有相关的输入输出信息,这些信息都包含在Map任务对应的Context对象中,Context通过RecordReader来获取输入数据,Map任务的输入文件保存在InputSplit中,这个InputSplit保存了文件的路径、范围和位置;通过RecordWriter来保存处理后的数据,在Context中还有个任务报告器TaskReporter,它不断向ApplicationMaster报告任务的执行进度

Mapper abstract class Context implements MapContext(接口)

Reducer的abstract class Context implements ReduceContext(接口)

MapContext和ReduceContext都是extends TaskInputOutputContext(接口)

interface TaskInputOutputContext的实现abstractclass TaskInputOutputContextImpl

TaskInputOutputContextImpl的write方法由abstractclass RecordWriter实现

Map和Reduce中的Context对象的write方法都是调用RecordWriter的write方法

其中RecordWriter有很多的实现类如classMapTask下的privateclass NewOutputCollector

private class NewDirectOutputCollector

NewOutputCollector举例:

public void write(K key, V value)throws IOException, InterruptedException {

      collector.collect(key, value,partitioner.getPartition(key,value, partitions));

}

其中collector的类型是MapOutputCollector(接口),最后实现类是static class MapOutputBuffer,是MapTask下的类,方法签名如下:

public synchronized void collect(K key, V value, final int partition ) throws IOException
在2.x中对1.x进行了改进,1.x中的int [] kvoffsets, int[] kvindices2.x中的IntBufferkvmeta给替换了

首选会调用publicvoid init(MapOutputCollector.Context context 方法进行初始化)

什么时候调用init的呢?

如果是使用runNewMapper的话,当getNumReduceTasks() != 0时,调用private MapOutputCollectorcreateSortingCollector(JobConfjob, TaskReporter reporter)方法内部调用的collector.init(context),其中在NewOutputCollector类的构造方法中调用了collector =createSortingCollector(job, reporter),getNumReduceTasks() == 0时,调用另一种直接的方式

使用runOldMapper的话,是直接当numReduceTasks > 0时调用createSortingCollector(job, reporter),当numReduceTasks =0时,调用另一种直接的方式

final float spillper = job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);

final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100);

key和value需要进行序列化的操作

keyClass = (Class)job.getMapOutputKeyClass();

valClass = (Class)job.getMapOutputValueClass();

serializationFactory = new SerializationFactory(job);

keySerializer = serializationFactory.getSerializer(keyClass);

keySerializer.open(bb);

valSerializer = serializationFactory.getSerializer(valClass);

valSerializer.open(bb);

初始化操作之后就执行public synchronized void collect(K key,V value, final int partition)方法

然后执行flush方法:将缓存中的数据刷到磁盘上

 

进行spill的时候,先是对缓存中的数据进行排序

sorter.sort(MapOutputBuffer. this, mstart, mend, reporter)


MapTask的运行主要代码:

MapTask的入口是run方法publicvoid run(final JobConf job,final TaskUmbilicalProtocol umbilical)

    if (isMapTask()) {

      // 如果没有reducer的任务,map阶段会支配所有的进程

      if (conf.getNumReduceTasks() == 0) {

        mapPhase = getProgress().addPhase("map", 1.0f);

      } else {

        // 如果有reducer的任务,全部被分为map阶段和sort阶段,各自占据一定的处理过程.

        mapPhase = getProgress().addPhase("map", 0.667f);

        sortPhase  =getProgress().addPhase("sort", 0.333f);

      }

}

//判断是否使用新的api

boolean useNewApi = job.getUseNewMapper()

Configuration类中的方法,默认是false

  public boolean getUseNewMapper() {

    return getBoolean("mapred.mapper.new-api",false);

  }

 

//useNewAPi是关于committer的设置不同,

initialize(job, getJobID(), reporter,useNewApi);

    if (useNewApi) {

      if (LOG.isDebugEnabled()) {

        LOG.debug("using new api for outputcommitter");

      }

      outputFormat =

        ReflectionUtils.newInstance(taskContext.getOutputFormatClass(), job);

      committer = outputFormat.getOutputCommitter(taskContext);

    } else {

      committer = conf.getOutputCommitter();

    }

 

 

做一些处理操作

    if (jobCleanup) {

      runJobCleanupTask(umbilical, reporter);

      return;

    }

    if (jobSetup) {

      runJobSetupTask(umbilical, reporter);

      return;

    }

    if (taskCleanup) {

      runTaskCleanupTask(umbilical, reporter);

      return;

    }

然后

    if (useNewApi) {

      runNewMapper(job, splitMetaInfo, umbilical, reporter);

    } else {

      runOldMapper(job, splitMetaInfo, umbilical, reporter);

    }

runNewMapper与runOldMapper的不同,对代码的不同包装,实现效果基本相同

你可能感兴趣的:(Hadoop,2.x源码分析)