Hadoop InputFormat 输入文件分片
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 输入文件分片的更多相关文章
- Hadoop InputFormat浅析
本文转载:http://hi.baidu.com/_kouu/item/dc8d727b530f40346dc37cd1 在执行一个Job的时候,Hadoop会将输入数据划分成N个Split,然后启动 ...
- Hadoop InputFormat详解
InputFormat是MapReduce编程模型包括5个可编程组件之一,其余4个是Mapper.Partitioner.Reducer和OutputFormat. 新版Hadoop InputFor ...
- Hadoop InputFormat
Hadoop可以处理不同数据格式(数据源)的数据,从文本文件到(非)关系型数据库,这很大程度上得益于Hadoop InputFormat的可扩展性设计,InputFormat层次结构图如下:
- hadoop InputFormat 类别
FileInputFormat是所有使用文件作为数据源的InputFormat的积累.它提供两个功能:一个是定义哪些文件包含在一个作业的输入中:一个为输入文件生成分片的实现.自动将作业分块 作业分块大 ...
- hadoop map(分片)数量确定
之前学习hadoop的时候,一直希望可以调试hadoop源码,可是一直没找到有效的方法,今天在调试矩阵乘法的时候发现了调试的方法,所以在这里记录下来. 1)事情的起因是想在一个Job里设置map的数量 ...
- Hadoop InputFormat OutputFormat
InputFormat有两个抽象方法: getSplits createRecordReader InputSplits 将数据按照Split进行切分,一个Split分给一个task执行. ...
- hadoop InputFormat getSplits
/** Splits files returned by {@link #listStatus(JobConf)} when * they're too big.*/ public InputSpli ...
- hadoop之mapreduce详解(进阶篇)
上篇文章hadoop之mapreduce详解(基础篇)我们了解了mapreduce的执行过程和shuffle过程,本篇文章主要从mapreduce的组件和输入输出方面进行阐述. 一.mapreduce ...
- Hadoop学习之旅三:MapReduce
MapReduce编程模型 在Google的一篇重要的论文MapReduce: Simplified Data Processing on Large Clusters中提到,Google公司有大量的 ...
随机推荐
- PHP----------PHP自身的性能优化注意事项
1.如果能将类的方法定义成static,就尽量定义成static,它的速度会提升将近4倍. 2.$row[’id’] 的速度是$row[id]的7倍. 3.echo 比 print 快,并且使用ech ...
- WPF中使用DataGrid时操作列按钮问题
在使用DataGrid的过程中,我们有时候需要对选取的某一行数据进行多个操作,这个时候操作列只有一个按钮显然无法满足我们的要求,我们需要多个按钮才能达到我们的目的. UI页面代码: <Grid& ...
- qt 画多边形(实现鼠标拖动节点)
---恢复内容开始--- 2018-01-06 这个小例子实现了移动鼠标,鼠标的坐标信息跟随鼠标移动,多边形的实现,鼠标点击可以拖动多边形点的位置,(其中有个问题?我在QMainWindow下,用mo ...
- C#调用Microsoft.DirectX.DirectSound问题记录及解决
问题1:初始化结构体WaveFormat或其他变量时卡死 修改App.config,修改方法如下: 原App.config <?xml version="1.0" encod ...
- IT题库9-线程池的概念和原理
在什么情况下使用线程池? 1.单个任务处理的时间比较短:2.需要处理的任务的数量大: 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销.2.如不使用线程池,有可能造成系统创建 ...
- 2017-2018-2 20155228 《网络对抗技术》 实验五:MSF基础应用
2017-2018-2 20155228 <网络对抗技术> 实验五:MSF基础应用 1. 实践内容 本实践目标是掌握metasploit的基本应用方式,重点常用的三种攻击方式的思路.具体需 ...
- 2018-2019-2 《网络对抗技术》Exp2 后门原理与应用 20165215
目录 实验内容 基础问题回答 常用后门工具 Netcat windows 获取 linux 的shell linux 获取 winsdows 的shell 使用nc传输数据 使用nc传文件 Socat ...
- 前端使用node.js的http-server开启一个本地服务器
前端使用node.js的http-server开启一个本地服务器 在写前端页面中,经常会在浏览器运行HTML页面,从本地文件夹中直接打开的一般都是file协议,当代码中存在http或https的链接时 ...
- 用iPhone查看pc电脑上写的html(Mac电脑Charles)简单版
对于客户端同学开发来说,写一段代码想在真机上看看,是非常容易的. 那么在这么一个大前端的环境下,客户端开发想写点html和js代码,又想在手机上看看效果,怎么办呢? 需要以下几个步骤: 大体流程:1. ...
- Java的流
Java中的流主要是用来读写文件的.只要明确你是读文件还是写文件就可以确定使用哪种流了.读:read 写:writer 1.读文件:filereader这个类,顾名思义就知道是读文件了. 2.写文 ...