在Map任务源码分析(上)中,对MAP阶段的代码进行了学习,这篇文章文章将学习Map任务的SORT阶段。假设Reducer的数量不为0。则还须要进行SORT阶段。但从上面的学习中并未发现与MAP阶段运行完毕调用mapPhase.complete()相似的在SORT阶段运行完毕调用sortPhase.complete()的源码,那SORT阶段是在什么时候启动的?对于Map任务来说,有输入就有输出。输入由RecordReader负责。输出则由RecordWriter负责,当Reducer的数量不为0时,RecordWriter为NewOutputCollector(该类为MapTask的私有内部类),SORT阶段对map的输出进行处理,由此判断SORT阶段的工作是由NewOutputCollector完毕的。以下将通过分析NewOutputCollector的源码要验证这一判断是否成立。该类继承自RecordWriter。拥有的变量例如以下:

private final MapOutputCollector<K,V> collector;//负责实际的输出操作
private final org.apache.hadoop.mapreduce.Partitioner<K,V> partitioner;//对键空间进行分区
private final int partitions;//分区数量。与Reducer的数量同样

该类的构造函数例如以下:

collector = createSortingCollector(job, reporter);
partitions = jobContext.getNumReduceTasks();
if (partitions > 1) {
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 partitions - 1;
}
};
}

在该段代码中还是未发现与SORT阶段有关的不论什么信息,但却发现了Sorting,据此判断方法createSortingCollector具有最大的可能性。该方法的源码例如以下:

//依据mapreduce.job.map.output.collector.class的值构建MapOutputCollector
//在未指定该參数值的情况,返回MapOutputBuffer对象
MapOutputCollector<KEY, VALUE> collector= (MapOutputCollector<KEY, VALUE>)
ReflectionUtils.newInstance.
(job.getClass(JobContext.MAP_OUTPUT_COLLECTOR_CLASS_ATTR,
MapOutputBuffer.class, MapOutputCollector.class), job);
LOG.info("Map output collector class = " + collector.getClass().getName());
MapOutputCollector.Context context =new MapOutputCollector.Context(this, job, reporter);
//默认调用MapOutputBuffer的init方法
collector.init(context);
return collector;

经过上面的一系列分析可知,SORT阶段的工作由NewOutputCollector完毕,而NewOutputCollector又将SORT工作交给了MapOutputCollector。终于由该接口的实现类MapOutputBuffer完毕,该类做为MapTask的内部类占用了MapTask源码中超过一半的行数(MapTask行数为2000行,MapOutputBuffer约为1100行)。但从行数就能够得出该类的重要性。

MapOutputBuffer的init方法的第一部分代码依据设置构建缓存,代码已经加入了相关凝视:

//sanity checks
//当kvbuffer缓存达到该值时,溢出线程将缓存内容写入硬盘
final float spillper =job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);
//用于排序文件的缓存的大小,默觉得100MB
final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100);
//mapreduce.task.index.cache.limit.bytes,默认值为1024 * 1024(1M)
indexCacheMemoryLimit = job.getInt(JobContext.INDEX_CACHE_MEMORY_LIMIT, INDEX_CACHE_MEMORY_LIMIT_DEFAULT);
if (spillper > (float)1.0 || spillper <= (float)0.0) {
throw new IOException("Invalid \"" + JobContext.MAP_SORT_SPILL_PERCENT +
"\": " + spillper);
}
//sortmb的最大值为2047Mb(111 1111 1111),取sortmb的最低11位
//若大于2047Mb,以下的左移20位将导致溢出
if ((sortmb & 0x7FF) != sortmb) {
throw new IOException("Invalid \"" + JobContext.IO_SORT_MB + "\": " + sortmb);
}
//默认使用高速排序
sorter = ReflectionUtils.newInstance(job.getClass("map.sort.class",
QuickSort.class, IndexedSorter.class), job);
// buffers and accounting
//sortbm*(2的20次方)。将sortbm转换为字节数(1024*1024,2的十次方乘以2的十次方)
int maxMemUsage = sortmb << 20;
//METASIZE=16,maxMemUsage=sortmb << 20
maxMemUsage -= maxMemUsage % METASIZE;
kvbuffer = new byte[maxMemUsage];
bufvoid = kvbuffer.length;
kvmeta = ByteBuffer.wrap(kvbuffer).order(ByteOrder.nativeOrder()).asIntBuffer();
setEquator(0);
//equator:标记元数据或者序列化数据的起源
//bufstart:标记溢出的起始位置,bufend:标记可收集收据的起始位置
//bufindex:标记已收集数据的结束位置。 所有初始化为0
bufstart = bufend = bufindex = equator;
//kvstart:标记溢出元数据的起源,kvend:标记溢出元数据的结束位置
//kvindex:标记全然序列化的记录的结束位置
kvstart = kvend = kvindex;
maxRec = kvmeta.capacity() / NMETA;
softLimit = (int)(kvbuffer.length * spillper);
bufferRemaining = softLimit;

