在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。拥有的变量例如以下:

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

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

  1. collector = createSortingCollector(job, reporter);
  2. partitions = jobContext.getNumReduceTasks();
  3. if (partitions > 1) {
  4. partitioner = (org.apache.hadoop.mapreduce.Partitioner<K,V>)
  5. ReflectionUtils.newInstance(jobContext.getPartitionerClass(), job);
  6. } else {
  7. partitioner = new org.apache.hadoop.mapreduce.Partitioner<K,V>() {
  8. @Override
  9. public int getPartition(K key, V value, int numPartitions) {
  10. return partitions - 1;
  11. }
  12. };
  13. }

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

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

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

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

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

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

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

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

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

  1. int spindex = mstart;
  2. //记录索引的startOffset、rawLength和partLength
  3. final IndexRecord rec = new IndexRecord();
  4. //封装value的字节表示的内部类
  5. final InMemValBytes value = new InMemValBytes();
  6. for (int i = 0; i < partitions; ++i) {
  7. //负责将map的输出写入中间文件
  8. IFile.Writer<K, V> writer = null;
  9. try {
  10. long segmentStart = out.getPos();
  11. writer = new Writer<K, V>(job, out, keyClass, valClass, codec,spilledRecordsCounter);
  12. if (combinerRunner == null) {
  13. // spill directly
  14. DataInputBuffer key = new DataInputBuffer();
  15. while (spindex < mend &&
  16. kvmeta.get(offsetFor(spindex % maxRec) + PARTITION) == i) {
  17. final int kvoff = offsetFor(spindex % maxRec);
  18. int keystart = kvmeta.get(kvoff + KEYSTART);
  19. int valstart = kvmeta.get(kvoff + VALSTART);
  20. key.reset(kvbuffer, keystart, valstart - keystart);
  21. getVBytesForOffset(kvoff, value);
  22. writer.append(key, value);
  23. ++spindex;
  24. }
  25. } else {
  26. int spstart = spindex;
  27. while (spindex < mend
  28. &&kvmeta.get(offsetFor(spindex % maxRec)+ PARTITION) == i) {
  29. ++spindex;
  30. }
  31. // Note: we would like to avoid the combiner if we've fewer
  32. // than some threshold of records for a partition
  33. if (spstart != spindex) {
  34. combineCollector.setWriter(writer);
  35. RawKeyValueIterator kvIter =new MRResultIterator(spstart, spindex);
  36. combinerRunner.combine(kvIter, combineCollector);
  37. }
  38. }
  39. // close the writer
  40. writer.close();
  41. // record offsets
  42. rec.startOffset = segmentStart;
  43. rec.rawLength = writer.getRawLength();
  44. rec.partLength = writer.getCompressedLength();
  45. spillRec.putIndex(rec, i);
  46. writer = null;
  47. } finally {
  48. if (null != writer) writer.close();
  49. }
  50. }

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

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

综合上面的分析可知,当在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. cocos2d-x学习知识点记录

    环境搭建 http://4137613.blog.51cto.com/4127613/751149 Cocos2d-x初探,HelloWorld解读 http://www.cnblogs.com/Ke ...

  2. SQL 各种连接:内连接,外连接(左外,右外,完全外)

    在讲述之前,假设有如下两个表EMP, DEPT, 并且他们数据如下:

  3. python学习之--自定义函数:

    Python之--自定义函数: 在Python中,定义一个函数要使用def语句,依次写出函数名.括号.括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回. 以下自定义 ...

  4. CPU,MPU,MCU,SOC,SOPC联系与差别

    转自CPU,MPU,MCU,SOC,SOPC联系与差别 1.CPU(Central Processing Unit),是一台计算机的运算核心和控制核心.CPU由运算器.控制器和寄存器及实现它们之间联系 ...

  5. Android 基础知识点(持续更新)

    一.AndroidManifest 每一个安卓工程都有AndroidManifest.xml的配置文件,在所有项目中该名称都不会变.该文件是Android工程的一个全局配置文件,所有在Android中 ...

  6. NAND Flash的基本操作——读、写、擦除

    基本操作 这里将会简要介绍一下NAND Flash的基本操作在NAND Flash内部是如何进行的,基本操作包括:读.写和擦除.   读:     当我们读取一个存储单元中的数据时(如图2.4),是使 ...

  7. 【转】Xcode7.1环境下上架iOS App到AppStore 流程 (Part 三)

    原文网址:http://www.cnblogs.com/ChinaKingKong/p/4964745.html 前言部分 part三 部分主要讲解 Xcode关联绑定发布证书的配置.创建App信息. ...

  8. HDU 5965 扫雷 【模拟】 (2016年中国大学生程序设计竞赛(合肥))

    扫雷 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submissi ...

  9. js怎样生成json的数据

    var row1 = {};row1.name = 'david';row1.age = '20'; //或者var row2 = {name: 'peter', age: '23'}; var da ...

  10. [Boost]图形处理库Boost::Polygon

    Background 工作中经师傅指导学习应用到了Boost::Polygon这个库,相对于Boost::Geometry,Polygon出自Intel.抽象于芯片流程,于是更贴近于芯片设计流程应用. ...