本篇分两部分,第一部分分析使用 java 提交 mapreduce 任务时对 mapper 数量的控制,第二部分分析使用 streaming 形式提交 mapreduce 任务时对 mapper 数量的控制。

环境:hadoop-3.0.2

前言:

熟悉 hadoop mapreduce 的人可能已经知道,即使在程序里对 conf 显式地设置了 mapred.map.tasks 或 mapreduce.job.maps,程序也并没有运行期望数量的 mapper。

这是因为,mapper 的数量由输入的大小、HDFS 当前设置的 BlockSize、以及当前配置中的 split min size 和 split max size 等参数共同确定,并不会受到简单的人工设置 mapper num 的影响。

因此,对于 mapper num 的控制,需要我们理解 hadoop 中对于 FileInputFormat 类中 getSplit() 方法的实现,针对性地配置 BlockSize、split min size、split max size 等参数,才能达到目的。

重点:

值得一提并且容易忽略的是,要区分 org.apache.hadoop.mapred.FileInputFormat类和 org.apache.hadoop.mapreduce.lib.input.FileInputFormat类,两者虽然相似,但在getSplit()上的实现是有区别的。

重要区别是,hadoop streaming 中使用的 InputFormat 类,使用的是 org.apache.hadoop.mapred.FileInputFormat,仅仅需要指定 mapreduce.job.maps ,就能够设置 mapper num了(具体源码分析在第二部分)。而使用JAVA设计的 mapreduce 任务中使用的 InputFormat 类,使用的是 org.apache.hadoop.mapreduce.lib.input.FileInputFormat,则需要通过配置BlockSize、split min size、split max size 等参数来间接性地控制 mapper num。

一、Java 本地提交 mapreduce 任务, org.apache.hadoop.mapreduce.lib.input.FileInputFormat 的 mapper num 控制

1. 在java本地编辑 mapreduce 任务,(默认)使用 FileInputFormat 类的子类 TextInputFormat

job.setInputFormatClass(TextInputFormat.class);

2. mapper 的切分逻辑在 FileInputFormat 类中的 getSplits()实现:

public List<InputSplit> getSplits(JobContext job) throws IOException {
StopWatch sw = (new StopWatch()).start();
long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
List<InputSplit> splits = new ArrayList();
List<FileStatus> files = this.listStatus(job);
Iterator var9 = files.iterator(); while(true) {
while(true) {
while(var9.hasNext()) {
FileStatus file = (FileStatus)var9.next();
Path path = file.getPath();
long length = file.getLen();
if (length != 0L) {
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus)file).getBlockLocations();
} else {
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0L, length);
} if (this.isSplitable(job, path)) {
long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining;
int blkIndex;
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
} if (bytesRemaining != 0L) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
}
} else {
if (LOG.isDebugEnabled() && length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization is possible: " + file.getPath());
} splits.add(this.makeSplit(path, 0L, length, blkLocations[0].getHosts(), blkLocations[0].getCachedHosts()));
}
} else {
splits.add(this.makeSplit(path, 0L, length, new String[0]));
}
} job.getConfiguration().setLong("mapreduce.input.fileinputformat.numinputfiles", (long)files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size() + ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
} return splits;
}
}
}

3. 最后确定 mapper 数量在这里:

                         if (this.isSplitable(job, path)) {
long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize); long bytesRemaining;
int blkIndex;
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
} if (bytesRemaining != 0L) {
blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
splits.add(this.makeSplit(path, length - bytesRemaining, bytesRemaining, blkLocations[blkIndex].getHosts(), blkLocations[blkIndex].getCachedHosts()));
}

含义:

a. 当 this.isSplitable 开启时,只要当前未分配的大小 bytesRemaining 大于 splitSize 的 1.1 倍,就添加一个 inputSplit, 即一个mapper 被生成。

b. 最后,不足 1.1 倍splitSize 的残余,补充为一个 mapper。因此,经常发现实际分配的 mapper 数比自己定义的会多 1 个。

c. 为什么设置1.1倍?避免将不足 0.1 倍 splitSize 的量分配为一个 mapper, 避免浪费。

