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 成本 | 适用 |
|---|---|---|---|---|---|
| none | 1.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) | 磁盘占比 |
|---|---|---|---|
| none | 850,000 | 830 | 100% |
| lz4 | 820,000 | 210 | 25% |
| snappy | 800,000 | 240 | 29% |
| zstd | 700,000 | 160 | 19% |
| gzip | 350,000 | 150 | 18% |
读法: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 换最大存储节省,就是压缩选型的全部目标。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。