压缩算法选择指南:LZ4、Snappy、gzip、Brotli、Zstd

56 阅读 2604 字 · 约 9 分钟

一个服务每天往外吐几十 G 日志。你第一反应不是「换个压缩算法」,是「太大了,压一下」。

gzip -9 access.log,跑完一看,体积缩到十分之一。满意了。

后来日志量翻倍,gzip 跑一次要二十分钟。你开始想:能不能快点。

再后来上了 Kafka,消息要实时传。压缩不是在磁盘上慢慢压——每一条消息发出去之前都要压,收到之后都要解。慢了,消费者那边就堵;压得不够小,带宽就爆。两个都卡,但卡的维度不同。

这时候你才开始看压缩算法。不是看谁压得小,是看 哪边快、哪边小、你的瓶颈在哪边


两个极端

压缩这件事,底层的原理都一样:找重复模式。重复的字节用更短的编码代替。但这个「找」,花的力气可以差很大。

gzip 走的是「慢慢找、使劲压」。它花大量 CPU 扫描数据,把重复模式找得尽可能彻底——压得小,但慢。LZ4 走的是「快找、差不多压」——找重复模式很快,但不太找得全——快,但压得不够小。

这两头的差距,不是百分之几十,是数量级。


gzip -1gzip -9LZ4Snappy
压缩速度~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 烧光了服务慢了,压缩就没意义。压得再快,带宽还是爆了,快也没意义。选之前先搞清楚你的瓶颈在哪——不是比谁的压缩率最高,是比谁在你卡住的那个维度上最好用。