http://hadoop.apache.org/docs/r2.9.1/api/
InputFormat会生成一个RecordReader。
(inputFile)-> InputFormat->List
(InputSplit对应的切片数据) ->RecordReader->
InputSplit
封装了一个切片信息,每一个mapper获得一个InputSplit。
RecordReader
读取InputSplit
中的切片信息,并将切片中的数据分成key/value对提供给mapper输入端;mapper#run方法中的context.nextKeyValue(),context.getCurrentKey(),context.getCurrentValue()最终都会调用RecordReader对应的nextKeyValue(),getCurrentKey(),getCurrentValue()。
来自官方api文档中关于InputFormat的描述。
InputFormat
describes the input-specification for a Map-Reduce job.
The Map-Reduce framework relies on the InputFormat of the job to:
InputSplit
s, each of which is then assigned to an individual Mapper
.RecordReader
implementation to be used to glean input records from the logical InputSplit
for processing by the Mapper
.The default behavior of file-based InputFormat
s, typically sub-classes of FileInputFormat
, is to split the input into logical InputSplit
s based on the total size, in bytes, of the input files. However, the FileSystem
blocksize of the input files is treated as an upper bound for input splits. A lower bound on the split size can be set via mapreduce.input.fileinputformat.split.minsize.
Clearly, logical splits based on input-size is insufficient for many applications since record boundaries are to respected. In such cases, the application has to also implement a RecordReader
on whom lies the responsibility to respect record-boundaries and present a record-oriented view of the logical InputSplit
to the individual task.
InputFormat实现类需要实现:
//Create a record reader for a given split.
RecordReader
//Logically split the set of input files for the job.
List getSplits(JobContext context)
实现了InputFormat
的 getSplits(JobContext context)方法,没有实现 createRecordReader(InputSplit inputSplit, TaskAttemptContext context)方法。
FileInputFormat提供了一个函数isSplitable(JobContext, Path)用来判断输入文件是否可以Split,默认返回true(可以切片)。FileInputFormat的具体实现类需要实现createRecordReader(InputSplit inputSplit, TaskAttemptContext context);可以重写isSplitable(JobContext, Path)方法(如果不可以切片,或者自定义是否可以切片的逻辑);可以重写 getSplits(JobContext context)方法。
public List<InputSplit> getSplits(JobContext job) throws IOException {
//部分代码
//----------------------------------------切片大小,单位是字节(B)----------------------------------------
long minSize = Math.max(getFormatMinSplitSize()/*long(1)*/, getMinSplitSize(job)/*job conf中可以设置最小切片大小,默认long(1)*/);
//job conf中可以设置最大切片大小,默认long型最大值
long maxSize = getMaxSplitSize(job);
//获取输入目录下的所有文件的信息。
List<FileStatus> files = listStatus(job);
//遍历文件
for (FileStatus file: files) {
//获取文件所在的块信息
if (file instanceof LocatedFileStatus) {
//从本地文件系统获取
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
//从hdfs获取
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
//如果当前处理文件所需的减压器支持切片
if (isSplitable(job, path)) {
//获取块大小
long blockSize = file.getBlockSize();
//使用公式Math.max(minSize, Math.min(maxSize, blockSize))计算切片大小。
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining = length;
//写入切片信息
//写入时需要判断bytesRemaining/splitSize > SPLIT_SLOP
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP/*1.1*/) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
//写入切片信息
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
}
//如果当前环境配置不支持切片,直接将整个文件作为一个切片。
else {
// not splitable
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
}
}
CombineFileInputFormat
重写了FileInputFormat
的 getSplits(JobContext context)和isSplitable(JobContext, Path),没有实现 createRecordReader(InputSplit inputSplit, TaskAttemptContext context)。意味着CombineFileInputFormat有自己的切片和判断文件是否可以切片的逻辑。
该类的具体实现类需要实现createRecordReader(InputSplit inputSplit, TaskAttemptContext context)方法。
CombineFileInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个MapTask处理。
生成切片过程包括:虚拟存储过程和切片过程二部分。
(1)虚拟存储过程:
(2)切片过程:
测试举例:有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件,则虚拟存储之后形成6个文件块,大小分别为:
1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)最终会形成3个切片,大小分别为:(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M
@Override
protected boolean isSplitable(JobContext context, Path file) {
final CompressionCodec codec =
new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
if (null == codec) {
return true;
}
return codec instanceof SplittableCompressionCodec;
}
TextInputFormat
是默认的FileInputFormat
具体实现类。
实现了createRecordReader(InputSplit inputSplit, TaskAttemptContext context)返回一个LineRecordReader
重写了isSplitable(JobContext, Path)。
没有重写 getSplits(JobContext context),因此该类的切片机制同其父类FileInputFormat。isSplitable方法逻辑同CombineFileInputFormat#isSplitable()。
为该行在文件中的起始字节偏移量。
该行的内容,不包括换行符和回车符。
例如输入文件为:
hello
word
则每条记录的(key,value)对:
(0,hello)
(6,word)
KeyValueTextInputFormat
是FileInputFormat
的具体实现类。
实现了createRecordReader(InputSplit inputSplit, TaskAttemptContext context)返回一个KeyValueLineRecordReader
重写了isSplitable(JobContext, Path);isSplitable方法逻辑同CombineFileInputFormat#isSplitable()。
没有重写 getSplits(JobContext context),因此该类的切片机制同其父类FileInputFormat。
每一行均为一条记录,被分隔符成Key,value;可以配置分隔符(默认为"Tab")。
该记录被分隔符分隔的前一部分。
该记录被分隔符分隔的后一部分。
例如输入文件:
//两单词空格部分为“tab”符。
hello word
hadoop mapreduce
则每条记录的(key,value)对:
(hello,word)
(hadoop,mapreduce)
是FileInputFormat
具体实现类。
实现了createRecordReader(InputSplit inputSplit, TaskAttemptContext context)返回一个LineRecordReader
重写了getSplits(JobContext context),不再按BlockSize进行切片,而是按NlineInputFormat指定的行数N进行切片。每一个文件仍然和FileInputFormat一样单独处理。
例如输入文件:
hello
word
hadoop
mapreduce
yarn
如果N=2则将该文件切成3个逻辑切片。
InputSplit1:
hello
word
该InputSplit的(key,value)为:
(0,hello)
(6,word)
InputSplit2:
hadoop
mapreduce
InputSplit3
yarn
CombineTextInputFormat
是CombineFileInputFormat
的具体实现类。
没有重写**getSplits**(JobContext context),切片机制同父类。
没有重写isSplitable(JobContext, Path),判断是否可切片逻辑同父类。
实现了createRecordReader(InputSplit inputSplit, TaskAttemptContext context)返回一个CombineFileRecordReader
http://ercoppa.github.io/HadoopInternals/MapReduceInput.html