hadoop hdfs 被設(shè)計(jì)用作海量數(shù)據(jù)存儲(chǔ),適合存儲(chǔ)大文件,文件在hdfs中是以block的形式存儲(chǔ)的,在hadoop 1.x中,hdfs block的默認(rèn)大小為64m,而在hadoop 2.x中,block的默認(rèn)大小為128m,可以在hdfs-site.xml文件中的dfs.block.size配置項(xiàng)修改默認(rèn)的塊大小。文件由一個(gè)或多個(gè)block組成,文件的元數(shù)據(jù)信息由namenode記錄,因此如果hdfs存儲(chǔ)大量的小文件時(shí),會(huì)占用大量的block以及namenode必須耗費(fèi)大量內(nèi)存來記錄這些文件的元數(shù)據(jù),造成存儲(chǔ)空間浪費(fèi)以及影響hdfs 集群的橫向擴(kuò)展。因此以下兩種方案可以用來處理hdfs 小文件的問題:
1.sequencefile
2.hadoop archives file
SequenceFile
sequencefile 由header和一個(gè)個(gè)記錄組成,header記錄著keyclass 類型,valueclass 類型,壓縮信息以及用戶自定義的信息,記錄record存儲(chǔ)的是真正的數(shù)據(jù)并以key-value的格式進(jìn)行存儲(chǔ),sequencefile文件按壓縮可分為無壓縮格式,記錄壓縮格式和塊壓縮格式。無壓縮格式和記錄壓縮格式相似,唯一的區(qū)別是記錄壓縮格式是值壓縮,格式如下圖所示:

而塊壓縮是對(duì)record進(jìn)行壓縮,一個(gè)塊由多個(gè)record組成,當(dāng)一個(gè)record的大小達(dá)到io.seqfile.compress.blockseze 默認(rèn)1000000字節(jié)時(shí),可加入到塊中,格式如圖所示:

示例代碼
package com.zjc.spark;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
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.Text;
import org.apache.hadoop.io.compress.DefaultCodec;
import org.apache.hadoop.util.LineReader;
import java.io.File;
import java.io.FileInputStream;
/**
* Created by zjc on 2018/11/14.
*/
public class sparkApplication1 {
static Configuration configuration = null;
static {
configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://z-cluster");
configuration.set("dfs.nameservices", "z-cluster");
configuration.set("dfs.ha.namenodes.z-cluster", "nn1,nn2");
configuration.set("dfs.namenode.rpc-address.z-cluster.nn1", "192.168.1.22:8120");
configuration.set("dfs.namenode.rpc-address.z-cluster.nn2", "192.168.1.107:8120");
configuration.set("dfs.client.failover.proxy.provider.z-cluster", "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider");
configuration.set("hadoop.user.name", "hadoop4.27");
}
public static void main6(String[] args) {
IntWritable key = new IntWritable();
Text value = new Text();
SequenceFile.Writer out = null;
try {
FileSystem fileSystem = FileSystem.get(configuration);
out = SequenceFile.createWriter(configuration, SequenceFile.Writer.file(new Path("/testFile")), SequenceFile.Writer.keyClass(IntWritable.class), SequenceFile.Writer.valueClass(Text.class), SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new DefaultCodec()));
for (int i = 0; i < 100; i++) {
key.set(100 - i);
value.set(DATA[i % DATA.length]);
out.append(key, value);
if (i % 20 == 0) {
out.sync();//每四百條記錄添加一個(gè)同步點(diǎn)
}
}
} catch (Exception e) {
System.out.println(e);
} finally {
IOUtils.closeStream(out);
}
}
public static void main18(String[] args) {
IntWritable key = new IntWritable();
Text value = new Text();
SequenceFile.Reader in = null;
try {
in = new SequenceFile.Reader(configuration, SequenceFile.Reader.file(new Path("/testFile")));
// in.sync(2129);
long position = in.getPosition();
while (in.next(key, value)) {
System.out.println("position:" + position + " key:" + key.get() + " value:" + value.toString());
position = in.getPosition();
}
} catch (Exception e) {
System.out.println(e);
} finally {
IOUtils.closeStream(in);
}
}
}
可通過mr將多個(gè)小文件合并成一個(gè)sequencefile文件,但是sequencefile的缺點(diǎn)是不支持追加。
Hadoop Archives File
可通過hdfs shell命令將多個(gè)小文件創(chuàng)建為歸檔文件,歸檔示例:
創(chuàng)建歸檔文件
hadoop archive -archiveName foo.har -p /user/hadoop -r 3 dir1 dir2 /user/zoo
上面的例子使用 /user/hadoop 作為創(chuàng)建歸檔的相對(duì)歸檔目錄。/user/hadoop/dir1 和 /user/hadoop/dir2 目錄將會(huì)歸檔到 /user/zoo/foo.har 里面。歸檔操作并不會(huì)刪除輸入文件。如果你想在創(chuàng)建歸檔文件之后刪除這些輸入文件,你需要自己做。在這個(gè)例子中,因?yàn)槲覀冎付?-r 3,那么副本因子為3將會(huì)被使用。
查找文件
在 hadoop 檔案中查找文件就像在文件系統(tǒng)上執(zhí)行 ls 一樣簡單。在我們歸檔完 /user/hadoop/dir1 和 /user/hadoop/dir2 目錄,如果我們想查看歸檔里面有哪些文件,你僅僅需要使用下面命令:
hdfs dfs -ls -R har:///user/zoo/foo.har/
要理解-p 參數(shù)的重要性,讓我們?cè)倏匆槐樯厦娴睦印?如果您只是在 hadoop 存檔上使用 ls(而不是lsr)
hdfs dfs -ls har:///user/zoo/foo.har
輸出如下:
har:///user/zoo/foo.har/dir1
har:///user/zoo/foo.har/dir2
您可以回憶一下使用以下命令創(chuàng)建存檔
hadoop archive -archiveName foo.har -p /user/hadoop dir1 dir2 /user/zoo
如果我們將上面命令修改為下:
hadoop archive -archiveName foo.har -p /user/ hadoop/dir1 hadoop/dir2 /user/zoo
那么在 Hadoop 歸檔上如下使用 ls 命令:
hdfs dfs -ls har:///user/zoo/foo.har
那么你會(huì)得到如下結(jié)果:
har:///user/zoo/foo.har/hadoop/dir1
har:///user/zoo/foo.har/hadoop/dir2
請(qǐng)注意,已歸檔文件已相對(duì)于 /user/ 而不是/ user/hadoop 進(jìn)行歸檔。
Hadoop Archives 和 MapReduce
在 MapReduce 中使用 Hadoop Archives 就像使用默認(rèn)文件系統(tǒng)中的文件一樣簡單。 如果我們?cè)?HDFS 上的 /user/zoo/foo.har 路徑里面存儲(chǔ)了 Hadoop 歸檔文件,那么在 MapReduce 里面將它作為輸入文件可以使用 har:///user/zoo/foo.har。
Hadoop Archives 是根據(jù)索引文件對(duì)目標(biāo)文件進(jìn)行讀取,所以讀性能比正常讀取低下。