【Hadoop06】:Hadoop压缩

前言

Hadoop集群能够存储非常庞大数量的数据文件,我们可以使用压缩技术来降低存储空间的使用量。

将文件压缩有两大好处:减少存储文件所需要的磁盘空间,并加速数据在网络和磁盘上的传输。这两大好处在处理大量数据时相当重要,所以我们值得仔细考虑在Hadoop中文件压缩的用法。

注意,Hadoop的压缩并不是指你上传一个文本文件,自动为你压缩,或者说你下载一个压缩文件,自动帮你解压。而是指,MapReduce程序在进行数据处理时,可以自动将所支持的压缩文件解压缩,转换为普通文本数据进行处理,以及处理完成后,根据所配置的压缩算法,将结果数据压缩后保存至HDFS。

当前有很多不同的压缩格式、工具和算法,它们各有千秋。下表是与Hadoop结合使用的常见压缩算法。

压缩格式 工具 算法 文件扩展名 是否可切分
DEFLATE DEFLATE .deflate
gzip gzip DEFLATE .gz
bzip2 bzip2 bzip2 .bz2
LZO lzop LZO .lzo
LZ4 LZ4 .lz4
Snappy Snappy .snappy

注意,DEFLATE是一个标准的压缩算法,该算法的标准实现时zlib。没有可用于生成DEFLATE文件的常用命令行工具,因为通常都使用gzip格式。注意,gzip文件格式只是在DEFLATE格式上增加了文件头和一个文件尾。.deflate文件扩展名是Hadoop约定的。

另外,如果LZO文件已经在预处理过程中被索引了,那么LZO文件是可切分的。

所有压缩算法都需要权衡时间和空间,压缩和解压缩的速度更快,其代价通常只能是节省少量空间。一般的压缩工具都会提供参数来设置速度优先还是空间优先,下面是gzip命令的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ll 1.txt 
-rw-rw-r-- 1 hadoop hadoop 102691656 Mar 6 15:52 1.txt
# 速度优先
$ gzip -1 1.txt
$ ll 1.txt.gz
-rw-rw-r-- 1 hadoop hadoop 729026 Mar 6 15:52 1.txt.gz

$ ll 2.txt
-rw-rw-r-- 1 hadoop hadoop 102691656 Mar 6 15:53 2.txt
# 空间优先
$ gzip -9 2.txt
$ ll 2.txt.gz
-rw-rw-r-- 1 hadoop hadoop 348532 Mar 6 15:53 2.txt.gz

不同的压缩工具有不同的压缩特性,gzip是一个通用的压缩工具,在空间和时间性能上较为适中。bzip2的压缩能力强于gzip,但压缩速度慢一些。LZO、LZ4和Snappy优化了压缩速度,其速度比gzip快一个数量级,但压缩效率稍逊一筹。

是否可切分表示对应的压缩算法是否支持切分,也就是说,是否可以搜索数据流的任意位置并进一步往下读取数据。可切分压缩格式尤其适合MapReduce,稍后详细进行讨论。

Codec

Codec是压缩-解压缩算法的一种实现。在Hadoop中,一个对CompressionCodec接口的实现代表一个codec。例如,GzipCodec包装了gzip的压缩和解压缩算法。下表是Hadoop实现的codec。

压缩格式 HadoopCompressionCodec
DEFLATE org.apache.hadoop.io.compress.DefaultCodec
gzip org.apache.hadoop.io.compress.GzipCodec
bzip2 org.apache.hadoop.io.compress.BZip2Codec
LZO org.apache.hadoop.io.compress.LzopCodec
LZ4 org.apache.hadoop.io.compress.Lz4Codec
Snappy org.apache.hadoop.io.compress.SnappyCodec

使用CompressionCodec对数据流进行压缩和解压缩

CompressionCodec包含两个函数,可以轻松用于压缩和解压缩数据。如果要对输出数据流进行压缩,可以使用createOutputStream(OutputStream out)方法在底层的数据流中对需要以压缩格式写入的数据新建一个CompressionOutputStream对象。相反,对输入数据流中读取数据进行解压缩的时候,应该使用createInputStream(InputStream in)来读取解压缩后的数据。

下面我们使用相关的API来完成压缩和解压缩操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo03 {
public static void main(String[] args) throws Exception {
// 创建对应的压缩算法实现类
CompressionCodec codec =
new GzipCodec();
((GzipCodec) codec).setConf(new Configuration());
FileSystem fs = FileSystem.get(new URI("hdfs://company01:9000"), new Configuration(), "hadoop");
// 压缩写入
FSDataOutputStream out = fs.create(new Path("/myfile.gz"));
CompressionOutputStream compressionOutputStream = codec.createOutputStream(out);
compressionOutputStream.write("Hello GzipCodec!".getBytes("UTF-8"));
compressionOutputStream.close();
out.close();

// 读取压缩数据
FSDataInputStream in = fs.open(new Path("/myfile.gz"));
CompressionInputStream compressionInputStream = codec.createInputStream(in);
IOUtils.copyBytes(compressionInputStream, System.out, 4096);
compressionInputStream.close();
in.close();

fs.close();
}
}

