一、壓縮
文件壓縮帶來兩大好處,它減少了存儲文件所需的空間並且加快了數據在網絡上或者從磁盤上或到磁盤的傳輸速度。在處理大容量數據時,這些都是很重要的,因此對於hadoop中如何使用壓縮,值得仔細考慮。一下壓縮格式可用於hadoop。
壓縮格式 工具 算法 文件擴展名 多文件 可分割性
DEFLATE* 無 DEFLATE .deflate 不 不
Gzip gzip DEFLATE .gz 不 不
ZIP zip DEFLATE ..zip 是 是,在文件範圍內
bzip2 bzip2 bzip2 .bz2 不 是
LZO lzop LZO .lzo 不 不
更快的壓縮和解壓速度通常會耗費更多的空間。-1表示速度最優,-9表示空間最優。例如下面命令使用最快的壓縮算法創建一個壓縮文件file.gz
gzip -1 file
上面的可分割性指壓縮格式是否支持分割,也就是說,你是否可以找到數據流中任何一個點並且從某點開始讀取。可分割壓縮格式特別適合mapreduce
二、編碼,解碼器。
常用編碼器:
org.apache.hadoop.io.compress.DefaultCodec 對應壓縮格式爲DEFLATE
org.apache.hadoop.io.compress.GzipCodec 對應Gzip
三、hadoop API中,已經有實現編碼,解碼的接口。
接口:
(1)CompressionCodec 接口
該接口包括解壓,壓縮的方法流
常用方法: createInputStream(InputStream in) //從指定輸入流中創建一個壓縮流
createOutputStream(OutputStream out)
//從給定輸出流中創建一個壓縮流
getCompressorType() //返回需要的編碼器的類型
getDefaultExtension() //返回編碼器的文件擴展名
如下所示,壓縮從標準輸入讀取的數據並將它寫到標準輸出
Class<?> codecClass = class.forName(codecClassname); //使用指定編碼器類來創建一個壓縮類,比如GzipCodec編碼器
Configuration conf = new Configuration();
CompressionCodec codec = (CompressionCodec) ReflectionUtil.newInstance(codexClass,conf); //創建新的實例
CompressionOutputStream out = codec.createOutputStream(System.out); //獲得壓縮好的out
IOUtils.copyBytes(System.in,out,4096,false); //將輸入System.in複製到經過CompressionOutputStream的輸出out
out.finish(); //結束向壓縮流寫入數據,但不關閉流
(2)CompressionCodeFactory類
該類爲工廠模式,從給定文件名找到編解碼器。
方法:
CompressionCodec getCodec(Path file) //根據文件後綴獲得相關的壓縮編解碼器
String removeSuffix(String filename, String suffix)
//如果文件有後綴名,去掉後綴名
四、本地庫
考慮到性能,最好使用一個本地庫(native library)來壓縮和解壓。例如,在一個測試中,使用本地gzip壓縮庫減少了解壓時間50%,壓縮時間減少了大約10%(與內置的java實現比較)。hadoop帶有預置的32位和64位linux本地壓縮庫,支持的格式有DEFLATE、Gzip、LZO,位於庫/本地目錄。對於其他平臺,需要自己編譯庫。
默認情況下,hadoop會在它運行的平臺上查找本地庫,如果發現,就自動加載。這意味着不必要更改任何設置就可以使用本地庫。在某些情況下,可能希望禁用本地庫,比如在調試壓縮相關問題時候。爲此,將屬性hadoop.native.lib設置爲false即可。
如果要用本地庫在應用中大量執行壓縮解壓任務,可以考慮使用CodePool(壓縮解碼池)。從而重用壓縮程序和解壓程序,節約創建對象的開銷。
CodePool類常用方法有:
static Compressor getCompressor(CompressionCodec codec, Configuration conf) //根據指定壓縮器從池中獲得一個Compressor實例
static void returnCompressor(Compressor compressor) //把Compressor實例返回到池中,下一次即可複用。
五、壓縮和輸入分割
在考慮如何壓縮那些將由MapReduce處理的數據時,考慮壓縮格式是否支持分割是很重要的。考慮儲存在HDFS中的未壓縮的文件,其大小爲1GB.HDFS的塊大小爲64MB,所以該文件被儲存爲16塊,將此文件用作輸入的MapReduce作業會創建16個輸入分片(split)。每個分片都被作爲一個獨立map任務的輸入單獨進行處理。
前面我提到過,gzip和DEFLATE不支持分割,所以map任務也就不可能獨立於其他塊讀取塊中的數據。這樣就必須以本地化爲代價,一個map任務將處理16個HDFS塊。由於map任務少,作業分割的粒度不夠細,從而導致運行時間變長。那麼,我們應採用哪種壓縮格式呢,顯然,根據不同情況,可選擇支持分割機制的壓縮格式如bzip2,或者使用支持壓縮和分割機制的Sequence File(序列文件)。對於大型文件,不要對整個文件使用不支持分割的壓縮格式,因爲這樣會損失本地性優勢,從而降低MapReduce應用的性能。
六、在MapReduce中使用壓縮。
如果輸入的文件是壓縮過的,那麼在被MapReduce讀取時,它們會被自動解壓,根據文件的擴展名來決定使用哪一個壓縮解碼器。
如果要壓縮MapReduce作業的輸出,請在作業配置文件中將mapred.output.compress屬性設置爲true,將mapred.output.compression.codec屬性設置爲自己想用的壓縮編碼/解碼器的類型。如下的mapreduce程序,可以這樣在綠色部分這樣設置:
JobConf conf = new JobConf(className);
conf.setJobName("jobname");
FileInputFormat.addInputPath(conf,path);
FileOutputFormat.setOutputPath(conf,path);
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setBoolean("mapred.output.compress",true);
conf.setClass("mapred.output.compression.codec",GzipCodec.class,CompressionCodec.class);
conf.setMapperClass(mapperclassname);
conf.setCombinerClass(reduceclassname);
conf.setReducerClass(reduceclassname);
JobClient.runJob(conf);
改程序運行後,最終輸出的部分都是被壓縮過的。可以設置mapred.output.compression.type屬性來控制壓縮類型。默認爲RECORD,他壓縮單獨的記錄。推薦更改爲BLOCK,因爲它是壓縮成一組記錄,而且有更好的壓縮比。
我們其實還可以進一步優化,在map作業輸出結果中進行壓縮,因爲map輸出會被寫入本地磁盤並通過網絡傳輸到reduce節點。如果使用LZO之類的快速壓縮,能,較少傳輸的數據量,提示性能。如:
conf.setCompressMapOutput(true);
conf.setMapOutputCompressorClass(GzipCodec.class);