压缩算法选择指南:LZ4、Snappy、gzip、Brotli、Zstd
一个服务每天往外吐几十 G 日志。你第一反应不是「换个压缩算法」,是「太大了,压一下」。
gzip -9 access.log,跑完一看,体积缩到十分之一。满意了。
后来日志量翻倍,gzip 跑一次要二十分钟。你开始想:能不能快点。
再后来上了 Kafka,消息要实时传。压缩不是在磁盘上慢慢压——每一条消息发出去之前都要压,收到之后都要解。慢了,消费者那边就堵;压得不够小,带宽就爆。两个都卡,但卡的维度不同。
这时候你才开始看压缩算法。不是看谁压得小,是看 哪边快、哪边小、你的瓶颈在哪边。
两个极端
压缩这件事,底层的原理都一样:找重复模式。重复的字节用更短的编码代替。但这个「找」,花的力气可以差很大。
gzip 走的是「慢慢找、使劲压」。它花大量 CPU 扫描数据,把重复模式找得尽可能彻底——压得小,但慢。LZ4 走的是「快找、差不多压」——找重复模式很快,但不太找得全——快,但压得不够小。
这两头的差距,不是百分之几十,是数量级。
| gzip -1 | gzip -9 | LZ4 | Snappy | |
|---|---|---|---|---|
| 压缩速度 | ~50 MB/s | ~10 MB/s | ~500 MB/s | ~200 MB/s |
| 解压速度 | ~100 MB/s | ~100 MB/s | ~2000 MB/s | ~500 MB/s |
| 压缩率 | ~2.5x | ~3x | ~2x | ~2x |
两个极端,背后是两种完全不同的设计哲学:
- gzip 是「存一次,读很多次」——日志归档、数据库备份、静态资源。花时间压缩不心疼,因为读的时候省的空间是持续受益的。
- LZ4 是「压一次,解一次,扔」——消息传输、RPC 调用、实时数据。每次都在压、每次都在解,每个字节都要过 CPU。压得稍微大一点没关系,CPU 不能占太久。
LZ4 已经在你的 Linux 内核里跑了十年——内存压缩用的就是它。内核不需要压得很小,但需要** 快 **,快到内存访问被压缩和解压拖慢的时间,比直接读未压缩的数据还短。
这个场景决定了 LZ4 的定位:** 不是「压得不够小」,是「快到你感觉不到它存在」。**
中间地带
极端之间,是两种不同的权衡。
Snappy 是 Google 的「够用就行」。 和 LZ4 一样走高速路线,但 Snappy 的设计前提是「我们不追求极限压缩,我们追求稳定吞吐」。Bigtable、Spanner 全在用 Snappy 压缩数据。不是 Snappy 压得好,是 Google 的数据量大到「不压缩带宽就爆,压缩慢了 CPU 就爆」,Snappy 恰好卡在这个中间:比 LZ4 慢一点,比 LZ4 压得稍微小一点,但快得足够用。
Zstd 是 Facebook 的「想两头都占」。 2015 年 Facebook 开源,目标是「LZ4 级别的速度 + gzip 级别的压缩率」。这在算法上是矛盾的,但 Zstd 靠的是动态调整:压缩级别从 1 到 22,级别越低越像 LZ4(快但大),级别越高越像 gzip(慢但小)。同一套代码,同一套格式——用 -1 就是高速场景,用 -19 就是归档场景。
这就是 Zstd 火的原因:不是你不用换格式了。 之前你要在 Kafka 里用低延迟的 LZ4,在归档里用高压缩的 gzip——两套格式,两套参数。Zstd 一套格式覆盖全部,调级别就行。
Brotli 是 Google 的「为 Web 而生」。 和 Zstd 一样两头兼顾,但 Brotli 的默认字典是 1024 种语言的常见词汇和 HTML 标签。压 Web 内容时,Brotli 比 Zstd 更小——不是 Brotli 算法更强,是它内置的字典刚好撞上你的数据。
怎么选
到这儿你可能会觉得 Zstd 是最优解——速度不差、压缩率不差、生态在涨。但实际工程中,选什么不取决于「哪个最平衡」,取决于 **你卡在哪一端 **。
问自己三件事:
1. 你的瓶颈在 CPU 还是带宽?
跑在千兆内网上,CPU 是瓶颈——每天日志几十 G,gzip 跑二十分钟,CPU 占满影响在线业务。这时候压缩不是目的,是手段——你宁可压得没那么小,也要 CPU 空出来给在线流量。
跑在公网、跨机房、边缘设备——带宽是瓶颈,每个字节都要花钱。这时候你宁可 CPU 多花一点,也要把体积压到最小。
CPU 的瓶颈选速度快的,带宽的瓶颈选压得小的。
2. 你的数据是「存一次读很多次」还是「压一次解一次就扔」?
日志归档、数据库备份、CDN 静态资源——存一次,读很多次。压缩的时间花在写入上,解压的时间分摊在所有读取上。gzip 或 Zstd 高级别是给这个场景的。
消息队列、RPC 调用、实时传输——每条数据都要过压缩,每个字节都要占 CPU。压缩的代价不摊在后续读取上,全在当下的延迟里。LZ4 或 Snappy 是给这个场景的。
一个容易踩的坑:用 gzip 压 Kafka 消息。gzip -9 压得上,但每条消息都要压、每条消息都要解。你的消费者 CPU 全烧在 gzip 上,吞吐直接腰斩。
3. 你是不是需要一套格式覆盖多个场景?
数据流经不同阶段——日志从 Kafka 进来,存到磁盘归档,再被 HTTP 请求拉走——每个阶段对压缩的要求不同。Kafka 要快,归档要小,HTTP 要兼容浏览器。
之前你要在不同阶段切不同压缩算法——Kafka 用 LZ4,归档用 gzip,HTTP 用 Brotli。切一次格式,解压再重压。代价不是 CPU,是** 换格式本身就消耗 CPU **。
Zstd 解决的就是这个:同一套格式,Kafka 里用 -1,归档里用 -19,HTTP 里用 -9。不换格式,不用解了再压,数据从 Kafka 到磁盘到 HTTP,全是 Zstd 格式在流转。格式一致,力度不同。
有这种「多阶段同一批数据」的需求——选 Zstd。没有——回到单阶段的答案。
每个压缩算法,都带着一个它最擅长的场景,和一个它一进去就容易踩的坑。
- 日志归档、数据库冷备、存一次读很多次 ——gzip。慢,但生态全认,三十年了还活着就说明够用。
- Kafka、RPC、实时传输、CPU 紧张 ——LZ4 或 Snappy。LZ4 更快,Snappy 更稳。差别几十 MB/s,在绝大多数场景下区别不大——选你生态里已经有的。
- Web 内容、CDN、浏览器兼容 ——Brotli。nginx 和 CDN 全支持,压缩率比 gzip 高 20%。比它好的都用不了——浏览器不认。
- 数据流经多阶段,不想反复解压重压 ——Zstd。一套格式覆盖全部,压缩级别调就行。如果你刚好是 Linux 6.x+ 或最新的 Kafka,Zstd 已经在生态里,不用额外装。
最后一条最容易被忽略:压缩只是手段,不是目的。 压得再小,CPU 烧光了服务慢了,压缩就没意义。压得再快,带宽还是爆了,快也没意义。选之前先搞清楚你的瓶颈在哪——不是比谁的压缩率最高,是比谁在你卡住的那个维度上最好用。
寒蝉 Hancic