MapOutputBuffer的init方法的第二部分启动SpillThread线程,该线程用于完毕SORT阶段的工作,并负责溢出缓存中的数据。

在该线程中的run方法中调用了sortAndSpill方法,由方法名就能够得知该方法负责map输出的排序和溢出工作。排序部分的源码例如以下:

final int mstart = kvend / NMETA;
// kvend is a valid record
final int mend = 1 + (kvstart >= kvend? kvstart
: kvmeta.capacity() + kvstart) / NMETA;
//对指定范围的数据进行排序。默认使用的QuickSort
sorter.sort(MapOutputBuffer.this, mstart, mend, reporter);

对数据排完序后就须要将已排序数据写出到文件里。源码例如以下:

int spindex = mstart;
//记录索引的startOffset、rawLength和partLength
final IndexRecord rec = new IndexRecord();
//封装value的字节表示的内部类
final InMemValBytes value = new InMemValBytes();
for (int i = 0; i < partitions; ++i) {
//负责将map的输出写入中间文件
IFile.Writer<K, V> writer = null;
try {
long segmentStart = out.getPos();
writer = new Writer<K, V>(job, out, keyClass, valClass, codec,spilledRecordsCounter);
if (combinerRunner == null) {
// spill directly
DataInputBuffer key = new DataInputBuffer();
while (spindex < mend &&
kvmeta.get(offsetFor(spindex % maxRec) + PARTITION) == i) {
final int kvoff = offsetFor(spindex % maxRec);
int keystart = kvmeta.get(kvoff + KEYSTART);
int valstart = kvmeta.get(kvoff + VALSTART);
key.reset(kvbuffer, keystart, valstart - keystart);
getVBytesForOffset(kvoff, value);
writer.append(key, value);
++spindex;
}
} else {
int spstart = spindex;
while (spindex < mend
&&kvmeta.get(offsetFor(spindex % maxRec)+ PARTITION) == i) {
++spindex;
}
// Note: we would like to avoid the combiner if we've fewer
// than some threshold of records for a partition
if (spstart != spindex) {
combineCollector.setWriter(writer);
RawKeyValueIterator kvIter =new MRResultIterator(spstart, spindex);
combinerRunner.combine(kvIter, combineCollector);
}
}
// close the writer
writer.close();
// record offsets
rec.startOffset = segmentStart;
rec.rawLength = writer.getRawLength();
rec.partLength = writer.getCompressedLength();
spillRec.putIndex(rec, i);
writer = null;
} finally {
if (null != writer) writer.close();
}
}

当保存索引的缓存超过限制时,就将索引保存到文件里,源码例如以下:

if (totalIndexCacheMemory >= indexCacheMemoryLimit) {
// create spill index file
//MAP_OUTPUT_INDEX_RECORD_LENGTH值为24,表示索引文件里每条记录的大小
Path indexFilename =mapOutputFile.getSpillIndexFileForWrite(numSpills, partitions
* MAP_OUTPUT_INDEX_RECORD_LENGTH);
spillRec.writeToFile(indexFilename, job);
} else {
indexCacheList.add(spillRec);
totalIndexCacheMemory +=spillRec.size() * MAP_OUTPUT_INDEX_RECORD_LENGTH;
}

