Hadoop-2.4.1学习之Map任务源代码分析(下)
在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任务源代码分析(下)的更多相关文章
- Hadoop学习:Map/Reduce初探与小Demo实现
原文地址:https://blog.csdn.net/liyong199012/article/details/25423221 一. 概念知识介绍 Hadoop MapReduce是一个用于处 ...
- 学习Road map Part 04 自动驾驶、SLAM、ROS、树莓派
学习Road map Part 04 自动驾驶.SLAM.ROS.树莓派
- Hadoop源代码分析
http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...
- java集合框架07——Map架构与源代码分析
前几节我们对Collection以及Collection中的List部分进行了分析,Collection中还有个Set,因为Set是基于Map实现的,所以这里我们先分析Map,后面章节再继续学习Set ...
- Hadoop基础--统计商家id的标签数案例分析
Hadoop基础--统计商家id的标签数案例分析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.项目需求 将“temptags.txt”中的数据进行分析,统计出商家id的评论标 ...
- Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线
Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...
- Hadoop源代码分析(完整版)
Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...
- 在HDInsight中从Hadoop的兼容BLOB存储查询大数据的分析
在HDInsight中从Hadoop的兼容BLOB存储查询大数据的分析 低成本的Blob存储是一个强大的.通用的Hadoop兼容Azure存储解决方式无缝集成HDInsight.通过Hadoop分布式 ...
- Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation
原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...
随机推荐
- UESTC 1425 Another LCIS
也是一个求最长连续单调区间的问题,不同于HDU 3308LCIS的是,单点更新变成了区间成段增加,没关系同样的方法可破之.由于是成段更新,所以比更新区间小的区间是最大连续区间长度是不变的,所以更新su ...
- 有关TCP和UDP 粘包 消息保护边界
http://www.cnblogs.com/lancidie/archive/2013/10/28/3392428.html 在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的.因 ...
- SPRING IN ACTION 第4版笔记-第三章ADVANCING WIRING-004-消除bean自动装配的歧义@Primary
一. 假设有如下三个类实现同一个接口,则自动装配时会产生歧义 @Component public class Cake implements Dessert { ... } @Component pu ...
- USB C和USB 3.1傻傻分不清?这篇文章可以帮你
USB Type-C接口以及USB 3.1标准的到来,理应为消费者提供更多便利.然而就目前来看,似乎这些新标准非但没有为消费者提供了更好的使用体验,反而带来了诸多隐患.Google的工程师Benson ...
- 猜测:信号槽的本质是使用Windows的自定义消息来实现的
在不断执行: void MyTool::DeleteAllFiles(){ for (i = 0; i <= n - 1; i++) { // do something }}在for循环没有执行 ...
- python3使用requests爬取新浪热门微博
微博登录的实现代码来源:https://gist.github.com/mrluanma/3621775 相关环境 使用的python3.4,发现配置好环境后可以直接使用pip easy_instal ...
- poj1286
等价类计数问题,我们就先构造置换群 显然置换分为两种类型,旋转和翻折 先考虑旋转,每旋转i格子,这个置换的循环数为gcd(i,n); (1<=i<=n) 为什么是这个范围,下篇报告再说 翻 ...
- The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.
The server is temporarily unable to service your request due to maintenance downtime or capacity pro ...
- EF框架搭建
EF框架搭配lambda表达式使用起来非常高效便捷,有两种方法使用EF框架: 一是.添加“ADO.NET Entity Data Model”项,绑定配置数据库链接,勾选表和存储过程等,自动生成实体: ...
- EntityFramework在不同数据库下的配置
1.SQLServer: <connectionStrings> <add name="EntityDesignEntities" connectionStrin ...