一、概述
压缩技术可以有效减少底层存储(HDFS)读写字节数。压缩提高了网络带宽与磁盘空间效率。在运行MR时,IO、网络数据传输、Shuffle和Merge要花费大量时间,尤其是数据规模很大和负载密集的情况下,因此使用数据压缩显得非常重要。
磁盘IO和网络带宽是Hadoop的宝贵资源,数据压缩对于节省资源、最小化磁盘IO和网络传输非常有帮助,可以在任意阶段启用压缩。采用压缩技术减少了磁盘IO,但同时也增加了CPU的运算负担,压缩技术运用得当可以提高性能,但运用不当也可能降低性能。
基本原则:
- 运算密集型job,少用压缩
- IO密集型的job,多用压缩
二、压缩方式
2.1 Gzip压缩
- 优点:压缩率比较高,压缩速度比较快.Hadoop本身支持,在应用中处理Gzip格式文件和直接处理文本一样,大部分Linux自带Gzip命令。
- 缺点:不支持Split
- 应用场景:当每个文件压缩之后在一个块大小内,都可以考虑使用Gzip压缩格式。
2.2 Bzip2压缩
- 优点: 支持Split,具有很高的压缩率,比Gzip压缩了高,Hadoop本身自带,使用方便。
- 缺点:压缩/解压速度慢
- 应用场景:适合对速度要求不高,但需要较高的压缩率的时候,或者输出之后的数据比较大。处理之后的数据需要压缩存档减少磁盘空间,并且以后的数据用的较少的情况,或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序。
2.3 Lzo压缩
- 优点:压缩/解压速度也比较快,合理的压缩率。支持Split,是Hadoop中最流行的压缩格式,可以在Linux中安装Lzop命令,使用方便。
- 缺点:压缩率比Gzip要低一点,Hadoop本身不支持,需要安装。在应用中对Lzo格式的文件需要做些特殊的处理(为了支持Split需要建索引,还需要指定InputFormat为Lzo格式)
2.4 Snappy压缩
- 优点:高速压缩速度和和合理的压缩率
- 缺点:不支持Split;压缩率比Gzip高,Hadoop本身不知福,需要安装。
- 应用场景:当MapReduce作业的Map输出数据比较大的时候,作为Map到Reduce的中间数据的压缩格式。或者作为一个MapReduce的作业输出和另外一个MapReduce的输入
2.5 压缩总结对比
压缩格式1 | hadoop自带 | 算法 | 文件扩展名 | 可切分? | 换成压缩格式后,原来的程序是否需要修改 |
---|---|---|---|---|---|
DEFAULT | 是 | DEFAULT | .deflate | 否 | 和文本处理一样,不需要修改 |
Gzip | 是 | DEFAULT | .gz | 否 | 和文本处理一样,不需要修改 |
bzip2 | 是 | bzip2 | .bz2 | 是 | 和文本处理一样,不需要修改 |
LZO | 否 | LZO | .lzo | 是 | 需要建索引,还需要指定输入格式 |
Snappy | 否 | Snappy | .snappy | 否 | 和文本处理一样,不需要修改 |
压缩格式 | 对应编码/解码器 |
---|---|
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 |
压缩速度与效率对比:
三、压缩位置选择
- Map强压缩
在有大量数据并计划重复处理的情况下,应该考虑对输入数据记下来压缩,压缩时无需指定使用的编码方式,Hadoop自动检查文件扩展名,如果扩展名能够匹配,就会用恰当的解码方式进行解压,否则Hadoop就不会使用任何编解码器。
- Map之后,Reduce之前
当Map任务输出的中间数据很大时,应该考虑使用压缩。这能够显著改变内部数据的Shuffle过程,Shuffle过程在Hadoop中是资源消耗最多的环节,如果发现数据量大造成网络传输缓慢,应该考虑使用压缩技术。可用于压缩Mapper输出的快速编解码器LZO或者Snappy。
注:LZO是供Hadoop压缩数据通用压缩编解码器。设计目的是达到与硬盘读取速度相当的压缩速度,因此速度是优先考虑的因素,而不是压缩率。
- Reduce之后
在此阶段启用压缩技术能够减少要存储的数据量,因此降低所需的磁盘空间。当MapReduce作业形成作业两条时,因为第二个作业的输入也已压缩,所以用压缩同样有效。
压缩的参数配置:
文件位置 | 参数 | 默认值 | 阶段 | 备注 |
---|---|---|---|---|
core-site.xml | io.compression.codecs | org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec | 输入压缩 | Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapred-site.xml | mapreduce.map.output.compress | false | mapper输出 | true启用mapper压缩 |
mapred-site.xml | mapreduce.map.output.compress.code | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 企业使用LZO或Snappy编解码器在此阶段压缩数据 |
mapred-site.xml | mapreduce.output.fileoutputformat.compress | false | reducer输出 | true开启reduce压缩 |
mapred-site.xml | mapreduce.output.fileoutputformat.compress.codec | org.apache.hadoop.io.compress. DefaultCodec | reducer输出 | 使用标准工具或者解码器,如gzip或gzip2 |
mapred-site.xml | mapreduce.output.fileoutputformat.compress.type | RECORD | reducer输出 | SequenceFile输出使用的压缩类型:NONE和BLOCK |
四、文件压缩
CompressionCodec有两个方法可以用于轻松地压缩或解压缩数据。
- 要想对正在被写入一个输出流的数据进行压缩,我们可以使用createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream,将其以压缩格式写入底层的流。
- 相反,要想对从输入流读取而来的数据进行解压缩,则调用createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,从而从底层的流读取未压缩的数据。
public class TestCompress {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String sourcePath = "e:/input/web.log";
String destPath = "e:/input/web";
// String clazz = "org.apache.hadoop.io.compress.DefaultCodec";
// String clazz = "org.apache.hadoop.io.compress.GzipCodec";
// String clazz = "org.apache.hadoop.io.compress.BZip2Codec";
// compress(sourcePath, destPath, clazz);
String fileName = "e:/input/web.bz2";
deCompress(fileName);
}
// 解压
private static void deCompress(String fileName) throws IOException {
// 1. 校验是否能解压缩
CompressionCodecFactory codecFactory = new CompressionCodecFactory(new Configuration());
CompressionCodec codec = codecFactory.getCodec(new Path(fileName));
if (codec == null){
System.out.println("cannot find codes for file "+ fileName);
return;
}
// 2.获取输入流
FileInputStream fis = new FileInputStream(new File(fileName));
CompressionInputStream cis = codec.createInputStream(fis);
// 3.获取输出流
FileOutputStream fos = new FileOutputStream(new File(fileName + ".decoded"));
// 4.流的对拷
IOUtils.copyBytes(cis, fos, 1024*1024*5, false);
fos.close();
cis.close();
fis.close();
}
// 压缩
private static void compress(String sourcePath, String destPath, String clazz) throws IOException,
ClassNotFoundException {
// 1.获取输入流
FileInputStream fis = new FileInputStream(new File(sourcePath));
// 利用反射获取类信息
Class<?> codeClass = Class.forName(clazz);
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codeClass,
new Configuration());
// 2.获取输出流
FileOutputStream fos = new FileOutputStream(new File(destPath + codec.getDefaultExtension()));
// 转换成可以进行压缩的流
CompressionOutputStream cos = codec.createOutputStream(fos);
IOUtils.copyBytes(fis, cos, 1024*1024*5, false);
cos.close();
fos.close();
fis.close();
}
}
Map输出采用压缩:
// 开启Map段输出压缩
configuration.setBoolean("mapreduce.map.output.compress", true);
// 设置map段输出压缩方式
configuration.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
Reduce是输出采用压缩:
//设置reduce段输出压缩开启
FileOutputFormat.setCompressOutput(job, true);
// 设置压缩方式
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);