压缩算法选型:lz4、zstd、snappy 与 gzip

系统讲解 Kafka 压缩算法选型:压缩发生在生产端还是 broker、lz4/snappy/gzip/zstd 四种算法的压缩比与吞吐实测、批量大小对压缩率的影响、broker 端重压缩配置、消息格式版本兼容性,以及按场景选择算法的决策方法

Kafka 的存储成本与网络带宽几乎完全由压缩决定:一条 1 KB 的 JSON 消息压缩后可能只有 200 字节,压缩比直接乘以你的磁盘、网络、以及副本同步流量。选错压缩算法,轻则浪费一半存储,重则把生产者 CPU 打满导致吞吐骤降。

但「哪个算法最好」没有统一答案——lz4 快、zstd 压缩比高、snappy 均衡、gzip 极端,选择取决于你的瓶颈是CPU、带宽还是存储。本文先讲清压缩发生在链路的哪一环,再用实测数据对比四种算法,最后给出按场景选型的决策表。

1. 压缩发生在链路的哪一环

1.1 生产端压缩(默认)

Producer 把一批消息(batch)压缩成一个压缩包 → 发送
Broker 原样存储压缩包(不解压)
Consumer 拉取压缩包 → 解压 → 逐条处理

关键:压缩在**批次(Batch)**粒度进行——一批消息一起压。批越大,压缩率越高(因为重复模式更多)。这是压缩率与批量大小强相关的根因。

1.2 broker 端压缩

Broker 只在特定情况下重压缩:

① 消息格式版本升级时(如 v0/v1 → v2)的转换
② compression.type 与生产者不一致时的重压缩
③ 否则:broker 原样转发压缩包,不耗 CPU
# broker 配置
compression.type=producer   # 默认:保持生产者压缩方式(推荐)
# compression.type=zstd     # 强制重压缩为 zstd(耗 broker CPU)

1.3 全链路压缩视角

生产端 CPU:压缩(可选算法)
Broker 磁盘:压缩后的存储(压缩比决定)
Broker 网络:压缩后的副本同步
消费端 CPU:解压(算法解压速度决定)

一句话:Kafka 压缩是端到端的——生产端压、broker 存、消费端解;broker 默认不重压,所以选算法的成本落在生产端 CPU 与消费端 CPU 上。

2. 四种算法对比

2.1 压缩比与速度

算法压缩比压缩速度解压速度CPU 成本适用
none1.0——无已压缩数据
lz4~2.0x极快极快极低吞吐优先(默认推荐)
snappy~1.8x极快快低低延迟
gzip~3.5x慢中高存储优先、离线
zstd~4.0x中快中压缩比优先(Kafka 2.1+)

数据为常见 JSON/文本负载的量级参考,实际随数据熵、批量大小浮动。

2.2 各算法定位

lz4:Kafka 0.8.2+ 引入,吞吐之王,生产端几乎无 CPU 压力
snappy:Google 出品,速度接近 lz4,压缩比略低,历史默认之一
gzip:压缩比高但 CPU 昂贵,适合「写入少、读取多、存储贵」的归档
zstd:Facebook 出品,压缩比接近 gzip、速度接近 lz4,新系统首选

2.3 消息格式版本要求

lz4        → 需要消息格式 v0.10+(Kafka 0.10+)
zstd       → 需要消息格式 v2(Kafka 2.1+)
gzip/snappy → 全版本支持

坑:zstd 需要 v2 消息格式——老客户端(0.10 以前)无法解压,混部集群升级前要确认客户端版本。

2.4 原生实现与依赖

Kafka 的压缩算法由客户端库实现,broker 只做透传:

lz4    → kafka-clients 内置(Java 原生实现)
snappy → 依赖 org.xerial.snappy:snappy-java
gzip   → JDK 内置 java.util.zip
zstd   → 依赖 com.github.luben:zstd-jni

