Hadoop2.7.3之文件压缩
一、概述
压缩技术能够有效减少底层存储系统(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支持的压缩编码
为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示:
压缩性能的比较:
可以访问官网进行查看:
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.
三、各种压缩方式详解
3.1 Gzip压缩
- 优点:压缩率比较高,而且压缩/解压速度也比较快;hadoop本身支持,在应用中处理gzip格式的文件就和直接处理文本一样;大部分linux系统都自带gzip命令,使用方便。
- 缺点:不支持split。
应用场景:当每个文件压缩之后在130M以内的(1个块大小内),都可以考虑用gzip压缩格式。譬如说一天或者一个小时的日志压缩成一个gzip文件,运行mapreduce程序的时候通过多个gzip文件达到并发。hive程序,streaming程序,和java写的mapreduce程序完全和文本处理一样,压缩之后原来的程序不需要做任何修改。
3.2 lzo压缩
- 优点:压缩/解压速度也比较快,合理的压缩率;支持split,是hadoop中最流行的压缩格式;可以在linux系统下安装lzop命令,使用方便。
- 缺点:压缩率比gzip要低一些;hadoop本身不支持,需要安装;在应用中对lzo格式的文件需要做一些特殊处理(为了支持split需要建索引,还需要指定inputformat为lzo格式)。
应用场景:一个很大的文本文件,压缩之后还大于200M以上的可以考虑,而且单个文件越大,lzo优点越越明显。
3.3 snappy压缩
- 优点:高速压缩速度和合理的压缩率。
- 缺点:不支持split;压缩率比gzip要低;hadoop本身不支持,需要安装;
应用场景:当mapreduce作业的map输出的数据比较大的时候,作为map到reduce的中间数据的压缩格式;或者作为一个mapreduce作业的输出和另外一个mapreduce作业的输入。
3.3 bzip2压缩
- 优点:支持split;具有很高的压缩率,比gzip压缩率都高;hadoop本身支持,但不支持native;在linux系统下自带bzip2命令,使用方便。
- 缺点:压缩/解压速度慢;不支持native。
应用场景:适合对速度要求不高,但需要较高的压缩率的时候,可以作为mapreduce作业的输出格式;或者输出之后的数据比较大,处理之后的数据需要压缩存档减少磁盘空间并且以后数据用得比较少的情况;或者对单个很大的文本文件想压缩减少存储空间,同时又需要支持split,而且兼容之前的应用程序(即应用程序不需要修改)的情况。
3.4 如何选择压缩格式?
- Hadoop应用处理的数据集非常大,因此需要借助于压缩。使用哪种压缩格式与待处理的文件的大小、格式和所使用的工具相关。下面我们给出了一些建议,大致是按照效率从高到低排序的。
1)使用容器文件格式,例如顺序文件、RCFile或者Avro 数据文件,所有这些文件格式同时支持压缩和切分。通常最好与一个快速压缩工具联合使用,例如LZO,LZ4或者 Snappy。
2)使用支持切分的压缩格式,例如bzip2(尽管bzip2 非常慢),或者使用通过索引实现切分的压缩格式,例如LZO。
3)在应用中将文件切分成块,并使用任意一种压缩格式为每个数据块建立压缩文件(不论它是否支持切分)。这种情况下,需要合理选择数据块的大小,以确保压缩后数据块的大小近似与HDFS块的大小。
4)存储未经压缩的文件。
对大文件来说,不要使用不支持切分整个文件的压缩格式,因为会失去数据的本地特性,进而造成MapReduce应用效率低下。
四、压缩/解压案例
-
说明:
CompressionCodec有两个方法可以用于轻松地压缩或解压缩数据。要想对正在被写入一个输出流的数据进行压缩,我们可以使用createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream,将其以压缩格式写入底层的流。相反,要想对从输入流读取而来的数据进行解压缩,则调用createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,从而从底层的流读取未压缩的数据。我们采用如下表格所示压缩方式进行压缩:
-
编写步骤:
- 引入Hadoop相关依赖(这里引入所有依赖),依赖如下:
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-common</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.7.3</version> </dependency>
- 创建maven工程,创建测试类,编写如下代码:
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.CompressionOutputStream; import org.apache.hadoop.util.ReflectionUtils; import java.io.*; public class CompressedDemo { // 压缩文件 public static void compress(String codedMethod) throws IOException, ClassNotFoundException { // 指定需要压缩的文件 File file = new File("d:/access.log"); // 利用输入流读取文件内容 InputStream inputStream = new FileInputStream(file); // 利用Java反射机制获取到编码/解码器 Class<?> codedClass = Class.forName(codedMethod); // 通过反射工具类获取真正的压缩编码/编码器 Configuration conf = new Configuration(); CompressionCodec compressionCodec = (CompressionCodec) ReflectionUtils.newInstance(codedClass, conf); // 创建一个带有扩展名的文件,即最终输出的压缩文件 File fileOut = new File("d:/access" + compressionCodec.getDefaultExtension()); fileOut.delete();// 如果之前存在该文件则先删除掉,成功返回true,否则返回false // 创建输出流对象 OutputStream outputStream = new FileOutputStream(fileOut); // 创建压缩的输出流对象 CompressionOutputStream cos = compressionCodec.createOutputStream(outputStream); // 利用IOUtils按照字节实现流的复制与压缩 IOUtils.copyBytes(inputStream,cos,4096,false); // 关闭资源 if (cos != null){ cos.close(); } if (outputStream != null){ outputStream.close(); } if (inputStream != null){ inputStream.close(); } } public static void decompress(File file) throws IOException { Configuration conf = new Configuration(); CompressionCodecFactory factory = new CompressionCodecFactory(conf); CompressionCodec codec = factory.getCodec(new Path(file.getName())); if( codec == null ) { System.out.println("Cannot find codec for file "+file); return; } File fileOut = new File(file.getName()+".txt"); InputStream in = codec.createInputStream( new FileInputStream(file) ); OutputStream out = new FileOutputStream(fileOut); IOUtils.copyBytes(in, out, 4096, false); in.close(); out.close(); } public static void main(String[] args) throws Exception { // 使用编码/解码器对本地文件进行压缩 compress("org.apache.hadoop.io.compress.DefaultCodec"); compress("org.apache.hadoop.io.compress.GzipCodec"); compress("org.apache.hadoop.io.compress.BZip2Codec"); // 解压 decompress(new File("d:/access.deflate")); decompress(new File("d:/access.gz")); decompress(new File("d:/access.bz2")); } }
- 引入Hadoop相关依赖(这里引入所有依赖),依赖如下:
-
案例结果
-
压缩结果(代码中文件路径是绝对路径)
-
解压结果(代码中文件路径是相对路径)
在IDEA中的的工程目录下会看到生成的解压文件:
在工程所在目录下看到解压文件,如下所示:
-