MapReduce框架原理之数据压缩
1. MapReduce中数据压缩的作用
- 压缩技术能够有效减少底层存储系统(HDFS)读写字节数,节省存储空间.
- 压缩可以减少网络和磁盘I/O,提高MapReduce程序运行速度.
虽然压缩节省了磁盘空间,减少了网络和磁盘I/O,但是却增加了CPU的运算负担,所以,压缩适用的基本规则:
(1)运算密集型的job,少用压缩
(2)IO密集型的job,多用压缩
2. MapReduce支持的压缩编码
2.1 压缩格式
压缩格式 | Hadoop自带 | 算法 | 文件扩展名 | 是否可切分 | 是否需修改源程序 |
---|---|---|---|---|---|
DEFLATE | 是,直接使用 | DEFLATE | .deflate | 否 | 不需要 |
Gzip | 是,直接使用 | DEFLATE | .gz | 否 | 不需要 |
bzip2 | 是,直接使用 | bzip2 | .bz | 是 |
不需要 |
LZO | 否,需要安装 | LZO | .lzo | 是 |
需要建索引,并指定输入格式 |
Snappy | 否,需要安装 | Snappy | .snappy | 否 | 不需要 |
2.2 对应的编码/解码器
压缩格式 | 编码/解码器 |
---|---|
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 |
2.3 压缩性能的比较
压缩格式 | 原始文件大小 | 压缩文件大小 | 压缩速度 | 解压速度 |
---|---|---|---|---|
Gzip | 8.3Gb | 1.8Gb | 17.5Mb/s | 58Mb/a |
bzip2 | 8.3Gb | 1.1Gb | 2.4Mb/s | 9.5Mb/s |
LZO | 8.3Gb | 2.9Gb | 49.3Mb/s | 74.6Mb/s |
http://google.github.io/snappy/
On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec
or more and decompresses at about 500 MB/sec or more.
Snappy虽然压缩比没有那么高,但是主要体现是高速.单核i7处理器64位模式下,压缩速度在250MB/s
,解压缩度在500MB/s
.
上面数字仅提供一个大概的印象参考.
3. 压缩方式选择
3.1 Gzip压缩
优点:压缩率比较高,而且压缩/解压速度也比较快;Hadoop本身支持,在应用中处理Gzip格式的文件就和直接处理文本一样;大部分Linux系统都自带Gzip命令,使用方便.
缺点:不支持Split.
应用场景:每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用Gzip压缩格式.例如一天或者一个小时的日志压缩成一个Gzip文件.
3.2 Bzip2压缩
优点:支持Split;具有很高的压缩率,比Gzip压缩率都高;Hadoop本身自带,使用方便.
缺点:压缩/解压速度慢
应用场景:适合速度要求不高,但需要较高的压缩率的时候;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用的比较少的情况;或者单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序的情况.
3.3 LZO压缩
优点:压缩/解压速度比较快,合理的压缩率;支持Split,是Hadoop中最流行的压缩格式;可以在Linux系统下安装lzop命令,使用方便.
缺点:压缩率比Gzip要低一些;Hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持Split需要建索引,还需要指定InputFormat为lzo格式)
应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo有点月明显.
3.4 Snappy压缩
优点:高速压缩和合理的压缩率
缺点:不支持Split;压缩率比Gzip要低;Hadoop本身不支持,需要安装.
应用场景:当MapReduce作业的Map输出的数据比较大的时候,作为Map到Reduce的中间数据的压缩格式;或者作为一个MapReduce作业的输出和另外一个MapReduce作业的输入.
4. 压缩位置选择
4.1 输入端压缩
在有大量数据,并计划重复处理的情况下,应该考虑对输入的数据进行压缩.
无序指定所使用的编解码方式,Hadoop自动检查文件扩展名,如果扩展名能匹配,就会用恰当的编解码方式对文件进行压缩和解压缩.否则,Hadoop就不会使用任何编解码器.
4.2 Mapper输出采用压缩
当Map任务的中间数据量很大时,应考虑在此阶段使用压缩技术.
可以显著的改善Shuffle过程,而Shuffle是Hadoop处理过程中消耗资源最多的环节.
如果发现数据量打,发生网络传输慢,则应考虑在此阶段使用有压缩技术.
可用于Mapper输出端的快速编解码器包括LZO,Snappy.
注:
LZO是提供Hadoop压缩数据的通用编解码器.其设计目标是达到与磁盘读取速度相当的压缩速度,因此速度是优先考虑的因素,而不是压缩率.
与Gzip编解码器相比,它的压缩速度竟是Gzip的5倍,解压缩速度是Gzip的2倍.用一个文件用LZO压缩后比用Gzip压缩后大50%,但比压缩前小25%~50%.这对改善性能非常有利,Map阶段完成时间快4倍.
4.3 Reducer输出采用压缩
在此阶段启用压缩技术,能够减少要存储的数据量,因此降低所需的磁盘空间
.
当MapReduce作业行成作业链条时,因为第二个作业的输入也已压缩,所以启用压缩同样有效.
5. 压缩参数配置
(1)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根据文件扩展名判断是否支持某种编解码器 |
(2)mapred-site.xml中
参数 | 默认值 | 阶段 | 建议 |
---|---|---|---|
mapreduce.map.output.compress | false | mapper输出 | 设置为true表示该阶段启用压缩 |
mapreduce.map.output.compress.codec | org.apache.hadoop.io.compress.DefaultCodec | mapper输出 | 企业应用多使用LZO或Snappy作为此阶段的编解码器 |
mapreduce.output.fileoutputformat.compress | false | reducer输出 | 设置为true表示该阶段启用压缩 |
mapreduce.output.fileoutputformat.compress.codec | org.apache.hadoop.io.compress.DefaultCodec | reducer输出 | 使用标准工具或者编解码器,如gzip和bzip2 |
mapreduce.output.fileoutputformat.compress.type | RECORD | reducer输出 | 输出所使用的压缩类型 |
6. 压缩实例操作
6.1 数据流的压缩和解压缩
CompressionCodec有两个方法可以用于轻松地压缩或解压缩数据.
- 压缩:
使用createOutputStream(OutputStream out)
方法创建一个CompressionOutputStream
,将其以压缩格式下入底层的流. - 解压缩:
使用createInputStream(InputStream in)
方法获得一个CompressionInputStream
,从而从底层的流读取为未压缩的数据.
在解压缩时需要用到CompressionCodecFactory
类,通过getCodec()
方法,可以将文件扩展名,映射到一个CompressionCodec
的方法.
package compress;
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.*;
public class FileCompress {
private static Configuration conf = new Configuration();
private static final int BUFFER = 1024 * 1024 * 4;
public static void main(String[] args) throws IOException, ClassNotFoundException {
//compress("i:\\compress\\test.docx", "org.apache.hadoop.io.compress.BZip2Codec");
decompress("i:\\compress\\test.docx.bz2", "org.apache.hadoop.io.compress.BZip2Codec");
}
private static void compress(String filename, String method) throws IOException, ClassNotFoundException {
// 1.获取输入流
FileInputStream fis = new FileInputStream(filename);
// 2.获取压缩工具类
Class codecClass = Class.forName(method);
CompressionCodec codec = (CompressionCodec) ReflectionUtils.newInstance(codecClass, conf);
// 3.获取输出流
FileOutputStream fos = new FileOutputStream(new File(filename + codec.getDefaultExtension()));
// 4.获取压缩输出流
CompressionOutputStream cos = codec.createOutputStream(fos);
// 5.流的对拷
IOUtils.copyBytes(fis, cos, BUFFER, false);
// 6.关闭流
IOUtils.closeStream(cos);
IOUtils.closeStream(fos);
IOUtils.closeStream(fis);
}
private static void decompress(String filename, String method) throws IOException {
// 1.校验能否解压
CompressionCodecFactory factory = new CompressionCodecFactory(conf);
CompressionCodec codec = factory.getCodec(new Path(filename));
if (codec == null) {
System.out.println("Can not find the codec for file " + filename);
return;
}
// 2.获取输入流
FileInputStream fis = new FileInputStream(filename);
CompressionInputStream cis = codec.createInputStream(fis);
// 3.获取输出流
FileOutputStream fos = new FileOutputStream(new File(filename + "decoded"));
// 4.流对拷
IOUtils.copyBytes(cis, fos, BUFFER, false);
// 5.关闭流
IOUtils.closeStream(fos);
IOUtils.closeStream(cis);
IOUtils.closeStream(fis);
}
}
6.2 Map输出端采用压缩
以WordCount为例,相信大家都写过WordCount的MapReduce,(如果没写过…可以看我另外一篇MapReduce概述)
在此基础上,只需要修改Driver即可,Mapper和Reducer都不需要进行改动.
而Driver中也仅仅需要添加一点改动,来告诉框架,开启Map端输压缩出及指定压缩格式
Configuration conf = new Configuration();
// 启用map输出端压缩
// conf.setBoolean("mapreduce.map.output.compress", true);
conf.setBoolean(Job.MAP_OUTPUT_COMPRESS, true); // 跟上面作用相同
// 设置map端输出的压缩格式
// conf.setClass("mapreduce.map.output.compress.codec", BZip2Codec.class, CompressionCodec.class);
conf.setClass(Job.MAP_OUTPUT_COMPRESS_CODEC, BZip2Codec.class, CompressionCodec.class);
Job job = Job.getInstance(conf); // 跟上面作用相同
...
...
输出对比
- 压缩前
- 压缩后
6.3 Reduce输出端采用压缩
还是前面WordCount的例子.
这次我们在Reduce输出端采用压缩,只需要添加如下内容即可.
// 启用reduce端输出压缩
FileOutputFormat.setCompressOutput(job, true);
// 设置reduce端输出的压缩格式
FileOutputFormat.setOutputCompressorClass(job, BZip2Codec.class);
输出文件
输出对比
- 压缩前
- 压缩后