坑:非 Java 客户端(Go、Python、Rust)需各自引入对应压缩库,版本不匹配会导致解压失败。lz4 与 gzip 兼容性最好,zstd 需确认客户端版本支持。

一句话:lz4 是吞吐安全的默认,zstd 是压缩比与速度的最佳平衡(新集群首选),gzip 只在存储成本主导时用,snappy 已被 lz4/zstd 边缘化。

3. 批量大小:压缩率的真正杠杆

3.1 批越大,压得越狠

单条压缩:几乎没有冗余可压,压缩比 ≈ 1.1x
100 条一批:重复的字段名、枚举值 → 压缩比 3~5x

结论:提升压缩率最有效的手段不是换算法,而是增大批量。

3.2 关键批量参数

# 生产端批量控制
batch.size=16384                 # 单分区单批最大字节(默认 16 KB,偏小)
linger.ms=5                      # 等待攒批时间,配合 batch.size
compression.type=lz4
max.request.size=1048576         # 单请求最大字节(默认 1 MB)

关系:linger.ms 决定「等多久攒批」,batch.size 决定「批上限」。二者共同决定实际批大小,从而决定压缩率。

3.3 权衡:延迟 vs 压缩率

linger.ms=0:立即发送,批小,压缩率低,延迟最低
linger.ms=20:攒 20ms,批大,压缩率高,延迟 +20ms

实践:吞吐优先场景 linger.ms=5~20;延迟敏感场景 linger.ms=0 并接受较低压缩率。更多批量调优见 生产者批量与调优 。

一句话:压缩率 = 算法 × 批大小;批大小由 batch.size 与 linger.ms 决定——先调批量,再选算法。

4. 实测对比:如何压测你的负载

4.1 压测方法

# 用官方工具压测不同压缩算法
kafka-producer-perf-test.sh \
  --topic perf-test \
  --num-records 1000000 \
  --record-size 1024 \
  --throughput -1 \
  --producer-props \
      bootstrap.servers=kafka:9092 \
      compression.type=zstd \
      batch.size=65536 \
      linger.ms=10

输出关注:records/sec(吞吐)、MB/sec(带宽)、avg latency(延迟)。

4.2 四种算法对照压测

for c in none lz4 snappy gzip zstd; do
  echo "=== $c ==="
  kafka-producer-perf-test.sh --topic perf-test \
    --num-records 1000000 --record-size 1024 --throughput -1 \
    --producer-props bootstrap.servers=kafka:9092 \
        compression.type=$c batch.size=65536 linger.ms=10
done

4.3 典型结果(示意)

算法吞吐 (records/s)带宽 (MB/s)磁盘占比
none850,000830100%
lz4820,00021025%
snappy800,00024029%
zstd700,00016019%
gzip350,00015018%

读法:lz4 用 3% 的吞吐损失换 75% 的存储节省;gzip 压缩比略优于 zstd 但吞吐腰斩。

4.4 何时不值得压缩

数据已是压缩格式(图片、视频、gz 文件)→ 再压收益极低,白耗 CPU
高熵数据(随机 ID、加密负载)→ 压缩比接近 1,无意义
极小消息(< 100 字节)→ 批太小,压缩头开销占比高

一句话:别信通用 benchmark,压测你自己的真实负载——压缩比高度依赖数据的重复模式;用 kafka-producer-perf-test.sh 换算法跑一遍,数据说话。

5. 存储与读取侧的压缩

5.1 日志段存储

压缩后的批被写入**日志段(Log Segment)**文件:

topic-0/00000000000000000000.log   ← 压缩批按序排列
topic-0/00000000000000000000.index ← offset 索引

注意:压缩是批内的,日志段里混存不同批次(各自独立压缩)。查看段大小时,压缩后的字节就是真实磁盘占用。日志段结构详见 日志段内部结构 。

5.2 消费端解压成本

消费端拉取压缩批 → 解压 → 逐条返回给应用
解压是 CPU 密集操作,高吞吐消费端要关注解压线程

