1. Mapper 与 Reducer 数量

对于一个默认的MapReduce Job 来说,map任务的数量等于输入文件被划分成的分块数,这个取决于输入文件的大小以及文件块的大小(如果此文件在 HDFS中)。但是对于 reduce的任务,并不会自动决定reducer数目的大小,若未指定,则默认为1。例如:

但单个reducer任务执行效率不尽人意,在实际场景中会将它设置为一个较大的数值。此时,决定Key条目被送往哪个reducer由方法 setPartitionerClass() 指定:job.setPartitionerClass(HashPartitioner.class);

 
默认为HashPartitioner,它会将每条Key做Hash,然后与最大的整型值做一次按位与操作,以得到一个非负整数。然后对分区数做取模(mod)操作,将key分配到其中一个分区。这里的分区数即为reducer数目。HashPartitioner 源码如下:
 
public class HashPartitioner<K, V> extends Partitioner<K, V> {
    public HashPartitioner() {
    }     public int getPartition(K key, V value, int numReduceTasks) {
        return (key.hashCode() & 2147483647) % numReduceTasks;
    }
}

若是为reducer数目设置为默认值1,则所有的中间数据都会被放入到一个reducer中,作业处理效率会非常低效。若是设置了过大的值,则每个reducer都会输出一个文件,会导致过多的小文件。

在为一个任务选择多少个reducer个数时,应遵循的原则为:目标reducer保持在每个运行5分钟左右,且产生至少一个HDFS块的输出比较合适。

记录在发送给 reducer 之前,会被MapReduce系统进行排序。因此输入文件中的行会被交叉放入一个合并后的输出文件。

2. 输入格式

我们已经了解到map的输入是分片(split),一个分片对应一个mapper,且仅被一个mapper处理。分片里面是多条记录(item)。“输入分片”在Hadoop中以InputSplit 接口的方式提供:

public interface InputSplit extends Writable {
    long getLength() throws IOException;     String[] getLocations() throws IOException;
}

它包含两个方法,分别为getLength() 与 getLocations()。其中getLength() 用于获取数据的长度(以字节为单位);getLocations() 用于获取一组存储位置(也就是一组主机名)。其中getLocations()的返回值由mapreduce系统获取后,实现data locality,也就是尽量将map任务放在离数据节点近的地方。而getLength() 的返回值用于排序分片,将最大的分片优先处理,以最小化整个作业运行的时间。

InputSplit(mapreduce中的分片)由InputFormat创建,它负责创建输入分片,并将它们分成一条条记录(item)。首先简单看一下InputFormat 抽象类:

public abstract class InputFormat<K, V> {
    public InputFormat() {
    }     public abstract List<InputSplit> getSplits(JobContext var1) throws IOException, InterruptedException;     public abstract RecordReader<K, V> createRecordReader(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
}

这里 getSplits() 方法计算分片,然后将计算得到的List 结果发给 application master。Application master 根据其分片所在节点信息,调度map任务到离分片数据最近的节点。在map任务端,会把输入分片传给 InputFormat 的 createRecordReader() 方法,此方法会返回一个 RecordReader 对象,用于迭代读取这个分片上的记录(item),并生成记录的键值对,之后传递给 map函数。通过查看 Mapper 类中的 run() 方法,更好的了解此过程:

public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
    this.setup(context);     try {
        while(context.nextKeyValue()) {
            this.map(context.getCurrentKey(), context.getCurrentValue(), context);
        }
    } finally {
        this.cleanup(context);
    } }

这里,先运行setup() 操作,然后从 context 不断迭代获取分片的内容,并传给map() 方法,并由map() 方法进一步对 key-value 对进行处理。

3. FileInputFormat

在Hadoop 中,数据源一般为文件,而 FileInputFormat 类就是用于处理数据源为文件的一个(继承于)InputFormat 类:

public abstract class FileInputFormat<K, V> implements InputFormat<K, V> {

可以看到它是一个抽象类,它的实现类有CombineFileInputFormat、TextInputFormat、KeyValueTextInputFormat、NLineInputFormat以及SequenceFileInputFormat。

FileInputFormat类提供两个功能:1. 指出作业的输入文件位置;2. 为输入文件生成分片的代码实现。

在FileInputFormat中,作业的输入可以是一个文件、一个目录,也可以是目录与文件的集合。它提供了四种方法用于设置Job的输入路径:

public static void addInputPath(JobConf conf, Path path)
public static void addInputPaths(JobConf conf, String commaSeparatedPaths)
public static void setInputPaths(JobConf conf, Path... inputPaths)
public static void setInputPaths(JobConf conf, String commaSeparatedPaths) 

其中addInputPath() 和 addInputPaths() 用于添加路径,以构成路径列表。而setInputPath() 用于一次性设置完整的路径列表(会替换前面所有路径设置)。

在设置路径后,也可以指定需要排除的特定文件,此功能由 setInputPathFilter() 实现:

public static void setInputPathFilter(JobConf conf, Class<? extends PathFilter> filter) {
    conf.setClass("mapreduce.input.pathFilter.class", filter, PathFilter.class);
}

它可以设置一个过滤器PathFilter,默认的实现是过滤掉隐藏文件(以 . 和 _ 开头的文件)。如果通过setInputPathFilter() 设置过滤器,它会在默认过滤器的基础上进行过滤,也就是说,仅会在非隐藏文件中再次进行过滤。

输入路径的设置可以通过属性与选项进行配置,在属性配置中相关配置为:

mapreduce.input.fileinputformat.inputdir (逗号分隔属性,无默认值)

mapreduce.input.pathFilter.class  (PathFilter 类名,无默认值)

4. FileInputFormat 类处理输入分片

在设置了一组文件后,FileInputFormat会将文件转换为输入分片。这里需要注意的是:在HDFS中,一个文件可以占用(分布到)多个block,但是不会存在一个block中存多个文件。对于小文件(小于一个HDFS 块大小的文件)来说,一个文件就是占用一个block,但是不会占据整个block的空间。例如,当一个1MB的文件存储在一个128MB 的块中时,文件只使用 1MB 的磁盘空间,而不是128MB)。

