存在的问题
HDFS设计是用来存储海量数据的,特别适合存储TB、PB量级别的数据。但是随着时间的推移,HDFS上可能会存在大量的小文件,这里说的小文件指的是文件大小远远小于一个HDFS块(128MB)的大小;HDFS上存在大量的小文件至少会产生以下影响:
消耗NameNode大量的内存
延长MapReduce作业的总运行时间
因为MapReduce框架默认的 TextInputFormat 切片机制是对任务按文件规划切片,如果有大量小文件,就会产生大量的 MapTask,处理小文件效率非常低。
解决方案
Hadoop内置提供了一个CombineTextInputFormat类来专门处理小文件,其核心思想是:根据一定的规则,将HDFS上多个小文件合并到一个 InputSplit中,然后会启用一个Map来处理这里面的文件,以此减少MR整体作业的运行时间。
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
切片机制过程
CombineTextInputForma切片机制过包括:虚拟存储过程和切片过程二部分
假设 setMaxInputSplitSize 值为 4M,有如下四个文件
a.txt 1.7M
b.txt 5.1M
c.txt 3.4M
d.txt 6.8M
(1)虚拟存储过程
(1.1)将输入目录下所有文件大小,依次和设置的 setMaxInputSplitSize 值比较,如果不大于设置的最大值,逻辑上划分一个块。
(1.2)如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块,当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。
1.7M < 4M 划分一块
5.1M > 4M 但是小于 2*4M 划分二块:块1=2.55M,块2=2.55M
3.4M < 4M 划分一块
6.8M > 4M 但是小于 2*4M 划分二块:块1=3.4M,块2=3.4M
最终存储的文件:
1.7M
2.55M,2.55M
3.4M
3.4M,3.4M
(2)切片过程
(2.1)判断虚拟存储的文件大小是否大于 setMaxIputSplitSize 值,大于等于则单独形成一个切片。
(2.2)如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
最终会形成3个切片:
(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M
代码举例:
main方法中:
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
总结:
- 默认读取数据机制的弊端
-
默认TextInputFormat 逐个遍历文件 意味着不管文件多大 至少自己本身都是一个切片
如果小文件过多 势必会形成很多个切片 就会启动多个maptask 浪费资源。 - CombineTextInputFormat
- 设置一个切片的最大值
-
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
- 首先,虚拟存储过程
-
遍历每一个小文件 跟设置值进行比较
如果小于 自己本身就是一个
如果大于 不超过2倍 就把该文件平均
如果大于 超过2倍 就按照最大值切
- 然后,切片规划的过程
-
上一步虚拟存储的结果 逐个判断 如果不超过设置的值 就和后面的合并 合并之后如果依然没超过,继续合并 直到超过设置最大值。形成切片。
-
针对小文件 ,最佳的处理方式 还是上传hdfs之前进行合并,目标--->block size