1. 要压缩什么?1.1 压缩输入文件1.2 压缩输出文件1.3 压缩Map输出2. 常见压缩格式2.1 Gzip2.2 Bzip22.3 LZO2.4 Snappy3. 折衷4. 有关压缩和输入拆分的问题5. IO密集型与CPU密集型6. 总结6.1 需要压缩原因6.2 不需要压缩原因
文件压缩带来两大好处:它减少了存储文件所需的空间,并加速了数据在网络或者磁盘上的传输速度。在处理大量数据时,这两项节省可能非常重要,因此需要仔细考虑如何在 Hadoop 中使用压缩。
1. 要压缩什么?
1.1 压缩输入文件
如果输入文件是压缩的,那么从HDFS读入的字节就会减少,这意味着读取数据的时间会减少。对于提升作业执行的性能是有帮助的。
如果输入文件被压缩,在 MapReduce 读取时会自动解压缩,根据文件扩展名来确定使用哪个编解码器。例如,以
.gz
结尾的文件可以被识别为 gzip
压缩文件,因此使用 GzipCodec
进行读取。1.2 压缩输出文件
通常我们需要将输出存储为历史文件。如果每天的输出文件很大,并且我们需要存储历史结果以供将来使用,那么这些累积结果将占用大量的 HDFS 空间。但是,这些历史文件可能不会非常频繁地被使用,导致浪费 HDFS 空间。因此,在 HDFS 上存储之前,需要压缩输出。
1.3 压缩Map输出
即使你的 MapReduce 应用程序读取和写入未压缩的数据,它也可能从压缩 Map 阶段的中间输出中受益。由于 Map 输出被写入磁盘并通过网络传输到 Reducer 节点,所以通过使用 LZO 或 Snappy 等快速压缩器,由于减少了传输的数据量从而获得性能提升。
2. 常见压缩格式
2.1 Gzip
gzip 是 Hadoop 内置压缩方法,基于 DEFLATE 算法,组合 LZ77 和 Huffman 编码。
2.2 Bzip2
bzip2 能够进行高质量的数据压缩。它利用先进的压缩技术,能够把普通的数据文件压缩10%至15%,压缩的速度和解压的效率都非常高!支持大多数压缩格式,包括tar、gzip 等等。
2.3 LZO
LZO压缩格式由许多较小(大约256K)的压缩数据块组成,因此允许作业沿着块边界进行分割。此外,设计时考虑了速度要素,目的是达到与硬盘读取速度相当的压缩速度:压缩速度是 gzip 的5倍,解压缩速度是 gzip 的2倍。同一个文件用 LZO 压缩后比用 gzip 压缩后大50%,但比压缩前小20-50%,这对改善性能非常有利,Map 阶段完成时间要快四倍。
2.4 Snappy
Snappy 是一个压缩/解压库。它的目标不是最大压缩率,也不关心与任何其他压缩库的兼容性。相反,它旨在提供非常高的速度和合理的压缩。例如,与 zlib 的最快压缩模式相比,Snappy 对于大多数输入都快了一个数量级,但是生成的压缩文件都要比 zlib 模式大20%到100%。在一个64位,单核酷睿i7处理器上,Snappy 压缩速度在 250 MB/秒以上,解压缩速度在 500 MB/秒以上。Snappy 广泛应用于 Google 内部,BigTable,MapReduce 以及内部 RPC 系统各个地方都在使用。
3. 折衷
所有压缩算法都在空间与时间上进行权衡:更快的压缩和解压缩速度通常以更少的空间节省为代价,意味着耗费更大的空间。上表中列出的工具通常会在压缩时通过提供九种不同的选项来控制这种权衡:-1表示对速度进行优化,-9表示对空间进行优化。
不同的工具具有非常不同的压缩特性。Gzip 是一个通用压缩器,空间与时间权衡的更好一些。Bzip2 比 gzip 压缩更有效(压缩后文件更小),但速度较慢。 Bzip2 的解压缩速度比压缩速度快,但它仍然比其他方法慢。另一方面,LZO 和 Snappy 都对速度进行了优化,并且比 gzip 快一个数量级,但是压缩效率较低。解压缩方面 Snappy 明显快于LZO。
4. 有关压缩和输入拆分的问题
当考虑如何压缩由 MapReduce 处理的数据时,重要的是要了解压缩格式是否支持分割。考虑存储在 HDFS 中大小为 1GB 的未压缩文件。如果 HDFS 块大小为 64MB(MR1默认64MB,MR2默认128MB),文件将存储为16个块,并且使用此文件作为输入的 MapReduce 作业将创建16个 InputSplit(输入拆分),每一个 InputSplit 作为一个独立 Map 任务的输入单独进行处理。
假设我们有一个大小为 1GB 的 gzip 压缩文件,和以前一样,HDFS 将文件存储为16块。然而,无法为每个块创建 InputSplit,因为不能从 gzip 数据流中的任意位置开始读取,因此 Map 任务不可能独立于其他 Map 任务而只读取一个 InputSplit 中的数据。gzip 格式使用 DEFLATE 算法存储压缩数据,DEFLATE 算法将数据存储为一系列压缩的数据块。问题在于,用任何方法也不能区分每个块的开始位置,每个块的开始位置保证了允许从流中的任意位置能够读到下一个块的开始位置,这就意味着能够读出单个块的数据。因此,gzip 不支持拆分。
在这种情况下,MapReduce 不会尝试对压缩文件进行分割,因为 MapReduce 知道输入文件是通过 gzip 压缩(通过查看文件扩展名),并且知道 gzip 不支持分割。这种情况下 MapReduce 还是会继续工作的,但是以牺牲数据局部性的特性为代价:单个 Map 将会处理 16个 HDFS 块,大部分都不会在 Map 本地节点。此外,使用较少的 Mapper,作业的粒度变小,因此可能运行较长时间。
假设示例中的文件是一个 LZO 文件,我们也会遇到同样的问题,因为底层的压缩格式不能提供一种方法与流同步读取。但是,可以使用 Hadoop LZO 库附带的索引器工具处理 LZO 文件。该工具建立分割点的索引,当使用恰当的 MapReduce 输入格式时,可以有效地使他们进行拆分。另一方面,bzip2 文件在块之间提供了同步标记(pi的48位近似),因此它支持拆分。
5. IO密集型与CPU密集型
在 HDFS 中存储压缩数据能够进一步分配你的硬件,因为压缩数据通常是原始数据大小的25%。此外,由于 MapReduce 作业几乎都是IO密集型,存储压缩数据意味着整体上更少的IO处理,意味着作业运行更快。然而,有两个注意事项:
- 一些压缩格式不能拆分来并行处理
- 一些解压速度比较慢,作业变为CPU密集型,抵消你在IO上的收益。
gzip 压缩格式说明了第一个注意事项。假设有一个 1.1GB 的 gzip 文件,并且集群中块大小为 128MB。这个文件分割为 9 个 HDFS 块,每个大约128MB。为了在 MapReduce 作业中并行处理这些数据,每个块将由不同的 Mapper 负责。但这意味着第二个 Mapper 将在文件中大约 128MB 的任意字节处开始。gzip 用于解压缩输入的上下文字典在这为空,这意味着 gzip 解压缩器将无法正确解释字节。结果就是,Hadoop 中的大型 gzip 文件需要由单个 Mapper 处理,这违背了并行性的目的。
Bzip2压缩格式说明了作业成为CPU密集型的第二个注意事项。Bzip2文件压缩效果良好,也可以拆分,但是解压缩算法速度比较慢,无法跟上在 Hadoop 作业中常见的流式磁盘读取。虽然 Bzip2 压缩效果良好,节省了存储空间,但是正在运行的作业需要花费时间等待CPU完成解压数据,这会降低整体速度并抵消其他收益。
6. 总结
6.1 需要压缩原因
- 数据存储但不经常处理。这是通常的 DWH 场景。在这种情况下,空间节省可能比处理开销更重要;
- 压缩因子非常高,节省了大量的IO;
- 解压缩非常快(例如 Snappy)使我们有一定的收益;
- 数据已经到达压缩状态(Data already arrived compressed)
6.2 不需要压缩原因
- 压缩数据不可拆分。 必须注意的是,现在许多格式都是以块级压缩构建的,以实现文件的拆分和部分处理;
- 数据在集群中创建,压缩需要很长时间。必须注意,压缩通常比解压缩需要更多的CPU;
- 数据几乎没有冗余,没有必要进行压缩;