为什么需要在HDFS中处理小文件?(为什么HDFS不适合处理大量的小文件)
- 在HDFS中,每一个block,文件夹或者目录都占用150字节的内存。无论为一个block的大小为1KB或者128MB,都将占用128MB的内存。在HDFS中存储大量的小文件意味着Namenode中需要存储大量的block元数据。由于Namenode的内存是有限的,在集群中存储大量的小文件可能导致内存很快被消耗完。
- 在mapreduce计算阶段,由于maptask的数量等同于一个job中输入数据的切片数量(block数量)。如果输入数据是大量的小文件,就需要开启大量的maptask来映射数据。每开启一个maptask都需要启动一次JVM,从来带来大量JVM启动开销。最终导致job需要花费更多的的时间
如何解决?
- archieve归档(har文件)
#将一些小文件归档成har文件,对外为一个整体,对内依然为一些小文件
$ bin/hadoop achieve -achievename 文件名.har -p src dst
#解归档
$ hadoop fs -cp har://src/文件名.har/* dstsrc
#原始的小文件需要手动删除,har文件不可修改
#若要删除har文件,只能通过-rmr
$ hadoop fs -rmr src/文件名.har
#har文件作为mapreduce阶段的的输入数据
$ hadoop jar包 har://src/文件名.har/* dst
该方法在一定程度上节约了Namenode的内存。但在mapreduce计算阶段,依然按照原始的小文件切片,需开启大量的maptask。
- Sequence Files:文件名作为key,文件内容作为value
public static void seqFileWrite(String[] args) throws IOException {
Text key = new Text();
BytesWritable value = new BytesWritable();
//获取配置对象
Configuration conf = new Configuration();
//输出路径及文件名(.seq类型)
Path dst = new Path(args[0]);
SequenceFile.Writer.Option optionPath = SequenceFile.Writer.file(dst);
//配置kv类型
SequenceFile.Writer.Option optionKey = SequenceFile.Writer.keyClass(key.getClass());
SequenceFile.Writer.Option optionValue = SequenceFile.Writer.valueClass(value.getClass());
//获取writer
SequenceFile.Writer writer = SequenceFile.createWriter(conf, optionPath, optionKey, optionValue);
//输入路径
File src = new File(args[1]);
//循环读取文件并写入
for (File file : src.listFiles()) {
//封装kv
key.set(file.getName());
value.set(FileUtils.readFileToByteArray(file), 0, FileUtils.readFileToByteArray(file).length);
//写入kv
writer.append(key, value);
}
//关闭资源
IOUtils.closeStreams(writer); //writer.close();
}
public static void seqFileRead(String[] args) throws IOException {
//配置文件对象
Configuration conf = new Configuration();
//Reader配置
//读取路径
Path src = new Path(args[0]);
SequenceFile.Reader.Option optionPath = SequenceFile.Reader.file(src);
//获取Reader
SequenceFile.Reader reader = new SequenceFile.Reader(conf, optionPath);
//获取kv类型
Writable key = (Writable) ReflectionUtils.newInstance(reader.getKeyClass(), conf);
BytesWritable value = (BytesWritable) ReflectionUtils.newInstance(reader.getValueClass(), conf);
while (reader.next(key, value)) {
//打印kv
System.out.println(key);
String decode = Text.decode(value.getBytes()); //BytesWritable转换为Text类型
System.out.println(decode);
}
//关闭资源
IOUtils.closeStream(reader); //reader.close();
}
- CombineInputFormat
CombineTextInputFormat.setMaxInputSplitSize(job, 1024 * 1024 * 128); //128MB
job.setInputFormatClass(CombineTextInputFormat.class);
- 开启JVM宠用
#mapredu-site.xml
<property>
<name>mapred.job.reuse.jvm.num.tasks</name>
<value>-1</value>
</property>