综合上面的分析可知,当在map方法中运行context.write时,将先数据写入到缓存中,当缓存中的数据达到预先设置的阈值时由后台SpillThread线程负责数据排序并将数据溢出到map任务的中间输出文件里。

Hadoop-2.4.1学习之Map任务源代码分析(下)的更多相关文章

  1. Hadoop学习:Map/Reduce初探与小Demo实现

    原文地址:https://blog.csdn.net/liyong199012/article/details/25423221 一.    概念知识介绍 Hadoop MapReduce是一个用于处 ...

  2. 学习Road map Part 04 自动驾驶、SLAM、ROS、树莓派

    学习Road map Part 04 自动驾驶.SLAM.ROS.树莓派

  3. Hadoop源代码分析

    http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

  4. java集合框架07——Map架构与源代码分析

    前几节我们对Collection以及Collection中的List部分进行了分析,Collection中还有个Set,因为Set是基于Map实现的,所以这里我们先分析Map,后面章节再继续学习Set ...

  5. Hadoop基础--统计商家id的标签数案例分析

    Hadoop基础--统计商家id的标签数案例分析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.项目需求 将“temptags.txt”中的数据进行分析,统计出商家id的评论标 ...

  6. Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线

    Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...

  7. Hadoop源代码分析(完整版)

    Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...

  8. 在HDInsight中从Hadoop的兼容BLOB存储查询大数据的分析

    在HDInsight中从Hadoop的兼容BLOB存储查询大数据的分析 低成本的Blob存储是一个强大的.通用的Hadoop兼容Azure存储解决方式无缝集成HDInsight.通过Hadoop分布式 ...

  9. Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation

    原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

随机推荐

  1. Cocoapod错误 - Xcode6.4

    当我从Xcode 6.3切换到Xcode6.4的时候,因为我是mac上安装了两个不同的版本,现在把Xcode 6.3卸掉了. 现在再次运行pod install命令的时候,提示如下错误:   Upda ...

  2. 老陈---谈Delphi中SSL协议的应用[转]

    摘要:本文主要介绍如何在Delphi中使用SSL协议.一共分为七个部分:(1)SSL协议是什么?(2)Delphi中如何使用SSL协议?(3)SSL客户端编程实例.(4)SSL服务端编程实例.(5)S ...

  3. WebService开发应用

    WebService是运行于服务端(一般放在信息服务器上的)让客户端来调用的. 以下开发两个简单的实例 1.自己开发服务端自己调用(vs2010) 1).菜单:“新建-项目”,在打开的窗体中选择,如下 ...

  4. [QuickX]xcode运行Quick-cocos2d-x项目时自动更新lua资源文件

    1.项目设置 build settings ->build options ->Scan all source files and Includes = YES 2.加入script (1 ...

  5. 人一生必看的100部电影(全球最佳电影排名榜TOP250)

    人一生必看的100部电影(全球最佳电影排名榜TOP250) 人的一生能看多少部电影?假设我们每周都看一部,从10岁看到80岁将会看3640部.但是我们也不可能喜欢这全部的电影.大多数的可能,我们会根据 ...

  6. JavaScript元素的创建、添加、删除

    <script> var x=document.getElementById("p2"); var obj=document.createElement("p ...

  7. 30个最常用css选择器解析

    转自:http://www.cnblogs.com/yiyuanke/archive/2011/10/22/CSS.html 你也许已经掌握了id.class.后台选择器这些基本的css选择器.但这远 ...

  8. Modifying the ASP.NET Request Queue Limit

    Modifying the ASP.NET Request Queue Limit When ASP.NET is queried, the request for service is carrie ...

  9. HTTP Authorization

    谨以此文献给那些需要实现HTTP AUTH的“程序猿”们. 关于HTTP AUTH的文档不多. RFC在 http://www.ietf.org/rfc/rfc2617.txt wiki在 http: ...

  10. 全新ASP框架——IISNODE介绍

    Asp是一门经典的动态网页编程语言,通常使用vbscript或者Jscript脚本来实现.一个好的框架,可以帮助您更加快速地使用Asp来完成您的网站开发任务.而Asp框架的终结者——IISNODE框架 ...