hadoop核心逻辑shuffle代码分析-map端
首先要推荐一下:http://www.alidata.org/archives/1470
阿里的大牛在上面的文章中比较详细的介绍了shuffle过程中mapper和reduce的每个过程,强烈推荐先读一下。
不过,上文没有写明一些实现的细节,比如:spill的过程,mapper生成文件的 partition是怎么做的等等,相信有很多人跟我一样在看了上面的文章后还是有很多疑问,我也是带着疑问花了很久的看了cdh4.1.0版本 shuffle的逻辑,整理成本文,为以后回顾所用。
首先用一张图展示下map的流程:

- <span style="font-size:18px;"> /**
- * Called once for each key/value pair in the input split. Most applications
- * should override this, but the default is the identity function.
- */
- @SuppressWarnings("unchecked")
- protected void map(KEYIN key, VALUEIN value,
- Context context) throws IOException, InterruptedException {
- context.write((KEYOUT) key, (VALUEOUT) value);
- }
- </span>
- <span style="font-size:18px;">public void run(Context context) throws IOException, InterruptedException {
- setup(context);
- while (context.nextKeyValue()) {
- map(context.getCurrentKey(), context.getCurrentValue(), context);
- }
- cleanup(context);
- }</span>
key value在写入context中后实际是写入MapOutputBuffer类中。在第一个阶段的初始化过程中,MapOutputBuffer类会根据配置文件初始化内存buffer,我们来看下都有哪些参数:
- <span style="font-size:18px;">partitions = job.getNumReduceTasks();
- rfs = ((LocalFileSystem)FileSystem.getLocal(job)).getRaw();
- //sanity checks
- final float spillper =
- job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);
- final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100);
- 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);
- }
- 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);</span>

buf的右侧开始往左写,同时,会把一条keyvalue的meta信息(partition,keystart,valuestart)写入到最左边的
index区域。当wrap
buf大小达到spill的触发比例后会block写入,挖出一部分数据开始spill,直到spill完成后才能继续写,不过写入位置不会置零,而是类
似循环buf那样,在spill掉数据后可以重复利用内存中的buf区域。
- <span style="font-size:18px;">@Override
- public void write(K key, V value) throws IOException, InterruptedException {
- collector.collect(key, value,
- partitioner.getPartition(key, value, partitions));
- }</span>
在keyvalue对写入MapOutputBuffer时会调用
partitioner.getPartition方法计算partition即应该分配到哪个reducer,这里的partition只是在内存的
buf的index区写入一条记录而已,和下一个部分的partition不一样哦。看下默认的partitioner:HashPartition
- <span style="font-size:18px;">/** Use {@link Object#hashCode()} to partition. */
- public int getPartition(K key, V value,
- int numReduceTasks) {
- return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
- }</span>
HashPartition只是把key hash后按reduceTask的个数取模,因此一般来说,不同的key分配到哪个reducer是随即的!所以,reducer内的所有数据是有序的,但reducer之间的数据却是乱序的!要想数据整体排序,要不只设一个reducer,要不使用TotalOrderPartitioner!
- <span style="font-size:18px;">sorter.sort(MapOutputBuffer.this, mstart, mend, reporter);</span>
- <span style="font-size:18px;">public int compare(final int mi, final int mj) {
- final int kvi = offsetFor(mi % maxRec);
- final int kvj = offsetFor(mj % maxRec);
- final int kvip = kvmeta.get(kvi + PARTITION);
- final int kvjp = kvmeta.get(kvj + PARTITION);
- // sort by partition
- if (kvip != kvjp) {
- return kvip - kvjp;
- }
- // sort by key
- return comparator.compare(kvbuffer,
- kvmeta.get(kvi + KEYSTART),
- kvmeta.get(kvi + VALSTART) - kvmeta.get(kvi + KEYSTART),
- kvbuffer,
- kvmeta.get(kvj + KEYSTART),
- kvmeta.get(kvj + VALSTART) - kvmeta.get(kvj + KEYSTART));
- }</span>

