覺得有幫助的,請多多支持博主,點贊關注哦~
文章目錄
Hadoop數據壓縮
一、概述
壓縮技術能夠有效減少底層存儲系統(HDFS)讀寫字節數,提高了網絡帶寬和磁盤空間的效率。
在 Hadoop 下,尤其是數據規模很大和工作負載密集的情況下,使用數據壓縮顯得非常重要。在這種情況下,I/O 操作和網絡數據傳輸要花大量的時間。還有,Shuffle與 Merge 過程同樣也面臨着巨大的 I/O 壓力。
鑑於磁盤 I/O 和網絡帶寬是 Hadoop 的寶貴資源,數據壓縮對於節省資源、最小化磁盤I/O 和網絡傳輸非常有幫助。不過,儘管壓縮與解壓操作的 CPU 開銷不高,其性能的提升和資源的節省並非沒有代價。
如果磁盤 I/O 和網絡帶寬影響了 MapReduce 作業性能,在任意 MapReduce 階段啓用壓縮都可以改善端到端處理時間並減少 I/O 和網絡流量。
壓縮 Mapreduce 的一種優化策略:通過壓縮編碼對 Mapper 或者 Reducer 的輸出進行壓縮,以減少磁盤 IO,提高 MR 程序運行速度(但相應增加了 cpu 運算負擔)。
注意:壓縮特性運用得當能提高性能,但運用不當也可能降低性能。
基本原則:
(1)運算密集型的 job,少用壓縮
(2)IO 密集型的 job,多用壓縮
二、MR 支持的壓縮編碼
1、壓縮格式
壓縮格式 | hadoop 自帶? | 算法 | 文件擴展名 | 是否可切分 | 換成壓縮格式後,原來的程序是否需要修改 |
---|---|---|---|---|---|
DEFAULT | 是,直接使用 | DEFAULT | .deflate | 否 | 和文本處理一樣,不需要修改 |
Gzip | 是,直接使用 | DEFAULT | .gz | 否 | 和文本處理一樣,不需要修改 |
bzip2 | 是,直接使用 | bzip2 | .bz2 | 是 | 和文本處理一樣,不需要修改 |
LZO | 否,需要安裝 | LZO | .lzo | 是 | 需要建索引,還需要指定輸入格式 |
Snappy | 否,需要安裝 | Snappy | .snappy | 否 | 和文本處理一樣,不需要修改 |
2、編碼/解碼器
爲了支持多種壓縮/解壓縮算法,Hadoop 引入了編碼/解碼器,如下表所示
壓縮格式 | 對應的編碼/解碼器 |
---|---|
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 |
Snappy | org.apache.hadoop.io.compress.SnappyCodec |
3、壓縮性能的比較
壓縮算法 | 原始文件大小 | 壓縮文件大小 | 壓縮速度 | 解壓速度 |
---|---|---|---|---|
gzip | 8.3GB | 1.8GB | 17.5MB/s | 58MB/s |
bzip2 | 8.3GB | 1.1GB | 2.4MB/s | 9.5MB/s |
LZO | 8.3GB | 2.9GB | 49.3MB/s | 74.6MB/s |
Snappy | 250 MB/s | 500 MB/s |
三、壓縮方式選擇
Bzip2:Map輸出結果
Lzo、Snappy:shuffle中間階段結果
Gzip:reduce輸出結果
1、Gzip 壓縮
優點:
- 壓縮率比較高,而且壓縮/解壓速度也比較快;
- hadoop 本身支持,在應用中處理gzip 格式的文件就和直接處理文本一樣;大部分 linux 系統都自帶 gzip 命令,使用方便。
缺點:
- 不支持 split。
應用場景:
- 當每個文件壓縮之後在 130M 以內的(1 個塊大小內),都可以考慮用 gzip壓縮格式。
例如說一天或者一個小時的日誌壓縮成一個 gzip 文件,運行 mapreduce 程序的時候通過多個 gzip 文件達到併發。hive 程序,streaming 程序,和 java 寫的 mapreduce 程序完全和文本處理一樣,壓縮之後原來的程序不需要做任何修改。
2、Bzip2 壓縮
優點:
- 支持 split;
- 具有很高的壓縮率,比 gzip 壓縮率都高;
- hadoop 本身支持,但不支持 native;
- 在 linux 系統下自帶 bzip2 命令,使用方便。
缺點:
- 壓縮/解壓速度慢;
- 不支持 native。
應用場景:
- 適合對速度要求不高,但需要較高的壓縮率的時候,可以作爲 mapreduce 作業的輸出格式;
- 或者輸出之後的數據比較大,處理之後的數據需要壓縮存檔減少磁盤空間並且以後數據用得比較少的情況;
- 或者對單個很大的文本文件想壓縮減少存儲空間,同時又需要支持 split,而且兼容之前的應用程序(即應用程序不需要修改)的情況。
3、Lzo 壓縮
優點:
- 壓縮/解壓速度也比較快,合理的壓縮率;
- 支持 split,是 hadoop 中最流行的壓縮格式;
- 可以在 linux 系統下安裝 lzop 命令,使用方便。
缺點:
- 壓縮率比 gzip 要低一些;
- hadoop 本身不支持,需要安裝;
- 在應用中對 lzo 格式的文件需要做一些特殊處理(爲了支持 split 需要建索引,還需要指定 inputformat 爲 lzo 格式)。
應用場景:
- 一個很大的文本文件,壓縮之後還大於 200M 以上的可以考慮,而且單個文件越大,lzo 優點越越明顯。
4、Snappy 壓縮
優點:
- 高速壓縮速度和合理的壓縮率。
缺點:
- 不支持 split;
- 壓縮率比 gzip 要低;
- hadoop 本身不支持,需要安裝;
應用場景:
- 當 Mapreduce 作業的 Map 輸出的數據比較大的時候,作爲 Map 到 Reduce的中間數據的壓縮格式;或者作爲一個 Mapreduce 作業的輸出和另外一個 Mapreduce 作業的輸入。
四、壓縮位置選擇
壓縮可以在 MapReduce 作用的任意階段啓用。
階段 | 說明 |
---|---|
Map輸入端採用壓縮 | 在有大量數據並計劃重複處理的情況下,應考慮對輸入進行壓縮。 Hadoop自動檢查文件擴展名,根據擴展名自動選擇編碼方式對文件進行壓縮和解壓。(bzip2支持分片) |
Map輸出端採用壓縮 | 當map任務輸出的中間數據量很大時。 能提升Shuffle性能。 可選擇LZO或者Snappy. |
Reduce輸入端採用壓縮 | 能減少要存儲的數據量。 降低所需的磁盤空間。(gzip不支持分片) |
五、壓縮位置選擇
要在 Hadoop 中啓用壓縮,可以配置如下參數:
參數 | 默認值 | 階段 | 建議 |
---|---|---|---|
io.compression.codecs (在 core-site.xml 中配置) |
org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec |
輸入壓縮 | Hadoop 使用文件擴展名判斷是否支持某種編解碼器 |
mapreduce.map.output.compress (在 mapred-site.xml中配置) |
false | mapper 輸出 | 這個參數設爲true 啓用壓縮 |
mapreduce.map.output.compress.codec (在mapred-site.xml 中配置 |
org.apache.hadoop.io.compress.DefaultCodec | mapper 輸出 | 使用LZO 或snappy編解碼器在此階段壓縮數據 |
mapreduce.output.fileoutputformat.compress (在mapred-site.xml 中配置) |
false | reducer 輸出 | 這個參數設爲true 啓用壓縮 |
mapreduce.output.fileoutputformat.compress.codec (在mapred-site.xml 中配置) |
org.apache.hadoop.io.compress.DefaultCodec | reducer 輸出 | 使用標準工具或者編解碼器,如gzip 和bzip2 |
mapreduce.output.fileoutputformat.compress.type (在mapred-site.xml 中配置) |
RECORD | reducer 輸出 | SequenceFile輸出使用的壓縮類型:NONE和BLOCK |
# 案例
$HADOOP_HOME/etc/hadoop/core-site.xml
<property>
<name>io.compression.codecs</name>
<value>org.apache.hadoop.io.compress.GzipCodec</value>
</property>
六、壓縮實操案例
1、數據流的壓縮和解壓縮
CompressionCodec 有兩個方法可以用於輕鬆地壓縮或解壓縮數據。要想對正在被寫入一個輸出流的數據進行壓縮,我們可以使用 createOutputStream(OutputStreamout)方法創建一個 CompressionOutputStream,將其以壓縮格式寫入底層的流。相反,要想對從輸入流讀取而來的數據進行解壓縮,則調用 createInputStream(InputStreamin)函數,從而獲得一個CompressionInputStream,從而從底層的流讀取未壓縮的數據。
測試一下如下壓縮方式:
DEFLATE --> org.apache.hadoop.io.compress.DefaultCodec
gzip --> org.apache.hadoop.io.compress.GzipCodec
bzip2 --> org.apache.hadoop.io.compress.BZip2Codec
package com.biubiubiu.dataYasuo;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.CompressionCodecFactory;
import org.apache.hadoop.io.compress.CompressionInputStream;
import org.apache.hadoop.io.compress.CompressionOutputStream;
import org.apache.hadoop.util.ReflectionUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestCompress {
public static void main(String[] args) throws Exception {
compress("d_in/customer2.txt","d_in/customer2gz","org.apache.hadoop.io.compress.GzipCodec");
// decompress("d_in/customer2gz.gz","d_in/customer2gznew.txt");
}
// 壓縮
/*
filename:源文件
outpath:壓縮後的文件
method:壓縮方式(寫完整路徑 )org.apache.hadoop.io.compress.GzipCodec
*/
private static void compress(String filename, String outPath,String method) throws Exception {
// 1 獲取輸入流
FileInputStream fis = new FileInputStream(new File(filename));
//基於反射得到編碼對象
Class codecClass = Class.forName(method);
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, new Configuration());
// 2 獲取輸出流
FileOutputStream fos = new FileOutputStream(new File(outPath +codec.getDefaultExtension()));
CompressionOutputStream cos = codec.createOutputStream(fos);
// 3 流的對拷
IOUtils.copyBytes(fis, cos, 1024*1024*5, false);
// 4 關閉資源
fis.close();
cos.close();
fos.close();
}
// 解壓縮
private static void decompress(String filename,String outPath) throws FileNotFoundException, IOException {
// 1 校驗是否能解壓縮
CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());
CompressionCodec codec = factory.getCodec(new Path(filename));
if (codec == null) {
System.out.println("cannot find codec for file " + filename);
return;
}
// 2 獲取輸入流
CompressionInputStream cis = codec.createInputStream(new FileInputStream(new File(filename)));
// 3 獲取輸出流
FileOutputStream fos = new FileOutputStream(new File(outPath ));
// 4 流的對拷
IOUtils.copyBytes(cis, fos, 1024*1024*5, false);
// 5 關閉資源
cis.close();
fos.close();
}
}
2、Map 輸出端採用壓縮
即使你的 MapReduce 的輸入輸出文件都是未壓縮的文件,你仍然可以對 map 任務的中間結果輸出做壓縮,
因爲它要寫在硬盤並且通過網絡傳輸到 reduce 節點,對其壓縮可以提高很多性能,這些工作只要設置兩個屬性
hadoop 源碼支持的壓縮格式有:BZip2Codec 、DefaultCodec
//1)創建Configuration對象,指明namespace的路徑
Configuration conf = new Configuration();
conf.set("dfs.defaultFS","hdfs://192.168.159.151:9000");
//開啓map端輸出數據壓縮
conf.setBoolean("mapreduce.map.output.compress",true);
//設置map端輸出數據壓縮格式
conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
3、Reduce 輸出端採用壓縮
//開啓reduce端輸出數據壓縮
FileOutputFormat.setCompressOutput(job,true);
//設置reduce端輸出數據壓縮格式
FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
//FileOutputFormat.setOutputCompressorClass(job,BZip2Codec.class);
//FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class);
基於 workcount 案例處理
package com.biubiubiu.test;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.BZip2Codec;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.DefaultCodec;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
public class WordCount {
/**
* 數據壓縮
*
* 第1部分:自定義Mapper(靜態內部類)
* hello,aa,b,c
* d,e,f
* aa,bb,cc
* dd,ee,ff
* Mapper<四個參數>:
* 1)前兩個是輸入參數:行號,當前行的內容
* Object,Text
* 2)後兩個是輸出參數
* hello 1
*
* 結論:都是(key,value)
*/
static class CustomMapper extends Mapper<Object, Text,Text, IntWritable>{
/**
* aa,bb,cc 輸出結果:aa 1
* bb 1
* cc 1
* 參數說明:
* 參數1:Object key:行號(很少用)
* 參數2:Text value:行的內容(必須用)
* 參數3:Context context:(上下文環境)
*/
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
System.out.println("map函數的輸入值:"+key.toString()+" "+value.toString());
//1)得到當前行的內容
String line=value.toString();
//2)對當前行內容進行清理
String [] words=line.split(",");
//3)向context輸出結果
for(String word :words){
context.write(new Text(word),new IntWritable(1));
System.out.println("map函數的輸出結果:"+word+" "+1);
}
}
}
/**第2部分:自定義Reducer(靜態內部類)
* 4個參數:
* 1)前兩個是輸入參數(來源於map的輸出)(因此reduce的輸入參數=map的輸出參數):
* 2)後兩個參數reduce的輸出結果:
* hello 4
* a 5
*/
static class CustomReducer extends Reducer<Text, IntWritable,Text,IntWritable>{
/**reduce函數執行的次數,取決於key的個數
* @param key :hello
* @param values :(1,1,1,1,1,1)
* @param context
* 輸出結果(hello,6)
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//1)初始化變量
int count =0;
//2)迭代累加
for(IntWritable value:values){
count+=1;
}
//3)輸出關於當前key的結果
context.write(new Text(key.toString()),new IntWritable(count));
System.out.println("*************reduce的輸出結果:"+key.toString()+" "+count);
}
}
//第3部分,編寫Driver(main方法)
public static void main(String[] args) {
try{
//1)創建Configuration對象,指明namespace的路徑
Configuration conf = new Configuration();
conf.set("dfs.defaultFS","hdfs://192.168.159.151:9000");
//開啓map端輸出數據壓縮
conf.setBoolean("mapreduce.map.output.compress",true);
//設置map端輸出數據壓縮格式
conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
//2)創建Job
Job job =Job.getInstance(conf,"mywordcount");
job.setJarByClass(WordCount.class);
//3)自定義Mapper進行輸出參數(key,value)的配置
job.setMapperClass(CustomMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//4)自定義Reducer進行參數的配置
job.setReducerClass(CustomReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//5)配置處理的文件的路徑(input)以及處理結果存放的路徑(output)
FileInputFormat.addInputPath(job,new Path("data/word.txt"));
FileOutputFormat.setOutputPath(job,new Path("data/dataYasuo/default"));
//開啓reduce端輸出數據壓縮
FileOutputFormat.setCompressOutput(job,true);
//設置reduce端輸出數據壓縮格式
FileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
// FileOutputFormat.setOutputCompressorClass(job,BZip2Codec.class);
// FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class);
//6)讓程序執行
boolean result=job.waitForCompletion(true);
if(result){
System.out.println("執行正確!!!");
}else{
System.out.println("執行失敗.....");
}
}catch(Exception ex){
System.out.println("執行出錯:"+ex.getMessage());
ex.printStackTrace();
}
}
}
覺得有幫助的,請多多支持博主,點贊關注哦~