关键:解压速度比压缩速度更影响消费端——zstd 解压快(接近 lz4),gzip 解压慢,这是 zstd 优于 gzip 的又一理由。

5.3 端到端压缩的 CPU 分布

生产端:压缩(选快算法省 CPU)
broker:默认不重压(省 CPU)
消费端:解压(选解压快的算法)

5.4 压缩与副本同步

副本同步(Replication)传输的是压缩后的批,因此:

压缩比越高 → 副本同步网络流量越小 → ISR 同步越快
高压缩比可缓解「副本跟不上导致 ISR 收缩」

但:broker 在校验消息时可能解压(如格式校验),高压缩比 + 大消息会推高 broker CPU——收益与成本要一起看。

一句话:压缩比的收益在磁盘与网络,成本在两端 CPU——zstd 之所以是新首选,正是因为它在「压缩比接近 gzip」的同时把「解压速度做到接近 lz4」。

6. 选型决策

6.1 按瓶颈选

你的瓶颈推荐理由
生产端 CPU 吃紧lz4 / snappy压缩几乎零成本
磁盘/带宽贵zstd压缩比高、解压快
消费端 CPU 吃紧lz4 / zstd解压快
极致压缩、离线归档gzip压缩比最高
已压缩/高熵数据none省 CPU 无收益

6.2 默认建议

新集群默认:zstd(Kafka 2.1+,压缩比与速度平衡最佳)
老集群/客户端杂:lz4(兼容性最好、吞吐安全)
存储成本主导的归档 topic:gzip

6.3 配置示例

# 生产端:zstd + 较大批量
compression.type=zstd
batch.size=65536
linger.ms=10
max.request.size=2097152
# broker:保持生产者压缩,不重压
compression.type=producer

6.4 迁移与变更注意

改变 compression.type 无需停机,新批次用新算法
历史批次仍用旧算法(Kafka 逐批记录压缩类型)→ 混合存储
消费端需支持所有出现过的算法 → 升级客户端前先确认

一句话:选型三步——先看瓶颈(CPU/带宽/存储)、再确认客户端版本(zstd 需 v2 格式)、最后压测真实负载;无脑选 zstd,兼容性优先选 lz4。

7. 常见坑

7.1 高频事故清单

坑现象对策
broker 强制重压缩broker CPU 飙升保持 compression.type=producer
老客户端遇 zstd解压失败确认 v2 格式 + 客户端版本
批太小压缩率只有 1.2x增大 batch.size / linger.ms
压缩已压数据CPU 白耗、无收益设 compression.type=none
只看压缩比不看解压消费端 CPU 打满选解压快的算法
max.request.size 太小大批被拒/拆分增大至 ≥ batch.size

7.2 监控指标

生产端:压缩后字节 / 压缩前字节(实际压缩比)、压缩 CPU 使用率
broker:磁盘写入速率、副本同步网络流量
消费端:解压 CPU 使用率、消费延迟

7.3 与批量、性能的关系

压缩不是孤立参数——它与 整体性能调优 联动;处理大消息时压缩策略又不同,见 大消息处理 。

8. 小结

算法一句话何时用
lz4吞吐之王默认、CPU 敏感、兼容优先
snappy均衡但被替代历史系统
gzip压缩比高但慢归档、存储主导
zstd压缩比与速度兼得新集群首选
none不压已压/高熵数据

一句话记住:Kafka 压缩的收益在磁盘与网络,成本在两端 CPU;压缩率的真正杠杆是批大小而非算法,所以「先调 batch.size 与 linger.ms,再选算法」。默认 zstd(新集群)或 lz4(兼容优先),broker 保持 compression.type=producer 不重压——用最小 CPU 换最大存储节省,就是压缩选型的全部目标。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「kafka」更多文章

  1. 集群升级与滚动重启实践
  2. Kafka 应用测试策略:Testcontainers 与集成测试
  3. Kafka 与 Flink 流批一体集成