关于大量小文件的优化策略。
背景
默认情况下 TextInputformat 对任务的切片机制是按文件规划切片,不管文件多小,都会 是一个单独的切片,都会交给一个 maptask,这样如果有大量小文件,就会产生大量的 maptask, 处理效率极其低下。
优化
预处理
最好的办法,在数据处理系统的最前端(预处理/采集),将小文件先合并成大文 件,再上传到 HDFS 做后续分析。
CombineTextInputFormat
如果已经是大量小文件在 HDFS 中了,可以使用另一种 InputFormat
来做切片(CombineTextInputFormat
),它的切片逻辑跟 TextFileInputFormat
不同:它可以将 多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 maptask
。
优先满足最小切片大小,不超过最大切片大小 。
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
举例:0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m
具体配置
// 如果不设置 InputFormat,它默认用的是 TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
Demo
准备了5个小文件,如下:
☁ CombineTextInputFormat pwd
/Users/ylj/demo/input/CombineTextInputFormat
☁ CombineTextInputFormat ll
total 19400
-rw-r--r-- 1 ylj staff 176K 4 7 15:35 flie1.txt
-rw-r--r-- 1 ylj staff 879K 4 7 15:39 flie2.txt
-rw-r--r-- 1 ylj staff 1.1M 4 7 15:39 flie3.txt
-rw-r--r-- 1 ylj staff 2.1M 4 7 15:41 flie4.txt
-rw-r--r-- 1 ylj staff 3.3M 4 7 15:42 flie5.txt
测试默认配置
测试CombineTextInputFormat
仅设置setInputFormatClass
//设置InputFormat
job.setInputFormatClass(CombineTextInputFormat.class);
此时
SPLIT_MAXSIZE = Long.MAXValue
SPLIT_MINSIZE = 1
5个文件相加在此区间内,所以number of splits:1
设置SPLIT_MAXSIZE、SPLIT_MINSIZE
//设置InputFormat
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
此时
- 判断虚拟存储的文件大小是否大于
setMaxInputSplitSize
值,大于等于则单独形成一个切片; - 如果不大于则跟下一个存储文件进行合并,共同形成一个切片。
源码
文件:/org/apache/hadoop/mapreduce/lib/input/CombineFileInputFormat.java
if (maxSize == 0) {
myLength = left;
} else {
if (left > maxSize && left < 2 * maxSize) {
// if remainder is between max and 2*max - then
// instead of creating splits of size max, left-max we
// create splits of size left/2 and left/2. This is
// a heuristic to avoid creating really really small
// splits.
myLength = left / 2;
} else {
myLength = Math.min(maxSize, left);
}
}