本文的分析基于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[] kvindices被2.x中的IntBufferkvmeta给替换了
首选会调用publicvoid init(MapOutputCollector.Context context 方法进行初始化)
什么时候调用init的呢?
如果是使用runNewMapper的话,当getNumReduceTasks() != 0时,调用private
使用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
valClass = (Class
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的不同,对代码的不同包装,实现效果基本相同