FileInputFormat 只分割大文件,也就是文件超过HDFS块的大小。在FileInputFormat中,控制分片大小的属性有:

mapreduce.input.fileinputformat.split.minsize  一个文件分片最小的有效字节数(int类型),默认值为1(字节)

mapreduce.input.fileinputformat.split.maxsize  一个文件分片中最大的有效字节数(long 类型),默认值为Long.MAX_VALUE,即9223372036854775807

dfs.blocksize        HDFS中的块大小(按字节),默认为 128MB(即 134217728)

最小分片通常为1个字节,用户可以设置最小分片的大小超过HDFS 块大小,这样会强制分片比HDFS块大。但是如果数据存储在 HDFS 上,则这样对data locality 来说,并不友好,以至于延长任务执行时间。

最大分片默认是 Java Long 类型的最大值,只有把它的值设置为小于 HDFS Block 大小才有效,此时会强制分片比块小。

在 FileInputFormat中,分片的大小由以下公式计算:

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

其中参数部分为:

long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
protected long getFormatMinSplitSize() {
    return 1L;
}
public static long getMinSplitSize(JobContext job) {
    return job.getConfiguration().getLong("mapreduce.input.fileinputformat.split.minsize", 1L);
}
public static long getMaxSplitSize(JobContext context) {
    return context.getConfiguration().getLong("mapreduce.input.fileinputformat.split.maxsize", 9223372036854775807L);
}

minSize 若未指定,则默认为 1。MaxSize默认为Java Long类型最大值。再计算时,先取 maxSize 和 blockSize 的最小值,然后再取结果与 minSize的最大值。

在默认情况下:minSize < blockSize < maxSize

所以分片的大小就是 blockSize大小。

5. 小文件与 CombineFileInputFormat

相对于大量的小文件,Hadoop更适合处理少量的大文件。其中一个原因是:对于每个小文件(远小于 HDFS块大小),FileInputFormat 都会生成一个分片(生成的分片要么是文件的整个内容,要么是文件的部分内容),这样会产生大量的 map 任务,并且每个map任务仅处理一小部分数据,这样会导致任务执行效率低下,时间过长。

CombineFileInputFormat 可以缓解此问题,它针对小文件而设计。FileInputFormat 为每个小文件产生一个分片,而CombineFileInpurtFormat 把多个文件打包到一个分片中,以便于每个 mapper 可以处理更多的数据。更重要的是:CombineFileInputFormat在分配多个block到同一个 split时,会考虑到node locality 以及 rack locality。所以它的速度在一个典型的 mr 任务中,处理输入的速度并不会下降。

不过尽可能要避免小文件过多的情况,原因有:

  1. 处理小文件会增加运行作业而必须的寻址次数

  2. 浪费namenode的内存

可以尝试使用顺序文件(sequence file)将这些小文件合并成一个或多个大文件:例如将文件名作为key,文件内容作为 value。但是如果集群里已经有了大量小文件,可以尝试一下CombineFileInputFormat 方法。

CombinedFileInputFormat不仅处理小文件有好处,处理大文件时也有益处。例如,如果mapper在处理一个block时仅花费很少的时间,则可以考虑使用CombineFileInputFormat,并将maximum split size 设置为 HDFS block 大小的几倍(参数为mapred.max.split.size)。这样每个mapper会处理多个block,使得整个处理时间下降。

6. 避免分片

有时候可能需要计算整个文件里的顺序关系,这种任务无法分布式处理,所以只能让文件由一个mapper处理,此时需要避免文件被分片。

有两种方式可以避免文件被分片,而是当作一个单独分片处理:

  1. 设置最小分片大小split.minsize 为Java Long类型最大值(long.MAX_VALUE)

  2.  使用FileInputFormat 具体子类时,重写isSplitable() 方法,把返回值设置为 false

使用第二种方法时,以 TextInputFormat类为例:

public class TextInputFormat extends FileInputFormat<LongWritable, Text> implements JobConfigurable {
    private CompressionCodecFactory compressionCodecs = null;     public TextInputFormat() {
    }     public void configure(JobConf conf) {
        this.compressionCodecs = new CompressionCodecFactory(conf);
    }     protected boolean isSplitable(FileSystem fs, Path file) {
        CompressionCodec codec = this.compressionCodecs.getCodec(file);
        return null == codec ? true : codec instanceof SplittableCompressionCodec;
    }

….

}

默认会根据 CompressionCodec 类型判断是否切分,也可以直接指定return 为 false,使得输入文件不可切分。

References:Hadoop权威指南第4版

Hadoop InputFormat 输入文件分片的更多相关文章

  1. Hadoop InputFormat浅析

    本文转载:http://hi.baidu.com/_kouu/item/dc8d727b530f40346dc37cd1 在执行一个Job的时候,Hadoop会将输入数据划分成N个Split,然后启动 ...

  2. Hadoop InputFormat详解

    InputFormat是MapReduce编程模型包括5个可编程组件之一,其余4个是Mapper.Partitioner.Reducer和OutputFormat. 新版Hadoop InputFor ...

  3. Hadoop InputFormat

    Hadoop可以处理不同数据格式(数据源)的数据,从文本文件到(非)关系型数据库,这很大程度上得益于Hadoop InputFormat的可扩展性设计,InputFormat层次结构图如下:  

  4. hadoop InputFormat 类别

    FileInputFormat是所有使用文件作为数据源的InputFormat的积累.它提供两个功能:一个是定义哪些文件包含在一个作业的输入中:一个为输入文件生成分片的实现.自动将作业分块 作业分块大 ...

  5. hadoop map(分片)数量确定

    之前学习hadoop的时候,一直希望可以调试hadoop源码,可是一直没找到有效的方法,今天在调试矩阵乘法的时候发现了调试的方法,所以在这里记录下来. 1)事情的起因是想在一个Job里设置map的数量 ...

  6. Hadoop InputFormat OutputFormat

    InputFormat有两个抽象方法: getSplits     createRecordReader   InputSplits 将数据按照Split进行切分,一个Split分给一个task执行. ...

  7. hadoop InputFormat getSplits

    /** Splits files returned by {@link #listStatus(JobConf)} when * they're too big.*/ public InputSpli ...

  8. hadoop之mapreduce详解(进阶篇)

    上篇文章hadoop之mapreduce详解(基础篇)我们了解了mapreduce的执行过程和shuffle过程,本篇文章主要从mapreduce的组件和输入输出方面进行阐述. 一.mapreduce ...

  9. Hadoop学习之旅三:MapReduce

    MapReduce编程模型 在Google的一篇重要的论文MapReduce: Simplified Data Processing on Large Clusters中提到,Google公司有大量的 ...

随机推荐

  1. parcel 在js中导入 html 文件

    parcel不支持将html文件导入为字符串,如果您对parcel使用熟练,直接使用 parcel-plugin-phtml 插件即可,此插件使用 .phtml 后缀 为什么用parcel? 因为从我 ...

  2. SQL死锁操作

    这两天数据库经常被锁,所以记录一下操作: 查看被锁表:select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) ...

  3. bzoj1036 [ZJOI2008]树的统计Count 树链剖分模板题

    [ZJOI2008]树的统计Count Description 一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w.我们将以下面的形式来要求你对这棵树完成 一些操作: I. CHANGE u ...

  4. JS 画框操作

    Js中,我们有时候需要对图片进行操作,包括画框,其实对于UI前端来说挺简单的,没有网上说的那样复杂,这里说明一下 <div style="width:80%;height:300px; ...

  5. An owner of this repository has limited the ability to open a pull request to users that are collaborators on this repository.

    git 无法发起:pull request,提示:An owner of this repository has limited the ability to open a pull request ...

  6. IO字节流概念

    1.输入和输出概念: 输入:硬盘到内存为了使用: 输出:内存到硬盘为了保存: 2.一切皆为字节: 计算机只识别二进制数字,一个字节为8个二进制数字: 存储在硬盘是字节,传输也是字节:

  7. sqflite插件简单使用 key======================

    https://blog.csdn.net/weixin_34183910/article/details/86029912 https://blog.csdn.net/u013255127/arti ...

  8. 996.ICU与死亡因素

    昨天,我为996.ICU这场国际运动贡献了一颗星. 关于这件事,第一想到的就是我们工作的初心是为了更好的生活,工作扼杀生活的不良风气应该坚决抵制. 查了一下,近些年人类正常死亡原因中排名前三的原因为: ...

  9. 与TIME_WAIT相关的几个内核参数

    问题 公司用浏览器访问线上服务一会失败一会成功,通过ssh连接服务器排查时发现ssh也是这样: 检查 抓包后发现建立连接的请求已经到了服务器,但它没有响应: 纠结了好久,后来在腾讯云技术支持及查了相关 ...

  10. 复旦高等代数II(18级)每周一题

    本学期将继续进行高等代数每周一题的活动.计划从第一教学周开始,到第十五教学周结束,每周的周末公布一道思考题(预计15道),供大家思考和解答.每周一题将通过“高等代数官方博客”(以博文的形式)和“高等代 ...