为什么hdfs不适合小文件的存储?
1.因namenode将文件系统的元数据存放在内存中,因此存储的文件数目受限于 namenode的内存大小。HDFS中每个文件、目录、数据块占用150Bytes。如果存放1million的文件至少消耗300MB内存,如果要存 放1billion的文件数目的话会超出硬件能力
2.HDFS适用于高吞吐量,而不适合低时间延迟的访问。如果同时存入1million的files,那么HDFS 将花费几个小时的时间。
3.流式读取的方式,不适合多用户写入,以及任意位置写入。如果访问小文件,则必须从一个datanode跳转到另外一个datanode,这样大大降低了读取性能。
4.延长MapReduce作业的总运行时间,小文件随机寻址很慢
HDFS自带的小文件存储解决方案
hadoop自带了三种解决小文件的方案:Hadoop Archive、 Sequence File 和 CombineFileInputFormat
Hadoop Archive
Hadoop Archive或者HAR,是一个高效地将小文件放入HDFS块中的文件存档工具,它能够将多个小文件打包成一个HAR文件,这样在减少namenode内存使用的同时,仍然允许对文件进行透明的访问。
使用方法:
对某个目录/foo/bar下的所有小文件存档成/outputdir/ zoo.har:
hadoop archive -archiveName zoo.har -p /foo/bar /outputdir
查看看
hadoop dfs -ls har:///user/zoo/foo.har
存在的问题
- 1、存档文件的源文件目录以及源文件都不会自动删除需要手动删除
- 2、存档的过程实际是一个mapreduce过程,所以需要需要hadoop的mapreduce的支持
- 3、存档文件本身不支持压缩
- 4、存档文件一旦创建便不可修改,要想从中删除或者增加文件,必须重新建立存档文件
- 5、创建存档文件会创建原始文件的副本,所以至少需要有与存档文件容量相同的磁盘空间
Sequence File
sequence file由一系列的二进制的对组成,其中key为小文件的名字,value的file content。
,它可以在map/reduce过程中的input/output 的format时被使用。在map/reduce过程中,map处理文件的临时输出就是使用SequenceFile处理过的。
SequenceFile分别提供了读、写、排序的操作类。
SequenceFile提供了三种压缩方式:
- 不采用压缩:CompressionType.NONE
- key/value值都压缩的方式存储:CompressionType.RECORD
- 压缩value值不压缩key值存储的存储方式:CompressionType.BLOCK
使用方式
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.SequenceFile.CompressionType;
import org.apache.hadoop.io.SequenceFile.Reader;
import org.apache.hadoop.io.SequenceFile.Writer;
import org.apache.hadoop.io.Text;
/**
* @version 1.0
* @author Fish
*/
public class SequenceFileWriteDemo {
private static final String[] DATA = { "fish1", "fish2", "fish3", "fish4" };
public static void main(String[] args) throws IOException {
/**
* 写SequenceFile
*/
String uri = "/test/fish/seq.txt";
Configuration conf = new Configuration();
Path path = new Path(uri);
IntWritable key = new IntWritable();
Text value = new Text();
Writer writer = null;
try {
/**
* CompressionType.NONE 不压缩<br>
* CompressionType.RECORD 只压缩value<br>
* CompressionType.BLOCK 压缩很多记录的key/value组成块
*/
writer = SequenceFile.createWriter(conf, Writer.file(path), Writer.keyClass(key.getClass()),
Writer.valueClass(value.getClass()), Writer.compression(CompressionType.BLOCK));
for (int i = 0; i < 4; i++) {
value.set(DATA[i]);
key.set(i);
System.out.printf("[%s]\t%s\t%s\n", writer.getLength(), key, value);
writer.append(key, value);
}
} finally {
IOUtils.closeStream(writer);
}
/**
* 读SequenceFile
*/
SequenceFile.Reader reader = new SequenceFile.Reader(conf, Reader.file(path));
IntWritable key1 = new IntWritable();
Text value1 = new Text();
while (reader.next(key1, value1)) {
System.out.println(key1 + "----" + value1);
}
IOUtils.closeStream(reader);// 关闭read流
/**
* 用于排序
*/
// SequenceFile.Sorter sorter = new SequenceFile.Sorter(fs, comparator, IntWritable.class, Text.class, conf);
}
}
存在的问题
优点
- 1支持压缩,且可定制为基于Record或Block压缩(Block级压缩性能较优)
- 2本地化任务支持:因为文件可以被切分,因此MapReduce任务时数据的本地化情况应该是非常好的。
缺点
需要一个合并文件的过程,且合并后的文件将不方便查看。
CombineFileInputFormat
Hadoop内置提供了一个 CombineFileInputFormat 类来专门处理小文件,其核心思想是:根据一定的规则,将HDFS上多个小文件合并到一个 InputSplit中,然后会启用一个Map来处理这里面的文件,以此减少MR整体作业的运行时间。
CombineFileInputFormat类继承自FileInputFormat,主要重写了List getSplits(JobContext job)方法;这个方法会根据数据的分布,mapreduce.input.fileinputformat.split.minsize.per.node、mapreduce.input.fileinputformat.split.minsize.per.rack以及mapreduce.input.fileinputformat.split.maxsize 参数的设置来合并小文件,并生成List。其中mapreduce.input.fileinputformat.split.maxsize参数至关重要,下面是分区规则
- 当mapreduce.input.fileinputformat.split.maxsize > mapreduce.input.fileinputformat.split.minsize > dfs.blockSize的情况下,此时的splitSize 将由mapreduce.input.fileinputformat.split.minsize参数决定。
- 当mapreduce.input.fileinputformat.split.maxsize > dfs.blockSize > mapreduce.input.fileinputformat.split.minsize的情况下,此时的splitSize 将由dfs.blockSize配置决定。(第二次优化符合此种情况)
- 当dfs.blockSize > mapreduce.input.fileinputformat.split.maxsize > mapreduce.input.fileinputformat.split.minsize的情况下,此时的splitSize 将由mapreduce.input.fileinputformat.split.maxsize参数决定。
有以上的计算规则最终确定了CombineFileInputFormat分区的大小。
其中
mapreduce.input.fileinputformat.split.minsize设置,默认是1,最大字节数由mapreduce.input.fileinputformat.split.maxsize设置,默认是Long.MAX_VALUE。
用公式计算就是:
splitSize = Math.max(minSize, Math.min(maxSize, blockSize))
使用
CombineTextInputFormat使用起来特别简单:
1.在conf设置
Configuration conf = new Configuration(getConf());
conf.set("mapreduce.input.fileinputformat.split.maxsize", ONE_MB * 32);
2.在inputformat中设置
setInputFormatClass(CombineTextInputFormat.class);
引用:
https://www.iteblog.com/archives/2139.html
http://www.open-open.com/lib/view/open1330605869374.html