2.3 数据压缩


文档摘要

2.3 数据压缩 本节摘要:压缩在 MapReduce 全链路有三个安放位置:输入侧、中间数据侧、最终输出侧。选对位置与编码,能同时省磁盘、省网络、省时间;选错则适得其反——尤其是"不可分割"的压缩格式会摧毁分片机制。本节给出编码对比、配置方法与选择决策。 压缩解决的是三笔账 MapReduce 的每一层 IO 都在花钱:输入从 HDFS 磁盘读、中间数据从 Map 节点磁盘经网络流向 Reduce 节点、最终结果再写回 HDFS。压缩在这三个位置分别起作用: 输入侧:文件以压缩形式存储,读盘量按压缩比缩减,磁盘与总线压力同步下降; 中间侧(Shuffle 传输):Map 输出先压缩再落盘、再传输,Reduce 端解压。

2.3 数据压缩

本节摘要:压缩在 MapReduce 全链路有三个安放位置:输入侧、中间数据侧、最终输出侧。选对位置与编码,能同时省磁盘、省网络、省时间;选错则适得其反——尤其是"不可分割"的压缩格式会摧毁分片机制。本节给出编码对比、配置方法与选择决策。

压缩解决的是三笔账

MapReduce 的每一层 IO 都在花钱:输入从 HDFS 磁盘读、中间数据从 Map 节点磁盘经网络流向 Reduce 节点、最终结果再写回 HDFS。压缩在这三个位置分别起作用:

  1. 输入侧:文件以压缩形式存储,读盘量按压缩比缩减,磁盘与总线压力同步下降;
  2. 中间侧(Shuffle 传输):Map 输出先压缩再落盘、再传输,Reduce 端解压。这一步直接砍掉 Shuffle 的磁盘写入量与网络传输量,通常是最划算的一处——中间数据只活几分钟,却贡献了作业相当比例的 IO;
  3. 输出侧:最终结果压缩存储,省的是长期磁盘占用,以及下游作业的读取成本。

代价当然也有:压缩与解压要消耗 CPU。所以压缩选择本质上是在"IO 省下的时间"与"CPU 花去的时间"之间做交换。当代集群的趋势是磁盘与网络相对更慢、CPU 核相对富裕,这笔交换越来越划算,中间数据几乎总是应该压缩。

编编码对比:速度、压缩比与可分割性

常用编码各有性格,三个维度缺一不可:

编码 压缩比(典型) 压缩解压速度 可分割 出品方与定位
gzip 高,约 2.5–3 倍 通用,适合冷数据存档
bzip2 很高,约 4–5 倍 很慢 极致省空间,速度代价大
lzo 中,约 2 倍 需建索引才可分割 曾流行,需额外安装
snappy 中低,约 2 倍 极快 Google 出品,热路径首选
zstandard 中高,约 3 倍 新生代,速度与比兼顾

"可分割"决定了一个压缩文件能否被切成多个分片并行处理。gzip 流式编码从头到尾是一个整体,读到中间任何位置都无法独立解压——一个 10GB 的 gzip 文件只能有一个分片、一个 Map 任务,第一幕的并行机制直接失效。bzip2 在块边界上可以独立解压,zstandard 同样支持切割,snappy 与 gzip 不行。

这就得出第一条选型铁律:输入要并行处理的文件,要么用可分割编码,要么在写入时就切成多个可独立解压的成员文件(例如按块输出的分成员压缩文件)。

怎么配

中间数据压缩只需两行作业配置,是收益成本比最高的动作:

// 开启 Map 输出压缩 使用 snappy 换速度 Configuration conf = job.getConfiguration(); conf.setBoolean("mapreduce.map.output.compress", true); conf.set("mapreduce.map.output.compress.codec", "org.apache.hadoop.io.compress.SnappyCodec");

最终输出压缩配在 FileOutputFormat 上:

// 输出用 gzip 换空间 结果多为一次写多次读 FileOutputFormat.setCompressOutput(job, true); FileOutputFormat.setOutputCompressorClass(job, "org.apache.hadoop.io.compress.GzipCodec".getClass() .forName("org.apache.hadoop.io.compress.GzipCodec"));

集群层面还可设默认编码(如中间压缩默认 snappy),让所有作业共享合理缺省。命令行用户则有更顺手的方式,经典 MapReduce 提供流式接口,参数照配即可:

hadoop jar share/hadoop/tools/lib/hadoop-streaming.jar \ -input /data/logs/2026-08 \ -output /out/uv_daily \ -mapper "cut -d' ' -f3" \ -reducer "uniq -c" \ -compress -codec org.apache.hadoop.io.compress.SnappyCodec

每个位置该怎么选

把三个位置分开决策,是我的习惯做法:

输入侧:历史冷数据归档用 gzip 或 bzip2,省空间优先;待反复分析的活跃数据用 zstandard 或分成员的 lzo,兼顾读取并行。若拿到手的已是 10GB 整体 gzip,先跑一个"解压重切"的准备作业把它变成可分割形态,再进入主计算,否则一个 Map 任务串行读完全量,慢得离谱。

中间侧:无脑开,编码选 snappy(或 zstandard)。中间数据生命周期只有作业时长,压缩比略低无妨,速度最重要——它卡在 Shuffle 的关键路径上。

输出侧:看下游。若输出会被后续作业反复读,考虑下游读取场景选可分割编码;若直接沉入数据湖供偶尔回溯,gzip 级别的高压缩比更合适。

图 2.3-1 压缩在执行链路的三个安放点

图 2.3-1 压缩在执行链路的三个安放点

一个真实教训

曾接手一个日志统计作业,输入是第三方每天发来的整体 gzip 包,单文件 8GB。作业跑了四小时,Map 阶段只有一个任务。原因正是"整体 gzip 不可分割"——第一幕只规划出一个分片,几百台机器看着一个任务串行解压全量。修复方式不是换编码(输入格式无法要求上游),而是在管道前加一个纯 Map 的转码作业:读入、按 256MB 一组解压重写为分成员压缩的容器格式,后续作业立刻恢复上百路并行,总时长降到二十分钟以内。多花的一次转码,换回了整个集群的并行度。

本节要点回顾

  • 三个安放点:输入侧省读盘、中间侧省 Shuffle 磁盘与网络、输出侧省长期存储,各有各的选型逻辑。
  • 可分割性:决定压缩文件能否多分片并行,整体 gzip 是并行度杀手,bzip2 与 zstandard 可分割。
  • 中间必开:中间数据短命但 IO 占比高,用 snappy 级快速编码,收益成本比最高。
  • 按下游选输出:反复读选可分割,归档选高压缩比。
  • 本质:压缩是拿 CPU 换 IO,当代集群 CPU 富余 IO 紧张,多数位置这笔交换都划算。

第一幕的三个决策到此齐备。下一章进入第二幕:Map 任务开机后,数据如何流过环形缓冲区走向汇流。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U