MapTask运行通过执行.run方法:
1.生成TaskAttemptContextImpl实例,此实例中的Configuration就是job本身。
2.得到用户定义的Mapper实现类,也就是map函数的类。
3.得到InputFormat实现类。
4.得到当前task对应的InputSplit.
5.通过InputFormat,得到对应的RecordReader。
6.生成RecordWriter实例,
如果reduce个数为0,生成为MapTask.NewDirectOutputCollector
如果reduce个数不为0,但肯定是一个大于0的数,生成MapTask.NewOutputCollector
如果是有reduce的情况,在collector中会生成一个buffer的collector用来进行内存排序。
通过mapreduce.job.map.output.collector.class配置,默认为MapTask.MapOutputBuffer
在MapOutputBuffer中:
通过mapreduce.map.sort.spill.percent配置内存flush的比值,默认为0.8
spill的中文意思是溢出。
通过mapreduce.task.io.sort.mb配置内存bufer的大小,默认是100mb
通过mapreduce.task.index.cache.limit.bytes配置(还不知道是做什么的),默认为1024*1024
提示,这个配置是用来cache进行spill操作的index的大小。当spillindex达到此值的时候,
需要写入spillindex的文件。
通过map.sort.class配置排序实现类,默认为QuickSort,快速排序
通过mapreduce.map.output.compress.codec配置map的输出的压缩处理程序。
通过mapreduce.map.output.compress配置map输出是否启用压缩。默认为false.
MapOutputBuffer实例生成部分结束。
在生成MapTask.NewOutputCollector同时,会
检查是否用户有定义的Partitioner,默认是HashPartitioner。
如果生成的实例为MapTask.NewDirectOutputCollector,也就是没有Reduce的情况下,
不执行排序操作也不执行buffer的缓冲操作,直接写入到output的文件中。
通过OutputFormat的RecordWriter。
以下是mapper.run方法的执行代码:
publicvoidrun(Context context) throwsIOException, InterruptedException {
setup(context);
try{
while(context.nextKeyValue()) {
map(context.getCurrentKey(),context.getCurrentValue(), context);
}
}finally{
cleanup(context);
}
}
由上面的代码可以看出,map运行时,会执行一次setup函数,完成时会执行一次cleanup函数。
中间只要有值就会调用map函数。
其中run中传入的context生成由来:
if(job.getNumReduceTasks() == 0) {
output =
newNewDirectOutputCollector(taskContext,job, umbilical, reporter);
}else{
output = newNewOutputCollector(taskContext,job, umbilical, reporter);
}
MapContextImpl实例,包含input(RecordReader)与output,也就是上面提到的collector.
org.apache.hadoop.mapreduce.MapContext<INKEY,INVALUE, OUTKEY, OUTVALUE>
mapContext =
newMapContextImpl<INKEY, INVALUE, OUTKEY, OUTVALUE>(job,getTaskID(),
input, output,
committer,
reporter, split);
WrappedMapper.Context实例。包含MapContextImpl实例。
org.apache.hadoop.mapreduce.Mapper<INKEY,INVALUE,OUTKEY,OUTVALUE>.Context
mapperContext =
newWrappedMapper<INKEY, INVALUE, OUTKEY, OUTVALUE>().getMapContext(
mapContext);
接着看mapper.run中的context.nextKeyValue()函数:
调用WrappedMapper.Context.nextKeyValue()函数,-->
调用MapContextImpl.nextKeyValue函数,-->
调用RecordReader.nextKeyValue函数,RecordReader不在说明。
在map函数对过程处理完成后,会通过context.write写入分析的数据,
context.write(word,one);
看看此部分是如何执行的:
调用WrappedMapper.Context.write-->
调用MapContextImpl.write-->TaskInputOutputContextImpl.write-->
MapTask.NewOutputCollector.write/MapTask.NewDirectOutputCollector.write
MapTask.NewDirectOutputCollector.write:
这个里面没什么可以说的,直接写入到输出文件中。
NewDirectOutputCollector(MRJobConfigjobContext,
JobConf job,TaskUmbilicalProtocol umbilical, TaskReporter reporter)
throwsIOException, ClassNotFoundException, InterruptedException {
............................................
out= outputFormat.getRecordWriter(taskContext);
............................................
}
写入函数的定义
publicvoidwrite(K key, V value)
throwsIOException, InterruptedException {
reporter.progress();
longbytesOutPrev = getOutputBytes(fsStats);
直接写入文件。
out.write(key,value);
longbytesOutCurr = getOutputBytes(fsStats);
fileOutputByteCounter.increment(bytesOutCurr- bytesOutPrev);
mapOutputRecordCounter.increment(1);
}
重点来看看MapTask.NewOutputCollector.write这部分的实现:
通过Partitioner来生成reduce的partition值,调用MapOutputBuffer.collect函数。
也就是写入到buffer中。
publicvoidwrite(K key, V value) throwsIOException, InterruptedException {
collector.collect(key,value,
partitioner.getPartition(key,value, partitions));
}
MapOutputBuffer.collector:
publicsynchronized voidcollect(K key, V value, final intpartition
)throwsIOException {
reporter.progress();
检查传入的key的类型是否是job中MapOutputKeyClass的值
if(key.getClass() != keyClass){
thrownewIOException("Type mismatch in keyfrom map: expected "
+keyClass.getName()+ ", received "
+key.getClass().getName());
}
检查传入的value的类型是否是job中MapOutputValueClass的值。
if(value.getClass() != valClass){
thrownewIOException("Type mismatch in valuefrom map: expected "
+valClass.getName()+ ", received "
+value.getClass().getName());
}
检查partition是否在指定的范围内。
if(partition < 0 || partition >= partitions){
thrownewIOException("Illegal partition for" + key + "(" +
partition + ")");
}
检查sortSpillException的值是否为空,如果不为空,表示有spill错误,throwioexception
checkSpillException();
把可写入的buffer的剩余部分减去一个固定的值,并检查可用的buffer是否达到了sort与spill的值
默认是buffer的0.8的大小,如果buffer的0.8与METASIZE取于不等于0时,
得到的值可能会比0.8小METASIZE这么一点。
bufferRemaining-= METASIZE;
if(bufferRemaining<= 0) {
执行spill操作,这部分等下再进行分析
//start spill if the thread is not running and the soft limit has been
//reached
spillLock.lock();
try{
......................此部分代码先不看
} finally{
spillLock.unlock();
}
}
try{
第一次进入时,bufindex的值为0,以后的每一次是key.len+1+value.len+1的值增加。
//serialize key bytes into buffer
intkeystart = bufindex;
把key写入到此实例中的一个BlockingBuffer类型的属性bb中。这是一个buffer.
在写入时把bufferRemaining的值减去key.length的长度。这里面也会检查buffer是否够用
把key写入到kvbuffer中,同时把bufindex的值加上key.length。Kvbuffer就是具体的buffer.
在执行写入key/value时,首先是先把bufferRemaining的值减去key.length/value.length的长度。
同时检查此时