亞寵展、全球寵物產業風向標——亞洲寵物展覽會深度解析
965
2025-03-31
5.2? 壓縮
文件壓縮有兩大好處:減少存儲文件所需要的磁盤空間,并加速數據在網絡和磁盤上的傳輸。這兩大好處在處理大量數據時相當重要,所以我們值得仔細考慮在Hadoop中文件壓縮的用法。
有很多種不同的壓縮格式、工具和算法,它們各有千秋。表5-1列出了與Hadoop結合使用的常見壓縮方法。
表5-1. 壓縮格式總結
壓縮格式
工具
算法
文件擴展名
是否可切分
DEFLATE①
無
DEFLATE
.deflate
否
gzip
gzip
DEFLATE
.gz
否
bzip2
bzip2
bzip2
.bz2
是
LZO
lzop
LZO
.lzo
否②
LZ4
無
LZ4
.lz4
否
Snappy
無
Snappy
.snappy
否
①??DEFLATE是一個標準壓縮算法,該算法的標準實現是zlib。沒有可用于生成DEFLATE文件的常用命令行工具,因為通常都用gzip格式。注意,gzip文件格式只是在DEFLATE格式上增加了文件頭和一個文件尾。.deflate文件擴展名是Hadoop約定的。
② 但是如果LZO文件已經在預處理過程中被索引了,那么LZO文件是可切分的。詳情參見5.2.2節。
所有壓縮算法都需要權衡空間/時間:壓縮和解壓縮速度更快,其代價通常是只能節省少量的空間。表5-1列出的所有壓縮工具都提供9個不同的選項來控制壓縮時必須考慮的權衡:選項-1為優化壓縮速度,-9為優化壓縮空間。例如,下述命令通過最快的壓縮方法創建一個名為file.gz的壓縮文件:
%gzip -1 file
不同壓縮工具有不同的壓縮特性。gzip是一個通用的壓縮工具,在空間/時間性能的權衡中,居于其他兩個壓縮方法之間。bzip2的壓縮能力強于gzip,但壓縮速度更慢一點。盡管bzip2的解壓速度比壓縮速度快,但仍比其他壓縮格式要慢一些。另一方面,LZO、LZ4和Snappy均優化壓縮速度,其速度比gzip快一個數量級,但壓縮效率稍遜一籌。Snappy和LZ4的解壓縮速度比LZO高出很多。[1]
表5-1中的“是否可切分”列表示對應的壓縮算法是否支持切分(splitable),也就是說,是否可以搜索數據流的任意位置并進一步往下讀取數據。可切分壓縮格式尤其適合MapReduce,更多討論,可以參見5.2.2節。
5.2.1? codec
Codec是壓縮-解壓縮算法的一種實現。在Hadoop中,一個對CompressionCodec接口的實現代表一個codec。例如,GzipCodec包裝了gzip的壓縮和解壓縮算法。表5-2列舉了Hadoop實現的codec。
表5-2. Hadoop的壓縮codec
壓縮格式
HadoopCompressionCodec
DEFLATE
org.apache.hadoop.io.compress.DefaultCodec
gzip
org.apache.hadoop.io.compress.GzipCodec
bzip2
org.apache.hadoop.io.compress.BZip2Codec
LZO
com.hadoop.compression.lzo.LzopCodec
LZ4
org.apache.hadoop.io.compress.Lz4Codec
Snappy
org.apache.hadoop.io.compress.SnappyCodec
LZO代碼庫擁有GPL許可,因而可能沒有包含在Apache的發行版本中,因此,Hadoop的codec需要單獨從Google(http://code.google.com/p/hadoop-gpl-compression)或GitHub(http://github.com/kevinweil/hadoop-lzo)下載,該代碼庫包含有修正的軟件錯誤及其他一些工具。LzopCodec與lzop工具兼容,LzopCodec基本上是LZO格式的但包含額外的文件頭,因此這通常就是你想要的。也有針對純LZO格式的LzoCodec,并使用.lzo_deflate作為文件擴展名(類似于DEFLATE,是gzip格式但不包含文件頭)。
CompressionCodec包含兩個函數,可以輕松用于壓縮和解壓縮數據。如果要對寫入輸出數據流的數據進行壓縮,可用createOutputStream (OutputStream out)方法在底層的數據流中對需要以壓縮格式寫入在此之前尚未壓縮的數據新建一個CompressionOutputStream對象。相反,對輸入數據流中讀取的數據進行解壓縮的時候,則調用createInputStream(InputStream in)獲取CompressionInputStream,可以通過該方法從底層數據流讀取解壓縮后的數據。
CompressionOutputStream和CompressionInputStream,類似于java.util. zip.DeflaterOutputStream和java.util.zip.DeflaterInputStream,只不過前兩者能夠重置其底層的壓縮或解壓縮方法,對于某些將部分數據流(section of data stream)壓縮為單獨數據塊(block)的應用,例如SequenceFile(詳情參見5.4.1節對SequenceFile類的討論),這個能力是非常重要的。
范例5-1顯示了如何用API來壓縮從標準輸入中讀取的數據并將其寫到標準輸出。
范例5-1. 該程序壓縮從標準輸入讀取的數據,然后將其寫到標準輸出
public class StreamCompressor {
public static void main(String[] args) throws Exception {
String codecClassname = args[0];
Class> codecClass = Class.forName(codecClassname);
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec)
ReflectionUtils.newInstance(codecClass, conf);
CompressionOutputStream out = codec.createOutputStream(System.out);
IOUtils.copyBytes(System.in, out, 4096, false);
out.finish();
}
}
該應用希望將符合CompressionCodec實現的完全合格名稱作為第一個命令行參數。我們使用ReflectionUtils新建一個codec實例,并由此獲得在System.out上支持壓縮的一個包裹方法。然后,對IOUtils對象調用copyBytes()方法將輸入數據復制到輸出,(輸出由CompressionOutputStream對象壓縮)。最后,我們對CompressionOutputStream對象調用finish()方法,要求壓縮方法完成到壓縮數據流的寫操作,但不關閉這個數據流。我們可以用下面這行命令做一個測試,通過GzipCodec的StreamCompressor對象對字符串“Text”進行壓縮,然后使用gunzip從標準輸入中對它進行讀取并解壓縮操作:
% echo "Text" | hadoop StreamCompressor org.apache.hadoop.io. compress.GzipCodec \
| gunzip
Text
在讀取一個壓縮文件時,通常可以通過文件擴展名推斷需要使用哪個codec。如果文件以.gz結尾,則可以用GzipCodec來讀取,如此等等。前面的表5-1為每一種壓縮格式列舉了文件擴展名。
通過使用其getCodec()方法,CompressionCodecFactory提供了一種可以將文件擴展名映射到一個CompressionCodec的方法,該方法取文件的Path對象作為參數。范例5-2所示的應用便使用這個特性來對文件進行解壓縮。
范例5-2. 該應用根據文件擴展名選取codec解壓縮文件
public class FileDecompressor {
public static void main(String[] args) throws Exception {
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri), conf);
Path inputPath = new Path(uri);
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(inputPath);
if (codec == null) {
System.err.println("No codec found for " + uri);
System.exit(1);
}
String outputUri =
CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension());
InputStream in = null;
OutputStream out = null;
try {
in = codec.createInputStream(fs.open(inputPath));
out = fs.create(new Path(outputUri));
IOUtils.copyBytes(in, out, conf);
} finally {
IOUtils.closeStream(in);
IOUtils.closeStream(out);
}
}
}
一旦找到對應的codec,便去除文件擴展名形成輸出文件名,這是通過CompressionCodecFactory對象的靜態方法removeSuffix()來實現的。按照這種方法,一個名為file.gz的文件可以通過調用該程序解壓為名為file的文件:
% hadoop FileDecompressor file.gz
CompressionCodecFactory加載表5-2中除LZO之外的所有codec,同樣也加載io.compression.codecs配置屬性(參見表5-3)列表中的所有codec。在默認情況下,該屬性列表是空的,你可能只有在你擁有一個希望注冊的定制codec(例如外部管理的LZO codec)時才需要加以修改。每個codec都知道自己默認的文件擴展名,因此CompressionCodecFactory可通過搜索注冊的codec找到匹配指定文件擴展名的codec(如果有的話)。
表5-3. 壓縮codec的屬性
屬性名稱
類型
默認值
描述
io.compression.codecs
逗號分隔的類名
用于壓縮/解壓縮的額外的CompressionCodec類的列表
為了提高性能,最好使用“原生”(native)類庫來實現壓縮和解壓縮。例如,在一個測試中,使用原生gzip類庫可以減少約一半的解壓縮時間和約10%的壓縮時間(與內置的Java實現相比)。表5-4說明了每種壓縮格式是否有Java實現和原生類庫實現。所有的格式都有原生類庫實現,但是并非所有格式都有Java實現(如LZO)。
表5-4. 壓縮代碼庫的實現
壓縮格式
是否有Java實現
是否有原生實現
DEFLATE
是
是
gzip
是
是
bzip2
是
否
LZO
否
是
LZ4
否
是
Snappy
否
是
Apache Hadoop 二進制壓縮包本身包含有為64位Linux構建的原生壓縮二進制代碼,稱為libhadoop.so。對于其他平臺,需要自己根據位于源文件樹最頂層的BUILDING.txt指令編譯代碼庫。
可以通過Java系統的java.library.path屬性指定原生代碼庫。etc/hadoop文件夾中的hadoop腳本可以幫你設置該屬性,但如果不用這個腳本,則需要在應用中手動設置該屬性。
默認情況下,Hadoop會根據自身運行的平臺搜索原生代碼庫,如果找到相應的代碼庫就會自動加載。這意味著,你無需為了使用原生代碼庫而修改任何設置。但是,在某些情況下,例如調試一個壓縮相關問題時,可能需要禁用原生代碼庫。將屬性io.native.lib.available的值設置成false即可,這可確保使用內置的Java代碼庫(如果有的話)。
如果使用的是原生代碼庫并且需要在應用中執行大量壓縮和解壓縮操作,可以考慮使用CodecPool,它支持反復使用壓縮和解壓縮,以分攤創建這些對象的開銷。
范例5-3中的代碼顯示了API函數,不過在這個程序中,它只新建了一個Compressor,并不需要使用壓縮/解壓縮池。
范例5-3. 使用壓縮池對讀取自標準輸入的數據進行壓縮,然后將其寫到標準輸出
public class PooledStreamCompressor {
public static void main(String[] args) throws Exception {
String codecClassname = args[0];
Class> codecClass = Class.forName(codecClassname);
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec)
ReflectionUtils.newInstance(codecClass, conf);
Compressor compressor = null;
try {
compressor = CodecPool.getCompressor(codec);
CompressionOutputStream out =
codec.createOutputStream(System.out, compressor);
IOUtils.copyBytes(System.in, out, 4096, false);
out.finish();
} finally {
CodecPool.returnCompressor(compressor);
}
}
}
在codec的重載方法createOutputStream()中,對于指定的CompressionCodec,我們從池中獲取一個Compressor實例。通過使用finally數據塊,我們在不同的數據流之間來回復制數據,即使出現IOException異常,也可以確保compressor可以返回池中。
Hadoop 大數據 彈性文件服務
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。