MapReduce框架-数据压缩
概述
压缩技术能够有效减少底层存储系统(HDFS)读写字节数。压缩提高了网络带宽和磁盘空间的效率。在Hadood下,尤其是数据规模很大和工作负载密集的情况下,使用数据压缩显得非常重要。在这种情况下,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支持的压缩编码
压缩格式 |
工具 |
算法 |
文件扩展名 |
是否可切分 |
DEFAULT |
无 |
DEFAULT |
.deflate |
否 |
Gzip |
gzip |
DEFAULT |
.gz |
否 |
bzip2 |
bzip2 |
bzip2 |
.bz2 |
是 |
LZO |
lzop |
LZO |
.lzo |
否 |
LZ4 |
无 |
LZ4 |
.lz4 |
否 |
Snappy |
无 |
Snappy |
.snappy |
否 |
为了支持多种压缩/解压缩算法,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 |
LZ4 |
org.apache.hadoop.io.compress.Lz4Codec |
Snappy |
org.apache.hadoop.io.compress.SnappyCodec |
压缩性能的比较
压缩算法 |
原始文件大小 |
压缩文件大小 |
压缩速度 |
解压速度 |
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 |
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.
压缩方式的选择
Gzip压缩
优点:压缩率比较高,而且压缩/解压速度也比较快;Hadoop本身支持,在应用中处理Gzip格式的文件就和直接处理文本一样;大部分Linux系统都自带Gzip命令,使用方便。
缺点:不支持Split。
应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用Gzip压缩格式。例如说一天或者一个小时的日志压缩成一个Gzip文件。
Bzip2压缩
优点:支持Split;具有很高的压缩率,比Gzip压缩率都高;Hadoop本身自带,使用方便。
缺点:压缩/解压速度慢。
应用场景:适合对速度要求不高,但需要较高的压缩率的时候;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持Split,而且兼容之前的应用程序的情况。
Lzo压缩
优点:压缩/解压速度也比较快,合理的压缩率;支持Split是Hadoop中最流行的压缩格式;可以在Linux系统下安装lzop命令,使用方便。
缺点:压缩率比Gzip要低一些; Hadoop本身不支持,需要安装;在应用中对Lzo格式的文件需要做一些特殊处理(为了支持Split需要建索引,还需要指定InputFormat为Lzo格式)。
应用场景:一个很大的文本文件.压缩之后还大于200M以上的可以考虑,而且单个文件越大,Lzo优点越越明显。
Snappy压缩
优点:高速压缩速度和合理的压缩率。
缺点:不支持Split;压缩率比Gzip要低;Hadoop本身不支持,需要安装。
应用场景:当MapReduce作业的Map输出的数据比较大的时候.作为Map到Reduce的中间数据的压缩格式;或者作为一个MapReduce作业的输出和另外一个MapReduce作业的输入。
采用压缩的位置
压缩可以在MapReduce作用的任意阶段启用。
1)mapper输入压缩:
在有大量数据并计划重复处理的情况下,应该考虑对输入进行压缩。然而,你无须显示指定使用的编解码方式。Hadoop自动检查文件扩展名,如果扩展名能够匹配,就会用恰当的编解码方式对文件进行压缩和解压。否则,Hadoop就不会使用任何编解码器。
2)压缩mapper输出:
当map任务输出的中间数据量很大时,应考虑在此阶段采用压缩技术。这能显著改善内部数据Shuffle过程,而Shuffle过程在Hadoop处理过程中是资源消耗最多的环节。如果发现数据量大造成网络传输缓慢,应该考虑使用压缩技术。可用于压缩mapper输出的快速编解码器包括LZO、LZ4或者Snappy。
【注意】LZO是供Hadoop压缩数据用的通用压缩编解码器。其设计目标是达到与硬盘读取速度相当的压缩速度,因此速度是优先考虑的因素,而不是压缩率。与gzip编解码器相比,它的压缩速度是gzip的5倍,而解压速度是gzip的2倍。同一个文件用LZO压缩后比用gzip压缩后大50%,但比压缩前小25%~50%。这对改善性能非常有利,map阶段完成时间快4倍。
3)压缩reducer输出:
在此阶段启用压缩技术能够减少要存储的数据量,因此降低所需的磁盘空间。当mapreduce作业形成作业链条时,因为第二个作业的输入也已压缩,所以启用压缩同样有效。
【注意】MapReduce程序只需要管压缩,不需要管解压缩。因为MR程序在读取压缩文件的时候会根据压缩文件类型自动解压缩
压缩配置参数
要在Hadoop中启用压缩,可以配置如下参数(mapred-site.xml文件中):
参数 |
默认值 |
阶段 |
建议 |
io.compression.codecs (在core-site.xml中配置) |
org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec, org.apache.hadoop.io.compress.Lz4Codec |
输入压缩 |
Hadoop使用文件扩展名判断是否支持某种编解码器 |
mapreduce.map.output.compress |
false |
mapper输出 |
这个参数设为true启用压缩 |
mapreduce.map.output.compress.codec |
org.apache.hadoop.io.compress.DefaultCodec |
mapper输出 |
使用LZO、LZ4或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输出 |
SequenceFile输出使用的压缩类型:NONE和BLOCK |
压缩案例
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 |
压缩
/** * MapReduce压缩支持的位置一共有如下几个位置: * 1. Map的输入文件可以支持压缩 * 2. Map输出数据的时候也支持压缩 * 3. Reduce输出数据的时候也支持压缩 * * MapReduce程序只需要管压缩,不需要管解压缩。因为MR程序在读取压缩文件的时候会根据压缩文件类型自动解压缩 * 压缩说白了就是把一个文件去创建一个输入IO流,然后创建一个压缩工具的输出IO流,将输入流的数据传递给输出IO流就可以实现压缩 * @Author: ZYD * @Date: 2021/8/16 下午 17:26 */ public class Demo { public static void main(String[] args) throws Exception { /** * 测试Hadoop的压缩机制----将一个文件压缩成为Hadoop支持的压缩包 */ Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://192.168.218.55:9000"), conf, "root"); // 指定压缩文件 String fileName = "/school/web.log"; // 指定使用的压缩工具 // String className = "org.apache.hadoop.io.compress.DefaultCodec"; // String className = "org.apache.hadoop.io.compress.GzipCodec"; String className = "org.apache.hadoop.io.compress.BZip2Codec"; // 代表根据压缩工具的全限定类名创建一个压缩工具的编解码器 CompressionCodec cc = (CompressionCodec) ReflectionUtils.newInstance(Class.forName(className), conf); String outPath = fileName + ".bzip2"; // 压缩就是将文件重新以编码器的IO流输出即可 // 创建fileName的输入流 FSDataInputStream inputStream = fs.open(new Path(fileName)); // 解压缩 // 创建一个普通的获取文件的输出流 FSDataOutputStream fsDataOutputStream = fs.create(new Path(outPath)); // 根据普通的输出流创建一个 压缩输出流 CompressionOutputStream outputStream = cc.createOutputStream(fsDataOutputStream); IOUtils.copyBytes(inputStream, outputStream, 1024*1024, false); outputStream.close(); inputStream.close(); } }
解压缩
public class OpenDemo { public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(new URI("hdfs://192.168.218.55:9000"), conf, "root"); /** * 解压缩压缩包 */ // 1. 校验压缩包支不支持解压缩 CompressionCodecFactory factory = new CompressionCodecFactory(conf); // 2. 判断当前文件用的什么压缩编码器,如果没有对应解压缩编码器,则返回null Path p = new Path("/school/web.log.deflate"); CompressionCodec codec = factory.getCodec(p); if (codec == null) { System.out.println("当前文件不支持解压缩"); } else { // 创建文件输出流 CompressionInputStream inputStream = codec.createInputStream(fs.open(p)); // 创建解压缩输入流 FSDataOutputStream fsDataOutputStream = fs.create(new Path("/school/web-deflate.log")); IOUtils.copyBytes(inputStream, fsDataOutputStream, 1024*1024, false); fsDataOutputStream.close(); inputStream.close(); } } }
【注意】压缩和解压缩机制代码自己了解一下就可以了,真正在MR程序中,底层就是使用这种机制进行压缩和解压缩的。但是我们使用的时候,不需要写怎么压缩和解压缩,我们只需要指定是否需要开启压缩,采用什么压缩工具,那么MR底层就会帮助我们自动完成。
压缩的应用
map阶段的压缩
map端执行结束,在向reduce端溢写文件时可以将文件压缩。
即使你的MapReduce的输入输出文件都是未压缩的文件,你仍然可以对Map任务的中间结果输出做压缩,因为它要写在硬盘并且通过网络传输到Reduce节点,对其压缩可以提高很多性能,这些工作只要在Driver中设置两个属性即可:
// 开启map输出 conf.setBoolean("mapreduce.map.output.compress", true); // 一旦map端开启输出,我们就要指定map压缩采用什么压缩机制 conf.setClass("mapreduce.map.output.compress.codec", DefaultCodec.class, CompressionCodec.class);
map端开启压缩机制之后,我们从控制台是看不出来效果的。因为map输出将数据压缩之后,reduce还需要执行,或者OutputFormat还要输出数据,此时map阶段的压缩只是在map-reduce过程中将数据压缩减少传输时间。
reduce阶段的压缩
reduce阶段的压缩相当于是给最终文件进行压缩。具体步骤:
在Driver中添加如下配置:
// 给reduce端开启压缩 FileOutputFormat.setCompressOutput(job, true); // reduce端输出使用上面类型的压缩格式 FileOutputFormat.setOutputCompressorClass(job, DefaultCodec.class);