文章目錄
介紹
SequenceFile
是hadoop裏用來存儲序列化的鍵值對即二進制
的一種文件格式。SequenceFile
文件也可以作爲MapReduce作業的輸入和輸出,hive和spark
也支持這種格式。
它有如下幾個優點:
- 以二進制的形式存儲數據,所以可以在HDFS裏存儲圖像或者更加複雜的結構作爲KV對。
SequenceFile
支持壓縮
和分片
。當你壓縮爲一個SequenceFile
時,並不是將整個文件壓縮成一個單獨的單元,而是壓縮文件裏的record
或者block of records(塊)
。因此SequenceFile
是能夠支持分片
的,儘管使用的壓縮方式如Snappy, Lz4 or Gzip
不支持分片。SequenceFile
也可以用於存儲多個小文件。由於Hadoop本身就是用來處理大型文件的,所以用一個SequenceFile
來存儲很多小文件就可以提高處理效率,也能節省Namenode內存,因爲Namenode只需一個SequenceFile
的metadata,而不是爲每個小文件創建單獨的metadata。- 由於數據是以
SequenceFile
形式存儲,所以中間輸出文件即map輸出也會用SequenceFile
來存儲。
Sync points(同步點)
在SequenceFile
文件裏每隔100 bytes會記錄一個sync-marker
,基於這些sync points
,SequenceFile
就是可以切片的並用作MapReduce的輸入。
SequenceFile的壓縮形式
對SequenceFile
有三種壓縮選擇
NONE
---- 即key和value都不壓縮。RECORD
---- 只有value會被壓縮。BLOCK
---- key和value都會被壓縮。key和value都會以block
的形式單獨收集和壓縮。block
的大小是可以配置的,在core-site,xml
裏定義io.seqfile.compress.blocksize
,默認是1000000 bytes。
壓縮的好處:減少磁盤佔用、減少IO、減少shuffle的帶寬。
SequenceFile的文件格式
儘管有三種壓縮選擇,但是header
的格式都是一樣的。
SequenceFile文件的header格式
字段 | 說明 |
---|---|
version | 3 bytes of magic header SEQ, followed by 1 byte of actual version number (e.g. SEQ4 or SEQ6) |
KeyClassName | key class |
ValueClassName | value class |
Compression | A boolean which specifies if compression is turned on for keys/values in this file |
BlockCompression | CompressionCodec class which is used for compression of keys and/or values (if compression is enabled) |
Metadata | SequenceFile.Metadata for this file |
Sync | A sync marker to denote end of the header |
無壓縮的SequenceFile文件格式
sync-marker
---- 每100 bytes一個sync-marker
Record壓縮的SequenceFile文件格式
sync-marker
---- 每100 bytes一個sync-marker
存儲格式如下
Block壓縮的SequenceFile文件格式
- Header
- Record Block
- Uncompressed number of records in the block
- Compressed key-lengths block-size
- Compressed key-lengths block
- Compressed keys block-size
- Compressed keys block
- Compressed value-lengths block-size
- Compressed value-lengths block
- Compressed values block-size
- Compressed values block
- A sync-marker
every block
SequenceFile的相關類
SequenceFile
提供了SequenceFile.Writer, SequenceFile.Reader 和 SequenceFile.Sorter
用來寫、讀及排序數據。
由於有三種壓縮選擇,故SequenceFile Writer
有以下三類:
Writer
: Uncompressed records.RecordCompressWriter
: Record-compressed files, only compress values.BlockCompressWriter
: Block-compressed files, both keys & values are compressed.
通常用靜態方法SequenceFile.createWriter
去創建Writer
,創建時指定Writer.Option
來確定是否要壓縮及選擇哪種方式壓縮。
Java API讀寫SequenceFile
將HDFS某個目錄下所有的小文件合併成一個SequenceFile文件(寫)
以小文件的文件路徑作爲key,小文件的內容作爲value。然後將其寫入到SequenceFile
文件。
public static void writeHDFSFileTOHDFSSequenceFile() throws IOException {
String inputDir = "/user/root/input";
Path path = new Path(inputDir);
//最後生成的sequenceFile文件
Path outFile = new Path("/user/root/sequecenfile/out/seq_file");
//聲明一個byte[]用於後面存放小文件內容
byte[] buffer;
//獲取inputDir目錄下的所有文件
FileStatus[] fileStatusArr = fs.listStatus(path);
//構造writer, 並使用try獲取資源, 最後自動關閉資源
try(SequenceFile.Writer writer = SequenceFile.createWriter(conf,
SequenceFile.Writer.file(outFile),//設置文件名
SequenceFile.Writer.keyClass(Text.class),//設置keyclass
SequenceFile.Writer.valueClass(Text.class),//設置valueclass
SequenceFile.Writer.appendIfExists(false),
SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new GzipCodec()) //設置block+gzip的壓縮方式
)){
//循環外定義key和value,避免重複定義,因爲序列化時只是會序列化對應的內容
Text key = new Text();
Text value = new Text();
for (FileStatus fileStatus : fileStatusArr){
System.out.println("the file name is "+fileStatus.getPath());
//利用FileSystem打開文件
FSDataInputStream fsDataIn = fs.open(fileStatus.getPath());
//根據文件大小來定義byte[]的長度
buffer = new byte[((int) fileStatus.getLen())];
//將文件內容讀入到buffer這個byte[]裏
fsDataIn.read(buffer);
key.set(fileStatus.getPath().toString());
value.set(buffer);
//通過append方法寫入到SequenceFile
writer.append(key, value);
}
}
}
將本地某個目錄下所有的小文件合併成一個SequenceFile文件(寫)
public static void writeLocalFileToHDFSSequenceFile() throws IOException {
String inputDir = "/root/test";
java.nio.file.Path localDir = Paths.get(inputDir);
Path outFile = new Path("/user/root/sequecenfile/out/seq_file");
//構造writer, 並使用try獲取資源, 最後自動關閉資源
try(SequenceFile.Writer writer = SequenceFile.createWriter(conf,
SequenceFile.Writer.file(outFile),//設置文件名
SequenceFile.Writer.keyClass(Text.class),//設置keyclass
SequenceFile.Writer.valueClass(Text.class),//設置valueclass
SequenceFile.Writer.appendIfExists(false),
SequenceFile.Writer.compression(SequenceFile.CompressionType.BLOCK, new GzipCodec()) //設置block+gzip的壓縮方式
)){
Text key = new Text();
Text value = new Text();
// Collection<File> fileList = FileUtils.listFiles(new File(inputDir),null,false);
try(Stream<java.nio.file.Path> stream = Files.list(localDir)){
//遍歷所有以.txt結尾的小文件寫入到SequenceFile
stream.filter(x->x.toString().endsWith(".txt")).forEach(localPath->{
try {
System.out.println(localPath.toString());
byte[] bytes = Files.readAllBytes(localPath);
key.set(localPath.toString());
value.set(bytes);
writer.append(key, value);
} catch (IOException e) {
e.printStackTrace();
System.err.println("read "+localPath.toString()+" error");
}
});
}
}
}
讀HDFS上SequenceFile文件的內容(讀)
通過SequenceFile.Reader
的構造方法並指定參數創建Reader
來讀取SequenceFile
文件
public static void readSequenceFile() throws IOException {
Path seqPath = new Path("/user/root/sequecenfile/out/seq_file");
try(SequenceFile.Reader reader = new SequenceFile.Reader(conf,
SequenceFile.Reader.file(seqPath), //設置SequenceFile文件路徑
SequenceFile.Reader.bufferSize(1024*8)) //設置bufferSize
){
Text key = new Text();
Text value = new Text();
while (reader.next(key, value)){
System.out.println("------------key="+key+"----------");
System.out.println(value);
System.out.println("-------------------------------");
}
}
}
將其打包成具體的jar文件後執行hadoop jar sequenceFile.jar com.utstar.patrick.hadoop.data.format.SequenceFileDemo
其中sequenceFile.jar
就是打包的jar名,com.utstar.patrick.hadoop.data.format.SequenceFileDemo
就是需要運行的main類。
部分結果如下圖所示
使用SequenceFile作爲MapReduce的輸入和輸出
使用SequenceFile作爲MapReduce的輸入
將SequenceFile
文件作爲輸入很簡單,只需要在driver
代碼裏設置job的inputformat
即可。
job.setInputFormatClass(SequenceFileInputFormat.class);
我們以hadoop提供的example爲例,我通過-Dproperty=value
的方式設置了inputformat
來讀取上面生成的SequenceFile
文件
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.5.jar wordcount -Dmapreduce.job.inputformat.class=org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat /user/root/sequecenfile/out/seq_file /out_99
能正常的進行單詞統計功能,如下圖所示
使用SequenceFile作爲MapReduce的輸出
在driver
代碼裏設置如下參數即可,需要強調的是要通過SequenceFileOutputFormat.setOutputCompressionType
方式來說明SequenceFile選取哪種壓縮方式。
//設置outputformat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
FileInputFormat.addInputPath(job, new Path("/user/root/input"));
FileOutputFormat.setOutputPath(job, new Path(outpath));
//設置壓縮
FileOutputFormat.setCompressOutput(job, true);
//設置用哪種算法進行壓縮
FileOutputFormat.setOutputCompressorClass(job, Lz4Codec.class);
//必須通過SequenceFileOutputFormat.setOutputCompressionType來指定SequenceFile文件的壓縮類型
SequenceFileOutputFormat.setOutputCompressionType(job, SequenceFile.CompressionType.BLOCK);
順帶提下小文件的處理方式
hadoop自帶有3種小文件解決方式
Hadoop Archive
,具體可參考我寫的這篇博客Hadoop ArchiveCombineFileInputFormat
,具體可參考官方給出的例子MultiFileWordCount.javaSequenceFile
Hadoop Archive
雖然解決了小文件佔用namenode內存的問題,但是提交任務時還是一個小文件一個task;
CombineFileInputFormat
是一個抽象類,需要繼承該類實現對應的抽象方法public abstract RecordReader<K, V> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException
,比較麻煩;
所以解決小文件更好的方式還是 SequenceFile
提到小文件順便提下小job,對於小型job可以開啓jvm重用讓每個jvm跑幾個task,這樣可以減少進程關閉和創建的開銷,能有效提高小job的運行效率。
mapreduce.job.jvm.numtasks
的默認值是1
mapreduce.job.jvm.numtasks=10
但是對於大型job,該參數就不合適了,因爲在長期運行的job裏會有內存碎片,最好是重新創建jvm,而且創建jvm的時間和整個job的運行時間相比就很微不足道了。說白了,這都是一個trade-off
的過程。
參考網址
sequence-file-hadoop
how-to-read-and-write-sequencefile-in-hadoop
what-is-sequencefileinputformat-in-hadoop-mapreduce
reuse-jvm-in-hadoop-mapreduce-jobs