4.  重要的两个量:BlockSize 和 splitSize

long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);

其中,blockSize 是 hdfs 设置的,一般是 64MB 或 128MB,我的 hdfs 中为 128 MB = 132217728L。这个量可认为静态,我们不宜修改。

观察 splitSize 的获得:

     protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
return Math.max(minSize, Math.min(maxSize, blockSize));
}

在 getSplits()中找到 minSize, maxSize, blockSize 的赋值:

long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);

找到这些量的赋值、默认值:

maxSize 的 setter/getter:    除非用户重新设置,否则 maxSize 的默认值为 Long 的最大值

 public static void setMaxInputSplitSize(Job job, long size) {
job.getConfiguration().setLong("mapreduce.input.fileinputformat.split.maxsize", size);
} public static long getMaxSplitSize(JobContext context) {
return context.getConfiguration().getLong("mapreduce.input.fileinputformat.split.maxsize", 9223372036854775807L);
}

minSize 的 setter/getter:  除非用户重新设置,否则 minSize 的默认值为 1L

protected long getFormatMinSplitSize() {
return 1L;
} public static void setMinInputSplitSize(Job job, long size) {
job.getConfiguration().setLong("mapreduce.input.fileinputformat.split.minsize", size);
} public static long getMinSplitSize(JobContext job) {
return job.getConfiguration().getLong("mapreduce.input.fileinputformat.split.minsize", 1L);
}

因此容易算出,默认情况下,

long splitSize = this.computeSplitSize(blockSize, minSize, maxSize) = Math.max(Math.max(1L,1L), Math.min(9223372036854775807L, 128M=132217728L)) = 132217728L = 128M

5. 控制 mapper 数量

知道了上面的计算过程,我们要控制 mapper,在 BlockSize 不能动的情况下,就必须控制 minSize 和 maxSize 了。这里主要控制 maxSize。

TextInputFormat.setMinInputSplitSize(job, 1L);//设置minSize
TextInputFormat.setMaxInputSplitSize(job, 10 * 1024 * 1024);//设置maxSize

测试输入文件大小为 40MB, 很小, 在默认情况下, 被分配为 1 个或 2 个 mapper 执行成功。

现在希望分配 4 个mapper:那么设置 maxSize 为10M ,那么 splitSize 计算为 10M。对于 40MB 的输入文件,理应分配 4 个mapper。

实际运行,运行了 5 个mapper,认为成功摆脱了默认启动 2 个mapper 的限制,额外多出的 1 个 mapper 则猜测是上文提到的,对残余量的补充 mapper。

6. 至此,对Java 本地提交 mapreduce 任务, org.apache.hadoop.mapreduce.lib.input.FileInputFormat 的 mapper num 控制方法如上。接下来讨论 streaming 使用的 org.apache.hadoop.mapred.FileInputFormat 的 mapper 控制。

二、streaming 提交 mapreduce 任务, org.apache.hadoop.mapred.FileInputFormat 的 mapper num 控制

1. 可通过 mapreduce.job.maps 直接控制,即使不是绝对精确。原因在下面的源码分析中可以看到。

 hadoop dfs -rm -r -f /output && \

 hadoop jar /opt/hadoop-3.0.2/share/hadoop/tools/lib/hadoop-streaming-3.0.2.jar \
-D mapreduce.reduce.tasks=0 \
-D mapreduce.job.maps=7 \
-input /input \
-output /output \
-mapper "cat" \
-inputformat TextInputFormat

2. 将 org.apache.hadoop.mapreduce.lib.input.FileInputFormat 中的 maxSize,尝试通过 streaming 的 -D 设置,是无效的。因为 streaming 使用的是 org.apache.hadoop.mapred.FileInputFormat,在下面的源码分析中可以看到。