- <span style="font-size:18px;">for (int i = 0; i < partitions; ++i) {
- 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);
- key.reset(kvbuffer, kvmeta.get(kvoff + KEYSTART),
- (kvmeta.get(kvoff + VALSTART) -
- kvmeta.get(kvoff + 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);
- }
- }</span>
如果job没有定义combiner则直接写文件,如果有combiner则在这里进行combine。
在生成spill文件后还会将此次spillRecord的记录写在一个index文件中。
- <span style="font-size:18px;">Path indexFilename =
- mapOutputFile.getSpillIndexFileForWrite(numSpills, partitions
- * MAP_OUTPUT_INDEX_RECORD_LENGTH);
- spillRec.writeToFile(indexFilename, job);</span>
- <span style="font-size:18px;">rec.startOffset = segmentStart;
- rec.rawLength = writer.getRawLength();
- rec.partLength = writer.getCompressedLength();
- spillRec.putIndex(rec, i);</span>
- <span style="font-size:18px;">int mergeFactor = job.getInt(JobContext.IO_SORT_FACTOR, 100);</span>
的合并不同于combiner,无论有没有配置combiner这里的merge都会执行。merge阶段的输出是一个数据文件
MapFinalOutputFile和一个index文件。看下相关代码:
- <span style="font-size:18px;">RawKeyValueIterator kvIter = Merger.merge(job, rfs,
- keyClass, valClass, codec,
- segmentList, mergeFactor,
- new Path(mapId.toString()),
- job.getOutputKeyComparator(), reporter, sortSegments,
- null, spilledRecordsCounter, sortPhase.phase());
- //write merged output to disk
- long segmentStart = finalOut.getPos();
- Writer<K, V> writer =
- new Writer<K, V>(job, finalOut, keyClass, valClass, codec,
- spilledRecordsCounter);
- if (combinerRunner == null || numSpills < minSpillsForCombine) {
- Merger.writeFile(kvIter, writer, reporter, job);
- } else {
- combineCollector.setWriter(writer);
- combinerRunner.combine(kvIter, combineCollector);
- }</span>
说
下merge的算法。每个spill生成的文件中keyvalue都是有序的,但不同的文件却是乱序的,类似多个有序文件的多路归并算法。Merger分
别取出需要merge的spillfile的最小的keyvalue,放入一个内存堆中,每次从堆中取出一个最小的值,并把此值保存到merge的输出文
件中。这里和hbase中scan的算法非常相似,在分布式系统中多路归并排序真是当红小生啊!

