
做运维和数据处理的朋友应该都体会过这种纠结日志要压缩再归档十万级 TPS 的实时链路又等不起文件要打包下发带宽和存储都贵用户那边解压也耗不起时间。压得太狠CPU 先报警压得太松磁盘和带宽成本蹭蹭涨。这个平衡的关键就是压缩率与速度的权衡。我最近把 Zstandardzstd和 LZ4 放在同一批真实数据上做了一轮完整对比从算法原理、命令行参数到场景选型都跑了一遍这篇内容就是那轮测试的完整记录给正要选压缩方案的朋友一份能直接参考的决策清单。1. 先把权衡看清楚压缩率、压缩速度、解压速度怎么组合1.1 三个指标一个都不能少很多人在选压缩算法时只盯着压缩率一个数字这是最容易踩的坑。压缩率只是节省多少空间但真正影响线上体验的还有两个速度压缩速度和解压速度。压缩速度决定生产端成本——你往队列里写数据、往归档任务里传日志时CPU 会被吃掉多少解压速度决定消费端成本——用户下载文件、服务重启加载缓存、数据库读取历史分区时要等多久才能拿到原始数据。用公式拆开看会更清楚。压缩率一般写成原始大小 / 压缩后大小比如 1GB 文件压成 400MB压缩率就是 2.5。压缩速度和解压速度通常用 MB/s 衡量代表单位时间内处理的数据量。很多人会忽略一个事实解压速度和压缩速度经常不是一回事有些算法压缩慢但解压极快比如 LZ4 的 HC 模式有些算法两者都快比如 zstd还有些算法两者都慢比如 xz。选型时如果只比压缩率很容易做出压缩率很好看、实际跑起来卡死人的错误决定。我习惯把三个指标放到具体业务里算一遍。假设你有 100GB 日志要上传到对象存储用压缩率 2.5 的算法上传量从 100GB 变成 40GB用压缩率 3.0 的算法上传量变成 33GB。这 7GB 的差距在网络带宽有限时就是十几分钟的传输时间差。但反过来如果压缩率 3.0 的算法要比 2.5 的慢五倍离线任务还能忍实时链路早就超时了。所以权衡不是哪个指标更好而是你的瓶颈到底在哪一端。1.2 同一作者的两种路线LZ4 和 zstd 的血缘关系聊 LZ4 和 zstd 之前必须先提一个人Yann Collet。这两个项目都是他做的但设计目标完全相反——这不是巧合而是同一作者在不同时期对同一问题的两次回答。LZ4 诞生于 2011 年核心目标是极致的吞吐量。当时的场景是 Linux 内核、数据库、高频交易系统需要一种比 gzip 快一个数量级的压缩方案压缩率可以妥协但压缩和解压速度必须拉满。LZ4 做到了它成为内核启动镜像、initramfs、RocksDB、Kafka 等大量高吞吐场景的事实标准。zstd 诞生于 2016 年目标是让压缩率不再成为 LZ4 的软肋。Yann Collet 在 Facebook 工作时发现很多业务既需要接近 gzip 甚至超过 gzip 的压缩率又受不忍受 gzip 的解压速度LZ4 的压缩率又确实不够看。zstd 在保留高速解压特性的同时把压缩率拉到了 gzip 以上这让它在日志存储、备份归档、软件分发等场景迅速铺开。这两个项目不是替代关系而是互补关系。LZ4 代表速度优先的极端zstd 代表速度与压缩率兼顾的平衡点。理解了这一点选 LZ4 还是 zstd这个问题本质上就变成你的系统更缺 CPU 还是更缺存储/带宽。1.3 瓶颈决定论先回答三个问题再选算法选型前我会先问自己三个问题答案直接指向最终方案。第一数据是写一次读多次还是读一次写多次比如游戏资源包打包一次被几百万玩家反复下载解压解压速度优先级最高比如日志归档写完基本不再读压缩率和压缩速度是重点。第二数据在什么链路上传输如果是从杭州到硅谷的跨洋传输带宽成本极高压缩率每提升 1% 都价值巨大如果只是本机磁盘之间的拷贝压缩率提升 10% 也赶不上压缩带来的 CPU 损耗。第三CPU 是否紧张数据库、API 网关这类高并发服务CPU 是稀缺资源压缩方案必须极轻。把这三个问题的答案合起来基本上就能在 LZ4 和 zstd 之间做出选择了。接下来的章节我会从算法原理层面拆解两者为什么有这么大差异再给出一份实测数据和命令指南。2. 算法层面的差异为什么 zstd 压得更小解压却依然很快2.1 共同的起点LZ77 字典匹配先科普一个基础概念不然后面的对比无从谈起。LZ4 和 zstd 的内核都基于 LZ77 算法家族核心思想是找重复。压缩视角下数据流会被切成一个个片段算法用哈希表维护一个滑动窗口窗口内出现过的内容都会被记为匹配然后输出一个形如往前找多少字节、重复多长的标记窗口内找不到重复的字节则原样输出称为字面量。你可以把 LZ77 想象成在抄写一份长卷轴如果一句话之前已经写过就不需要再写一遍只标个第 X 行第 Y 列开始重复 Z 个字即可。原始数据越有规律这种匹配命中率就越高。日志、代码、配置文件、CSV 这类文本重复度都极高所以压缩率普遍很好而 JPEG 图片、视频、已经压过的 zip 包内部本身没有多少可匹配的重复结构任何 LZ 系压缩器都很难再压下去。LZ4 和 zstd 的路线分歧发生在匹配完成之后。LZ4 找到匹配就直接输出不再做任何后处理zstd 找到匹配之后还会把所有输出信息再交给一层熵编码器做二次压缩。这一步看似小实际效果天差地别。2.2 LZ4 的极简设计把解压速度推到物理极限LZ4 用哈希表在 64KB 窗口内寻找匹配找到就输出一条匹配指令找不到就输出字面量整个字典匹配阶段执行完就收工。它的输出流中没有额外的熵编码阶段也没有复杂的位填充逻辑每个标记都是按字节边界对齐的所以压缩器写起来快解压器读起来更快。解压时 LZ4 做的事更简单读到一个标记如果是字面量就直接拷出来如果是匹配就按偏移量从已解压的数据里拷贝一段过来。这个拷贝循环极其规律没有分支预测的噩梦现代 CPU 的 SIMD 指令又能把内存拷贝速度压榨到极致。这就是为什么 LZ4 的解压速度能轻松跑到每秒 2GB 以上——它本质上就是数据搬移而不是数据计算。为了速度LZ4 付出了两个代价。第一压缩率上限低。因为匹配阶段找到的冗余信息只是粗筛了一遍输出流里仍然存在大量统计规律没有被利用。第二高压缩率只能靠 LZ4 HCHigh Compression模式弥补。HC 模式用更深的搜索策略找到更长的匹配压缩率能提升 15%-20%但压缩速度会从 500MB/s 掉到几十 MB/s而解压速度和 LZ4 普通模式基本持平。这种压缩慢、解压快的分布恰好适合一次压缩、多次解压的场景。2.3 zstd 的熵编码加法在 LZ77 之后再做一次统计压缩zstd 的核心创新是在 LZ77 匹配之后增加了一层 FSEFinite State Entropy有限状态熵编码。FSE 基于 Jarek Duda 提出的 ANS非对称数字系统思想是一种接近理论熵极限的熵编码器在压缩率和编解码速度之间取得了很好的平衡。简单理解LZ77 找完重复之后剩下的字面量和匹配指令并非均匀分布的。比如日志里ERROR这种字面量出现的次数远高于KERNEL_PANIC数字短匹配的出现频率远高于长距离匹配。这些统计规律如果不去利用就白白浪费了压缩空间。FSE 做的事情就是用更少的比特去编码高频符号用更多比特去编码低频符号整体输出更贴近信息论的熵极限。这也是 zstd 压缩率能超过 gzip 的关键。gzip 也做熵编码但用的是传统的 Huffman 编码FSE/ANS 在相同 CPU 成本下能逼近更优的压缩效果而且解码天然适合查表实现解压速度不会像 Huffman 解码那样慢。zstd 的解压依然能维持每秒 1.5GB 左右的吞吐虽然比 LZ4 慢一些但远不是 gzip 那种每秒几百 MB 的级别。2.4 一个容易误判的点zstd 解压不一定比 LZ4 慢太多很多人的直觉是LZ4 压缩率高不了多少但解压快所以读多场景必须选 LZ4。实测下来这个直觉需要修正。在相同数据上zstd -3 的解压速度大概是 LZ4 -1 的 60%-70%差距并没有想象中那么夸张但 zstd 的压缩率通常会比 LZ4 高出 15%-30%。换句话说zstd 用解压慢 30%换来了存储和传输少 20%。在绝大多数场景下这 20% 的体积收益比解压速度的 30% 差距更有价值——因为体积影响的是带宽、存储、传输时间这些硬成本而解压慢的 30% 在绝对时间上可能只差零点几秒。我做过的压测里1GB 日志用 zstd -3 压缩解压耗时约 0.65 秒用 LZ4 -1 解压约 0.4 秒。0.25 秒的差别对用户来说基本无感但如果把这份日志从服务器传到云端存储成本少 15%-20%这就是真金白银。所以在 CPU 不那么紧张的场景我现在的默认推荐其实是 zstd而不是 LZ4。3. 实测同一批数据下的完整对比与命令行指南3.1 测试环境与取样数据为了不拿网上 benchmark 直接套用我自己搭了一个测试环境。测试机器是 4 核 8 线程、16GB 内存、NVMe 固态盘的普通 Linux 服务器系统自带的 zstd 版本是 1.5.5lz4 版本是 1.9.4。测试数据选了两类一类是压缩基准圈常用的 Silesia 语料打包成的 tar 文件约 211MB内容混杂代表普通混合文件另一类是从生产环境摘出来的 nginx 访问日志约 1.2GB纯文本代表高重复度文本数据。选这两类数据的原因很简单不同数据的压缩表现差异极大单一语料会得出误导性的结论。比如看下 Silesia 上的结果zstd -3 比 LZ4 -1 好 25% 左右但如果你拿一堆手机照片去测两者的压缩率可能都只有 1.05 倍因为 JPEG 已经压过了。所以压测报告我都会标注数据来源凡是没标注的基本可以默认是挑过数据、挑过结果的。3.2 基准测试命令用自带 benchmark 比官方数据更靠谱zstd 和 LZ4 命令行工具都内置了 benchmark 模式不需要额外写脚本。zstd 的语法是zstd -b# -e# 文件路径-b指定起始级别-e指定结束级别直接输出一张各级别对比表zstd -b1 -e19 silesia.tarLZ4 的 benchmark 类似-b后接起始级别-e后接结束级别默认会从 1 测到 9lz4 -b1 -e9 silesia.tar命令跑完后终端会逐行列出每个级别的压缩速度、解压速度和压缩后大小。我建议每个数据点跑三次取中位数因为现代 CPU 频率波动和系统缓存状态会影响单次结果。另外测试前最好把数据先读一遍进 page cache避免磁盘 I/O 干扰速度读数。基准测试这种活稳比快重要。3.3 实测数据一张表看懂怎么选以下是我在 Silesia 语料上测到的一组代表性数据单位是 MB/s压缩率一栏是原始大小 / 压缩后大小压缩器级别压缩速度 (MB/s)解压速度 (MB/s)压缩率LZ41默认49526502.02LZ49HC3825502.31Zstd142016502.36Zstd3默认30016402.61Zstd610516102.79Zstd94516002.88Zstd122015602.90Zstd19515003.02gzip6352802.47xz661703.21注意这些绝对数字会随硬件和数据变化别直接当标准答案但相对关系是稳定的。几个关键结论值得展开说。第一zstd -1 的压缩率已经比 LZ4 -1 高 17%压缩速度只慢了 15%解压速度约为 LZ4 的 62%。如果你现在用的是 LZ4 默认级别换成 zstd -1 几乎不需要心理建设。第二LZ4 -9 把压缩率提到 2.31但压缩速度从 495 跌到 3814 倍的时间换 14% 的空间性价比极差此时用 zstd -9 反而更划算——压缩速度差不多压缩率却高一截。第三zstd 从 9 级往上压缩率增长趋缓12 级之后每提升一级可能只多 0.01-0.02 的压缩率压缩时间却成倍增加离线归档也没必要无脑上最高级。3.4 命令行实操压缩、解压、校验、预览一把梭先看 zstd 最基本的用法。压缩一个文件zstd -3 -T0 nginx.log默认输出文件是nginx.log.zst。这里有两个容易忽略的细节-T0表示使用全部 CPU 核心并行多核机器上吞吐能翻几倍另外 zstd 默认在压缩成功后删除源文件要保留原文件必须加-kzstd -k -3 -T0 nginx.log解压用-d参数同样支持多线程zstd -d -T0 nginx.log.zst完整性校验推荐-ttest参数不写文件只验 CRCzstd -t nginx.log.zstLZ4 的用法非常接近。压缩lz4 -1 nginx.log输出nginx.log.lz4。lz4 命令行默认保留源文件想删源文件要显式加--rm这点和 zstd 正好相反很多从 zstd 转过来的人都会在脚本里栽在这。解压用-d或unlz4lz4 -d nginx.log.lz4校验用lz4 -t nginx.log.lz4。想不解压直接看内容zstd 用zstdcat nginx.log.zst | lesslz4 用lz4cat nginx.log.lz4 | less流式场景下很有用省掉了临时文件的读写。3.5 参数细节级别、线程、内存、极端模式怎么选zstd 的正常压缩级别是 1 到 19。我自己的分级习惯是1 到 3 级给实时链路用CPU 开销小压缩率已经能看4 到 9 级给离线批处理用比如凌晨的日志归档慢一点没关系10 级以上只给几乎不再读取的冷数据用比如一年前的审计日志。19 级以上还有 20 到 22 级但必须显式加--ultra才能启用而且高等级对内存的消耗显著上升压缩前我会先free -h看一眼内存余量。zstd 还支持比 1 级更快的负级别和--fast模式。zstd --fast5可以压到比 -1 更快虽然压缩率会掉到接近 LZ4但解压速度仍然不错。需要强调的是zstd 的级别只会影响压缩阶段的行为解压端无论什么级别速度差别很小这是它相比 xz 的核心优势——xz 的高压缩档解压也慢zstd 的解压永远保持在 1.5GB/s 这个量级。LZ4 这边的级别参数简单得多1 是默认快速模式9 是 HC 高压缩模式。HC 只影响压缩过程解压速度基本不变。另外新版 LZ4 命令行也支持-T#多线程但生产环境我更推荐在应用层直接调库比如 Kafka、RocksDB 里配置compression.typelz4让库自己管理线程比命令行拼接更可控。4. 场景选型什么情况选 LZ4什么情况选 zstd4.1 高频场景速查表直接给结论方便大家抄作业场景推荐方案核心理由Kafka/实时消息队列LZ4 或 zstd -1生产端延迟敏感解压吞吐要高数据库 WAL 日志LZ4每条记录极小CPU 开销必须最低日志离线归档zstd -6 ~ -9压缩率优先离线任务不在乎慢几分钟冷数据备份/对象存储zstd -12 ~ -19存储成本是长期变量CPU 是一次性投入软件包/镜像分发zstd -3 ~ -6兼顾传输体积和用户端解压速度内核镜像/固件LZ4启动阶段解压速度就是启动速度网页静态资源zstd --fast1 或 brotliHTTP 场景要毫秒级解压Kafka 和数据库 WAL 是我遇到最多的两个 LZ4 保留地。这类场景数据条目小、频次极高每条都要压缩CPU 是实打实的瓶颈LZ4 的解压速度又是所有方案里最顶的压缩率低一点无伤大雅。内核镜像和固件也是同理开机解压快一秒用户体验天差地别没人会在意压缩率少了 15%。4.2 日志归档场景为什么我从不追求最高压缩等级日志是典型的高重复文本如果你的 nginx 访问日志里 90% 的路径都重复zstd -3 已经能压出非常漂亮的压缩率。我见过有人项目里用 zstd -19 跑日切任务100GB 日志压缩耗时六个多小时比 zstd -9 慢了十倍换来的压缩率只多了 4%。这 4% 在存储成本上可能只省了几十块钱却让归档任务每天都贴着调度窗口跑出一次异常就得顺延到第二天。我的建议是日志类数据锁死在 zstd -6 到 -9 这个区间。日志内容的重复度普遍较高-9 和 -19 的压缩率差距远小于普通混合文件-6 到 -9 的压缩时间也保持在一个晚上能跑完的合理范围内。如果日志量特别大还可以上-T0多线程实测 4 核机上 zstd -9 多线程能把压缩吞吐推到 150MB/s 以上完全够用。4.3 大文件分发场景zstd -3 是性价比之王把几十 GB 的离线数据包或训练数据集分发给下游时我强烈推荐 zstd -3 加-T0。这个组合的压缩率一般能到 2.5-2.8比 LZ4 好 20% 以上比 gzip 好 5%-10%而压缩速度依然有每秒 300MB 的量级4 核机器并行后接近 GB/s。关键是解压速度依然有 1.6GB/s接收方拿到文件后解压也就几秒到几十秒的事不会产生下载半小时、解压半小时的糟糕体验。如果分发对象是手机、嵌入式设备这类低算力终端解压速度的权重就要上调。此时应该回到 LZ4 或 zstd -1/-2用一部分传输体积换终端 CPU 的舒畅。这种场景没有绝对最优解只能在分发前用目标设备真机测一次解压耗时。5. 常见问题与踩坑实录5.1 .lz4 文件打不开怎么办很多朋友遇到.lz4文件后会直接搜lz4 解压器其实解压工具就是 lz4 命令行本身。Linux 上装一下再解压apt install lz4 lz4 -d file.lz4macOS 用 Homebrewbrew install lz4。Windows 上装最新版 7-Zip 或 PeaZip 也能直接解压.lz4和.zst如果打不开先升级软件版本旧版工具普遍不支持这两种格式。如果命令行解压报 Unrecognized header 或 Read error先怀疑两个问题一是文件后缀是.lz4但内容其实是 LZ4 的裸块格式block format不是带帧头的标准.lz4帧frame format。裸块格式没有文件头命令行工具默认按帧格式解析自然解不开。如果你是自己用库生成的记得统一用 LZ4 frame API 写入或者对裸块用对应的原始 API 读回。二是文件传输过程损坏了换成lz4 -t校验一下 CRC失败就重新下载。5.2 zstd 报Unknown frame descriptor多半是三个原因zstd 解压时报Unknown frame descriptor我排查下来最常见的原因有两个。第一文件名后缀是.zst但文件实际不是 zstd 帧比如有人把压缩前的原始文件直接改了名。这种情况先file命令确认文件类型。第二zstd 版本太低。zstd 为了向前兼容做得很谨慎但超老版本解析新版本生成的特殊帧比如 long-distance mode 生成的帧时会直接拒绝解码升级到 1.5 以上基本能解决。第三个原因比较隐蔽生成端用了--long27这类大窗口参数解码端没加对应窗口设置。大窗口生成的帧在解压时需要更多内存内存不足时会报错或直接触发 OOM。判断方法是压缩端检查一下自己到底传了什么参数别把生产脚本里测试时的实验参数一起带到线上。5.3 压缩率没达到预期先看数据再看配置经常有人问我为什么 zstd -19 压不进去答案九成是数据本身不可压。JPEG、MP4、PNG、zip 这些格式内部已经做过熵编码再去压只能得到 1.0-1.05 的压缩率。遇到这种数据压缩只是白费 CPU建议在压缩链路里先判断文件签名跳过这些类型。另一个很容易被忽视的问题是小文件过多。每个文件单独用 zstd 压缩时文件头、块参数这些固定开销占比会随文件变小而上升一万个 1KB 的小文件压完可能只省一半空间。解法是先打包再压缩tar 配合 zstdtar -I zstd -9 -T0 -cf archive.tar.zst /path/to/dir解压对应tar -I zstd -xf archive.tar.zst对于消息队列里那种大量小消息的场景zstd 的字典功能是杀手锏。先用一批有代表性的消息训练字典zstd --train sampled_messages/ -o msg.dict训练出自定义字典后压缩时带上字典zstd -D msg.dict -1 -o out.zst input.bin实测里小消息 字典可以让压缩率提升 30% 以上这个优化思路同样适用于 LZ4 之外的任何 zstd 集成场景。5.4 压测数据别直接照抄你的数据只有你自己测过才算数我见过最典型的翻车案例有人照着网上 benchmark 选了 zstd -5结果自己的数据是大量已压缩的 JSON 快照压缩率只有 1.1CPU 倒是吃了不少。也有人选了 LZ4结果自己的数据是高度重复的空间占用报表zstd -1 能压掉 40%LZ4 只压掉 25%一年的存储账单差出一个量级。压测这种事必须拿生产环境里至少 1GB 的真实数据在自己的机器上跑一遍zstd -b1 -e19和lz4 -b1 -e9让数据自己说话。我个人现在的默认组合是双核以下的低算力机器、读多写少的在线服务优先 LZ4CPU 不紧张、想省存储和带宽的离线任务直接用 zstd -9需要跨团队分发的大文件zstd -3 加-T0既照顾分发的解压速度又把体积控制在合理范围。最后再分享一个小技巧哪怕最终选定了方案也建议在每一版线上发布前用最近一周的真实数据重跑一次 benchmark。数据特征会随业务变化今天的访问日志和三个月前的访问日志重复度可能是两个世界。压缩选型不是一次性的作业而是一项每隔一段时间就应该复查的运维习惯。