3. 查看 FileInputFormat 的 getSplits 源码

     public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException {
StopWatch sw = (new StopWatch()).start();
FileStatus[] files = this.listStatus(job);
job.setLong("mapreduce.input.fileinputformat.numinputfiles", (long)files.length);
long totalSize = 0L;
FileStatus[] var7 = files;
int var8 = files.length; for(int var9 = 0; var9 < var8; ++var9) {
FileStatus file = var7[var9];
if (file.isDirectory()) {
throw new IOException("Not a file: " + file.getPath());
} totalSize += file.getLen();
} long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong("mapreduce.input.fileinputformat.split.minsize", 1L), this.minSplitSize);
ArrayList<FileSplit> splits = new ArrayList(numSplits);
NetworkTopology clusterMap = new NetworkTopology();
FileStatus[] var13 = files;
int var14 = files.length; for(int var15 = 0; var15 < var14; ++var15) {
FileStatus file = var13[var15];
Path path = file.getPath();
long length = file.getLen();
if (length == 0L) {
splits.add(this.makeSplit(path, 0L, length, new String[0]));
} else {
FileSystem fs = path.getFileSystem(job);
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus)file).getBlockLocations();
} else {
blkLocations = fs.getFileBlockLocations(file, 0L, length);
} if (!this.isSplitable(fs, path)) {
if (LOG.isDebugEnabled() && length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization is possible: " + file.getPath());
} String[][] splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, 0L, length, clusterMap);
splits.add(this.makeSplit(path, 0L, length, splitHosts[0], splitHosts[1]));
} else {
long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(goalSize, minSize, blockSize); long bytesRemaining;
String[][] splitHosts;
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, length - bytesRemaining, splitSize, clusterMap);
splits.add(this.makeSplit(path, length - bytesRemaining, splitSize, splitHosts[0], splitHosts[1]));
} if (bytesRemaining != 0L) {
splitHosts = this.getSplitHostsAndCachedHosts(blkLocations, length - bytesRemaining, bytesRemaining, clusterMap);
splits.add(this.makeSplit(path, length - bytesRemaining, bytesRemaining, splitHosts[0], splitHosts[1]));
}
}
}
} sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size() + ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
} return (InputSplit[])splits.toArray(new FileSplit[splits.size()]);
}

与 org.apache.hadoop.mapreduce.lib.input.FileInputFormat 相似,但不同之处还是很重要的。主要在

long blockSize = file.getBlockSize();
long splitSize = this.computeSplitSize(goalSize, minSize, blockSize);

赋值:

long goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong("mapreduce.input.fileinputformat.split.minsize", 1L), this.minSplitSize);

4. 追溯这些量

protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
private long minSplitSize = 1L;

5. 分析

minSize 默认为1L,blocksize是当前目标文件的块大小,而 splitSize 就是 BlockSize 和 goalSize 的小值。

goalSize 的计算,就是输入文件总大小与 numSplits 的比值。而 numSplits 就是我们在streaming 中设置的 -D mapreduce.job.maps

因此,在streaming中才可以简单地直接设置 mapper 的数量了。

但是,只有当 goalsize 小于 blocksize 时,mapreduce.job.maps 才会生效!

当 goalsize < blocksize,splitsize = goalsize,此时你设置的 mapreduce.job.maps 数量一般大于输入块的数量,因此配置生效。

当 goalsize > blocksize,splitsize = blocksize,此时你设置的 mapreduce.job.maps 不足,一般少于输入块的数量,因此配置不生效。

换句话说,如果输入只有一个文件,那么只要 -D mapreduce.job.maps > 1,配置大多数会生效。

