前言
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 | $ ll 1.txt |
不同的压缩工具有不同的压缩特性,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 | public class Demo03 { |
通过扩展名推断CompressionCodec
在读取一个压缩文件时,通常可以根据文件的后缀名来推断需要使用哪个Codec。CompressionCodecFactory
提供了getCode()
方法可以通过文件的后缀返回对应的Codec。下面是使用方式。
1 | public class Demo05 { |
这里多说一句,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程序的影响进行了讨论。