因为虽然第四步中combine过但那只是部分输入的combine,在merge时仍然需要combine。这里有人问了,既然这里有
combiner,为啥在spill输出时还要combine纳,我认为是因为每次combine都会大大减少输出文件的大小,spill时就
combine能减少一定的IO操作。
- <span style="font-size:18px;">// record offsets
- rec.startOffset = segmentStart;
- rec.rawLength = writer.getRawLength();
- rec.partLength = writer.getCompressedLength();
- spillRec.putIndex(rec, parts);</span>
4.combine在spill和merge中都是进行。多次的combine会减少mapreduce中的IO操作,如果使用得当会很好的提高性能。但需要注意的是要深刻理解combine的意义,比如平均值就不适合用combine。
hadoop核心逻辑shuffle代码分析-map端的更多相关文章
- hadoop核心逻辑shuffle代码分析-map端 (转)
一直对书和各种介绍不太满意, 终于看到一篇比较好的了,迅速转载. 首先要推荐一下:http://www.alidata.org/archives/1470 阿里的大牛在上面的文章中比较详细的介绍了sh ...
- Hadoop基于Protocol Buffer的RPC实现代码分析-Server端
http://yanbohappy.sinaapp.com/?p=110 最新版本的Hadoop代码中已经默认了Protocol buffer(以下简称PB,http://code.google.co ...
- Hadoop基于Protocol Buffer的RPC实现代码分析-Server端--转载
原文地址:http://yanbohappy.sinaapp.com/?p=110 最新版本的Hadoop代码中已经默认了Protocol buffer(以下简称PB,http://code.goog ...
- 【hadoop代码笔记】Mapreduce shuffle过程之Map输出过程
一.概要描述 shuffle是MapReduce的一个核心过程,因此没有在前面的MapReduce作业提交的过程中描述,而是单独拿出来比较详细的描述. 根据官方的流程图示如下: 本篇文章中只是想尝试从 ...
- Hadoop on Mac with IntelliJ IDEA - 10 陆喜恒. Hadoop实战(第2版)6.4.1(Shuffle和排序)Map端 内容整理
下午对着源码看陆喜恒. Hadoop实战(第2版)6.4.1 (Shuffle和排序)Map端,发现与Hadoop 1.2.1的源码有些出入.下面作个简单的记录,方便起见,引用自书本的语句都用斜体表 ...
- Hadoop基础-Map端链式编程之MapReduce统计TopN示例
Hadoop基础-Map端链式编程之MapReduce统计TopN示例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.项目需求 对“temp.txt”中的数据进行分析,统计出各 ...
- 项目中Map端内存占用的分析
最近在项目中开展重构活动,对Map端内存尽量要省一些,当前的系统中Map端内存最高占用大概3G左右(设置成2G时会导致Java Heap OOM).虽然个人觉得占用不算多,但是显然这样的结果想要试 ...
- hadoop的压缩解压缩,reduce端join,map端join
hadoop的压缩解压缩 hadoop对于常见的几种压缩算法对于我们的mapreduce都是内置支持,不需要我们关心.经过map之后,数据会产生输出经过shuffle,这个时候的shuffle过程特别 ...
- Hadoop2.4.1 MapReduce通过Map端shuffle(Combiner)完成数据去重
package com.bank.service; import java.io.IOException; import org.apache.hadoop.conf.Configuration;im ...
随机推荐
- 图的基本算法(BFS和DFS)
图是一种灵活的数据结构,一般作为一种模型用来定义对象之间的关系或联系.对象由顶点(V)表示,而对象之间的关系或者关联则通过图的边(E)来表示. 图可以分为有向图和无向图,一般用G=(V,E)来表示图. ...
- (笔记)linux设备驱动--LED驱动
linux设备驱动--LED驱动 最近正在学习设备驱动开发,因此打算写一个系列博客,即是对自己学习的一个总结,也是对自己的一个督促,有不对,不足,需要改正的地方还望大家指出,而且希望结识志同道合的朋友 ...
- Spring JDBC批量操作
以下示例将演示如何使用spring jdbc进行批量更新.我们将在单次批次操作中更新student表中的记录. student表的结果如下 - CREATE TABLE student( id INT ...
- e796. 设置JSlider的方向
Besides being either horizontal or vertical, a slider can also be inverted. An inverted horizontal s ...
- MultipleActiveResultSets=true 数据库连接复用
注:EF连接Mysql时,连接字符串是不能包含MARS的,Mysql不支持这个特性. ADO.NET 1.n 利用SqlDataReader读取数据,针对每个结果集需要一个独立的连接. 这些独立的链接 ...
- 「下载神器」aria2 懒人安装教程 [Windows]
是一款开源.轻量级的多协议命令行下载工具,支持 HTTP/HTTPS.FTP.SFTP.BitTorrent 和 Metalink 协议,拥有众多第三方支持插件,被誉为「下一代下载工具」和「下载神器」 ...
- VS2013 此模板尝试加载组件程序集”NuGet.VisualStudio.interop,Version=1.0.0.0 的解决办法
此模板尝试加载组件程序集 NuGet.VisualStudio.Interop 分析错误 既然错误信息提示模板尝试加载组件程序集,那说明NuGet.VisualStudio.Interop程序集不 ...
- Xianfeng轻量级Java中间件平台:权限管理
权限管理:是通过系统对用户的行为进行控制的一套业务规则,可以做得很简单,比如通过硬编码的方式进行控制,也可以做得很复杂,比如通过一些复杂的权限模型去实现一些复杂的权限控制,比如菜单访问权限.按钮操作权 ...
- List&Map&Set的操作和遍历
Java的三大集合即:Set.List.Map. Set:代表无序.不可重复的集合,常用的有HashSet(哈希表实现).TreeSet(红黑树实现): List:代表有序.可以重复的集合,比较常用的 ...
- unity3d Start执行不同时问题
1.一个Scene的场景里有许多的GameObject,很多GameObject上绑定了Script,每个Script上又都有Start函数 using UnityEngine; using Syst ...