通过扩展名推断CompressionCodec

在读取一个压缩文件时,通常可以根据文件的后缀名来推断需要使用哪个Codec。CompressionCodecFactory提供了getCode()方法可以通过文件的后缀返回对应的Codec。下面是使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo05 {
public static void main(String[] args) throws Exception {
FileSystem fs = FileSystem.get(new URI("hdfs://company01:9000"), new Configuration(), "hadoop");
Path path = new Path("/myfile.gz");
// 创建CompressionCodecFactory
CompressionCodecFactory factory = new CompressionCodecFactory(new Configuration());
// 根据后缀名推断
CompressionCodec codec = factory.getCodec(path);
// 读取数据
FSDataInputStream in = fs.open(path);
CompressionInputStream compressionInputStream = codec.createInputStream(in);
IOUtils.copyBytes(compressionInputStream, System.out, 4096, true);

fs.close();
}
}

这里多说一句,MapReduce就是通过这种方式推断压缩文件响应的codec,在读取文件时自动解压缩。

原生类库

为了提高性能,最好使用原生的native类库来实现压缩和解压缩。下表说明了每种压缩格式是否有Java实现和原生类库实现。

压缩格式 是否有Java实现 是否有原生实现
DEFLATE
gzip
bzip2
LZO
LZ4
Snappy

从这里也可以看出,如果文件采用有Java实现方式的压缩格式,那么数据的压缩和解压缩操作可以在任意节点上运行。如果没有Java实现,那么程序就必须运行在有本地库的节点上。

Hadoop的安装包中本身包含了64位Linux构建的原生压缩二进制代码,称为libhadoop.so。对于其他平台,需要自己根据位于源码中的BUIDING.txt指令编译代码库。

默认情况下,Hadoop会根据自身运行的平台搜索原生代码库,如果找到相应的代码库就会自动加载。这意味着,你无需为了使用原生代码库而修改任何设置。但是在某些情况下,可能需要禁用原生代码库,而使用内置的Java代码库(如果有的话),只需要将io.native.lib.available设置为false即可。

压缩和输入分片

在考虑如何压缩将要由MapReduce处理的数据时,理解这些压缩格式是否支持切分是非常重要的。例如一个文件大小为1GB,如果HDFS设置的block大小为128MB,那么该文件就会被切分为8个数据块。将这个文件作为MapReduce的输入,将创建8个输入分片,其中每个分片作为一个单独的map任务的输入被独立处理。

假如文件是经过gzip压缩的,且压缩后大小为1GB。但是将每个数据块作为一个单独的分片无法工作,因为gzip不支持切分,无法从任意位置读取数据。

在这种情况下,MapReduce会采用正确的做法,它不会尝试切分gzip压缩文件,因为它知道输入的文件格式并知道它不支持切分。所以,只会有1个map任务来处理8个HDFS数据块,牺牲了数据本地性。

如何选择压缩算法

Hadoop应用处理的数据集非常大,因此需要借助与压缩。使用哪种压缩格式与待处理的文件大小、格式和所使用的工具相关。下面是一些建议,大致是按照效率从高到低排列的。

  • 使用容器文件格式。例如SequenceFile、Avro数据文件、ORCFiles或Parquet。这些文件格式同时支持压缩和切分,通常最好与一个快速压缩工具联合使用,如LZO、LZ4或Snappy
  • 使用支持切分的压缩格式,例如bzip2,尽管它很慢。或者使用通过索引实现切分的压缩格式,如LZO
  • 在应用中将文件切分为块,并使用任意一种压缩格式为每个数据块建立压缩文件,在这种情况下,需要合理选择数据块的大小,以确保压缩后的数据块的大小近似于HDFS块大小
  • 存储未压缩的文件

对于大文件来说,不要使用不支持切分整个文件的压缩格式,因为会失去数据的本地特性,进而造成MapReduce应用效率低下。

小结

本章介绍了Hadoop支持压缩格式,以及各种压缩格式之间的差异。在Hadoop中,所支持的每种压缩格式都是CompressionCodec接口的子类,可以通过CompressionCodecFactory根据文件名称自动推断出对应的压缩格式,这样就实现了自动解压操作,实际上在MapReduce中就是如此进行的。接下来我们就如何选择压缩算法,以及压缩文件是否支持切分对MapReduce程序的影响进行了讨论。

如果您觉得不错,请赞赏一下!