MapReduce :基于 FileInputFormat 的 mapper 数量控制的更多相关文章

  1. [Hadoop] mapper数量的控制

    确定map任务数时依次优先参考如下几个原则: 1)      每个map任务使用的内存不超过800M,尽量在500M以下 比如处理256MB数据需要的时间为10分钟,内存为800MB,此时如果处理12 ...

  2. MapReduce从输入文件到Mapper处理之间的过程

    1.MapReduce代码入口 FileInputFormat.setInputPaths(job, new Path(input)); //设置MapReduce输入格式 job.waitForCo ...

  3. Hadoop(十七)之MapReduce作业配置与Mapper和Reducer类

    前言 前面一篇博文写的是Combiner优化MapReduce执行,也就是使用Combiner在map端执行减少reduce端的计算量. 一.作业的默认配置 MapReduce程序的默认配置 1)概述 ...

  4. Hadoop-2.4.1学习之怎样确定Mapper数量

    MapReduce框架的优势是能够在集群中并行运行mapper和reducer任务,那怎样确定mapper和reducer的数量呢,或者说怎样以编程的方式控制作业启动的mapper和reducer数量 ...

  5. MR 的 mapper 数量问题

    看到群里面一篇文章涨了贱识 http://www.cnblogs.com/xuxm2007/archive/2011/09/01/2162011.html 之前关注过 reduceer 的数量问题,还 ...

  6. 基于VHDL利用PS2键盘控制的电子密码锁设计

    基于VHDL利用PS2键盘控制的密码锁设计 附件:下载地址 中文摘要 摘 要:现代社会,人们的安全意识正在不断提升.按键密码锁由于其具有方便性.低成本等特征,还是大有用武之地的.但是通常的按键密码锁开 ...

  7. Struts2中基于Annotation的细粒度权限控制

    Struts2中基于Annotation的细粒度权限控制 2009-10-19 14:25:53|  分类: Struts2 |  标签: |字号大中小 订阅     权限控制是保护系统安全运行很重要 ...

  8. 基于maven插件的缓存控制插件

    asset-cache-control github源码及下载地址: https://github.com/StruggleBird/asset-cache-control 基于maven插件的缓存控 ...

  9. Shiro入门之二 --------基于注解方式的权限控制与Ehcache缓存

    一  基于注解方式的权限控制 首先, 在spring配置文件applicationContext.xml中配置自动代理和切面 <!-- 8配置自动代理 -->    <bean cl ...

随机推荐

  1. javascript的数组之sort()

    sort()方法用in-place的算法对原数组进行排序,但不会产生新的数组.这个方法不是一个稳定的排序,默认采用的是安字符串Unicode码点进行排序的. let fruit =  ['cherri ...

  2. python全栈开发 * 进程理论 进程创建 * 180724

    一.进程理论 1.进程是资源分配的最小单位. 2.进程调度就是多个进程在操作系统的控制下被CPU执行,去享用计算机的资源. 先来先服务 短作业优先 时间片轮转 多级反馈队列 3.进程调度的过程是不能够 ...

  3. 如何将PDF文件转Word,有什么方法

    PDF文件怎样转换成Word呢?在现在的日常办公中PDF文件和Word文件都是办公必不可少的两种文件格式了.那么当我们在工作中需要对这两种文件进行转换时,我们应该怎样实现呢?下面我们就一起来看一下吧. ...

  4. git从已有分支拉新分支开发

    开发过程中经常用到从master分支copy一个开发分支,下面我们就用命令行完成这个操作: 1. 切换到被copy的分支(master),并且从远端拉取最新版本 $git checkout maste ...

  5. Codeforces 1136 - A/B/C/D/E - (Done)

    链接:https://codeforces.com/contest/1136/ A - Nastya Is Reading a Book - [二分] #include<bits/stdc++. ...

  6. LeetCode 12 - 整数转罗马数字 - [简单模拟]

    题目链接:https://leetcode-cn.com/problems/integer-to-roman/ 题解: 把 $1,4,5,9,10,40,50, \cdots, 900, 1000$ ...

  7. 在centos7下安装python3的步骤

    环境搭建 准备工具: centos7:http://mirror.bit.edu.cn/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1611.iso virtus ...

  8. linear-gradient常用实现效果

    之前也研究过css3的这个属性,感觉没什么大用,一般的开发不会用到,毕竟调出来的渐变不专业,不如找一个好看的图片,其实很多时候还是有用的,偷来三个例子. 一.控制虚线 一般写虚线都用dashed,但有 ...

  9. python second lesson

    1.系统模块 新建的文件名不能和导入的库名相同,要不然python会优先从自己的目录下寻找. import sys  sys是一个系统变量,sys.argv会调出文件的相对路径,sys.argv[2] ...

  10. Vue+element 实现文件导出xlsx格式

    傻瓜教程:   第一步:安装两个依赖包 npm install --save xlsx file-saver 第二步:建立一个Vue文件,导入以下代码即可 <template> <d ...