问题?
每一个比block size小的文件都会消耗掉一个完整block的分配,但是磁盘空间的实际占用是基于文件的大小,而不要误认为小文件会消耗掉一整个块的存储空间。
每个块都会消耗NameNode节点一定数量的内存。
NameNode能够寻址固定数量的块个数,这需要视NameNode节点的内存大小而定。
在Hadoop集群上的这些小文件会不断触及NameNode节点的内存限制。
很快问题就出现了,我们不能够再给Hadoop集群添加数据了,因为NameNode已经没有内存空间分配新的block了。
让我用一个简单的例子总结一下上面的问题。
我们有1000个小文件,每个小文件都是100KB。每个小文件都需要一个block来存储。那么NameNode就需要给分配1000个block,然而实际上消耗的存储空间只有1000*100KB(replication = 1)。
但是,如果我们将这些小文件合并为一个1000*100KB大小的文件,那么在NameNode上只会消耗一个block。
情况是NameNode需要将所有block的信息保存在内存中,因此和保存一个合并后的block信息不同的是,我们需要在NameNode的内存中消耗掉1000个block的空间。
解决方案
解决的方法相当直接明了,就是将小的文件合并为更大的文件以至于他们可以占满一个block的大小。
我在下面列举了一些解决办法,你可以根据自己的需要进行选择
1) HDFSConcat
如果文件都有相同的block size和复制因子,那么可以使用HDFS指定的API(HDFSConcat)将2个文件连接为一个文件,而且不需要复制块。
http://svn.apache.org/repos/asf/hadoop/common/trunk/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/tools/HDFSConcat.java
https://issues.apache.org/jira/browse/HDFS-222
2) IdentityMapper and IdentityReducer
使用IdentityMapper和IdentityReducer写一个MR任务,但是会产生更少的输出文件,可以通过设置reducers的个数来完成。
3) FileUtil.copyMerge
http://hadoop.apache.org/docs/current/api/org/apache/hadoop/fs/FileUtil.html#copyMerge
4) Hadoop File Crush
http://www.jointhegrid.com/hadoop_filecrush/index.jsp
Sample invocation
hadoop jar filecrush-2.0-SNAPSHOT.jar crush.Crush -Ddfs.block.size=134217728 \
--input-format=text \
--output-format=text \
--compress=none \
/path/to/my/input /path/to/my/output/merge 20100221175612
这里134217728是一个随机的时间戳,可以表示为运行脚本的时间。
阅读下面的文档了解详细的帮助,我也推荐用这个工具解决小文件的问题
http://www.jointhegrid.com/svn/filecrush/trunk/src/main/resources/help.txt
更多的参考可以查看:
http://stackoverflow.com/questions/11368907/hadoop-block-size-and-file-size-issue
http://blog.cloudera.com/blog/2009/02/the-small-files-problem/
http://www.ibm.com/developerworks/web/library/wa-introhdfs/
http://amilaparanawithana.blogspot.com.au/2012/06/small-file-problem-in-hadoop.html
http://www.quora.com/How-can-I-merge-files-in-HDFS