一、源码
public List<InputSplit> getSplits(JobContext job) throws IOException {
long minSize = Math.max(this.getFormatMinSplitSize(), getMinSplitSize(job));
long maxSize = getMaxSplitSize(job);
List<InputSplit> splits = new ArrayList();
List<FileStatus> files = this.listStatus(job); // 1. 获取input目录下的所有文件
Iterator i$ = files.iterator();
// 2. 遍历目录下的所有文件
while(true) {
while(i$.hasNext()) {
FileStatus file = (FileStatus)i$.next();
Path path = file.getPath();
FileSystem fs = path.getFileSystem(job.getConfiguration());
long length = file.getLen(); // 获取文件大小
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0L, length);
if(length != 0L && this.isSplitable(job, path)) {
long blockSize = file.getBlockSize();
//计算切片大小 默认情况下splitSize=blockSize=128M
long splitSize = this.computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining;
//假设这个文件大小是300M:aa.txt
//开始切片,形成第1个切片:aa.txt—0:128M
//第2个切片aa.txt—128:256M
//第3个切片aa.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)
for(bytesRemaining = length; (double)bytesRemaining / (double)splitSize > 1.1D; bytesRemaining -= splitSize) {
int blkIndex = this.getBlockIndex(blkLocations, length - bytesRemaining);
//将切片信息写到一个切片规划文件中
splits.add(new FileSplit(path, length - bytesRemaining, splitSize, blkLocations[blkIndex].getHosts()));
}
if(bytesRemaining != 0L) {
splits.add(new FileSplit(path, length - bytesRemaining, bytesRemaining, blkLocations[blkLocations.length - 1].getHosts()));
}
} else if(length != 0L) {
//将切片信息写到一个切片规划文件中
splits.add(new FileSplit(path, 0L, length, blkLocations[0].getHosts()));
} else {
//将切片信息写到一个切片规划文件中
splits.add(new FileSplit(path, 0L, length, new String[0]));
}
}
job.getConfiguration().setLong("mapreduce.input.num.files", (long)files.size());
LOG.debug("Total # of splits: " + splits.size());
return splits;//3. 整个切片的核心过程在getSplit()方法中完成
}
//4. 数据切片只是在逻辑上对输入数据进行分片,并不会再磁盘上将其切分成分片进行存储。
//InputSplit只记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等。
//5. 注意:block是HDFS上物理上存储的存储的数据,切片是对数据逻辑上的划分。
}
//提交切片规划文件到yarn上,yarn上的MrAppMaster就可以根据切片规划文件计算开启maptask个数。
二、FileInputFormat切片规则描述
- 默认的切片机制:
- (1)简单地按照文件的内容长度进行切片
- (2)切片大小,默认等于block大小
- (3)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片
比如待处理数据有两个文件:
file1.txt 320M
file2.txt 10M
经过FileInputFormat的切片机制运算后,形成的切片信息如下:
file1.txt.split1-- 0~128
file1.txt.split2-- 128~256
file1.txt.split3-- 256~320
file2.txt.split1-- 0~10M
- FileInputFormat切片大小的参数配置
- 通过分析源码,在FileInputFormat中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize));
切片主要由这几个值来运算决定
mapreduce.input.fileinputformat.split.minsize=1 默认值为1
mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认值Long.MAXValue
因此,默认情况下,切片大小=blocksize。
maxsize(切片最大值):参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值。
minsize (切片最小值):参数调的比blockSize大,则可以让切片变得比blocksize还大。
- 获取切片信息API
// 根据文件类型获取切片信息
FileSplit inputSplit = (FileSplit) context.getInputSplit();
// 获取切片的文件名称
String name = inputSplit.getPath().getName();
三、FileInputFormat源码图片流程描述
四、CombineTextInputFormat切片机制
- 关于大量小文件的优化策略
- 1)默认情况下TextInputformat对任务的切片机制是按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个maptask,这样如果有大量小文件,就会产生大量的maptask,处理效率极其低下。
- 2)优化策略
- (1)最好的办法,在数据处理系统的最前端(预处理/采集),将小文件先合并成大文件,再上传到HDFS做后续分析。
- (2)补救措施:如果已经是大量小文件在HDFS中了,可以使用另一种InputFormat来做切片(CombineTextInputFormat),它的切片逻辑跟TextFileInputFormat不同:它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个maptask。
- (3)优先满足最小切片大小,不超过最大切片大小
- CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
- CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
- 举例:0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m
- 3)具体实现步骤
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m