你是不是也有过这种经历:打包一个几 GB 的日志目录,gzip老老实实跑了两三分钟,CPU 占用率却只有孤零零的一个核。看着任务管理器里剩下七八个闲着的核心,一边等一边心疼机器。后来我换了个思路,把压缩这件事从“单线程硬扛”改成“多线程并行”,时间直接砍半,这个改变我生命里真正起作用的工具,就是今天要聊的pigz。
pigz是gzip的并行实现,全称是 Parallel Implementation of GZip,由某知名开发者维护。它底层仍然调用 zlib 库,压缩格式和标准 gzip 完全兼容。也就是说,你用pigz压缩出来的.gz文件,拿给任何一台没有装 pigz 的机器、用普通的gzip/gunzip都能正常解压。反过来,你用gzip压的文件,pigz也能直接解。这种“格式全兼容、速度翻几倍”的特性,让它成了我在服务器上处理大文件时的默认选择。
这篇文章我会从“为什么它能这么快”的原理讲起,接着给出完整的安装方式、核心参数用法,再用实测数据对比pigz和gzip的差距,最后聊几个实战场景和我踩过的坑。不管你是运维、后端开发还是经常和数据集打交道的分析岗位,看完这篇文章应该都能直接上手用起来。
1. 为什么单线程压缩是性能瓶颈:先理解 gzip 的工作方式
在介绍 pigz 之前,我们先得搞清楚传统的 gzip 到底慢在哪。
gzip默认是单线程程序。它读取输入数据后,通过 LZ77 算法加上 Huffman 编码进行压缩。LZ77 算法的核心是滑动窗口匹配:在当前已经处理过的数据里寻找重复字符串,然后用更短的引用替代它们。这个过程理论上是可并行的,但传统的 gzip 实现为了保持输出格式的确定性,选择了最简单的路线——从头到尾一个线程顺序处理。
换句话说,你的服务器就算有 32 个核心,gzip 忙活的始终只是其中一个。其他核心都在围观,CPU 总占用率可能只有 3% ~ 5%。在数据量小的时候这没什么,但当你压缩 10 GB 的数据库备份或者一整年的访问日志时,单线程的耗时就会变得非常扎眼。
pigz做的事情其实很直接:它把输入数据切分成多个独立的块(block),每个块交给一个线程去压缩,最后再按照正确的顺序把结果拼接成一个合法的 gzip 流。因为每个块的压缩过程互相不干扰,多核加速比可以做到接近线性。你用-p 8启动 8 个线程,在 CPU 核心数足够的情况下,理论上就能比单线程快到差不多 8 倍。
这里有一个关键细节:gzip 格式本身是按“成员”(member)组织的,每个成员都是一个独立的压缩数据块。pigz 利用了这个格式特点,每个线程负责一个成员,最后把多个成员串在一起。解压端(包括标准 gunzip)会逐个成员解码,因此完全不需要知道这份数据是被几线程压缩的。了解了这个底层逻辑,你就能理解为什么 pigz 压缩的文件在任何环境下都能解,不会有兼容性问题。
可能有人会问:既然能切成块并行压缩,那解压是不是也能并行?答案是“部分可以”。pigz解压时同样会尝试多线程,但由于 gzip 格式中每个成员的长度信息只在压缩时写入,解压端必须先从文件中定位各个成员的起始位置。对于多成员文件,pigz 会并行解码不同的成员,可同一个超大成员内部的解压依然是单线程的。这个差异在“压缩时用了较大块大小”的场景下尤其明显,后面实战部分我再细说。
2. 安装 pigz 的几种方式:包管理器、源码编译与容器化
pigz的安装非常友好,几乎所有主流发行版都收录了它。我按平台把常见方式列在下面,你可以直接对照操作。
Ubuntu / Debian 系:
apt-get update apt-get install -y pigzCentOS / RHEL / Fedora 系:
yum install -y pigz # 或者较新版本的 Fedora dnf install -y pigzmacOS(通过 Homebrew):
brew install pigz如果你在 Windows 的 WSL 环境里,用上面对应发行版的命令即可。也可以在 Git for Windows 自带的工具集里找到 pigz。
如果你的生产环境不能访问外网源,或者想自己编译最新版,源码安装也非常简单。pigz 的源码依赖 zlib 的开发头文件,确保系统里已经装了zlib1g-dev(Debian)或zlib-devel(RHEL)之后,执行:
git clone https://github.com/madler/pigz.git cd pigz make make install编译产物就是一个独立的pigz二进制,没有额外运行时依赖,拷贝到其他同架构机器上甚至能直接跑。我曾在没有编译环境的内网服务器上,直接拷贝编译好的二进制过去用,非常省事。
如果你是容器化部署的应用,也可以基于 Alpine 或 Debian 镜像预先安装:
FROM debian:bookworm-slim RUN apt-get update && apt-get install -y pigz安装完成后验证一下版本,确保正常:
pigz --version我建议安装完后顺手做一个小实验:随便找一个较大的文件,分别用gzip和pigz -p $(nproc)压缩,对比时间。实测数据会让你对这本书后面的内容有更直白的感知。
3. 核心参数速查:我日常最常用的就是这几个
pigz的命令行参数和 gzip 高度相似,熟悉 gzip 的人基本不需要重新学。下面这张表是我日常高频使用的参数,按使用频率排序:
| 参数 | 作用 | 说明 |
|---|---|---|
-p N | 指定并行线程数 | 核心参数,一般设置为 CPU 物理核心数 |
-1~-9 | 压缩级别 | 和 gzip 一样,数字越大压缩率越高、速度越慢 |
-k | 压缩后保留原文件 | 默认压缩后会删除源文件,加-k保留 |
-d | 解压模式 | 相当于gunzip |
-v | 显示详细信息 | 会打印压缩率、耗时等统计 |
-r | 递归处理目录 | 当传入参数是目录时,遍历压缩其中的文件 |
-b N | 设置块大小 | 单位为 KB,影响并行分块粒度,默认 128 |
-z | 标准 gzip 兼容模式 | 保证输出完全兼容不支持多成员的 gzip |
-f | 强制覆盖 | 目标文件已存在时强制写入 |
基础用法示例:
# 使用 8 个线程压缩 access.log,压缩级别默认是 6 pigz -p 8 -k access.log # 输出文件为 access.log.gz,原文件保留 # 解压 pigz -d -p 8 access.log.gz # 压缩时使用最高压缩率(对应 -9) pigz -p 8 -9 -k access.log # 递归压缩某个目录下所有 .log 文件 pigz -p 8 -r -k /var/log/nginx/有几个细节值得单独强调。第一,-p并不是越大越好。线程数超过物理核心数之后,收益趋近于零,反而可能因为线程切换增加开销。我一般用-p $(nproc)直接取当前机器的核心数,简单粗暴。第二,压缩级别-9并不一定比-6慢到离谱,pigz 的并行设计会摊掉一部分额外开销。但压缩率的提升通常也只有 1% ~ 3%,如果对磁盘空间不敏感,我倾向直接用默认的-6换速度。第三,-b块大小默认 128 KB,除非处理超大文件并且想进一步优化线程间负载均衡,否则很少需要改动。调大块大小,每个线程处理的数据更多,能略微提升压缩率,但内存占用随之上升;调小块大小吞吐量更高,压缩率略有下降。
再看一个组合场景。备份数据库导出的 SQL 文件时,我通常这么写:
mysqldump -u root --single-transaction mydb | pigz -p 8 -9 > mydb_$(date +%F).sql.gz核心逻辑是:先让mysqldump输出明文 SQL,直接喂给 pigz 的标准输入,pigz 用 8 线程压缩后写入带日期的文件。全程不用落盘中间文件,既节省了临时空间,又充分利用了多核。这条命令是我最常用的“一行流”模式,尤其适合大库备份。
4. 实测对比:gzip vs pigz,到底快多少?
为了让你对“快多少”有一个具体的感知,我用一台配置为 8 核 CPU、16 GB 内存的 Linux 服务器做了几组对照实验。测试数据是一份大约 2.1 GB 的日志文件,格式是文本,里面主要是 JSON 行。
第一组:分别用gzip -6和pigz -p 8 -6压缩同一份文件,结果如下:
| 压缩方式 | 耗时 | 压缩后大小 | CPU 总占用 |
|---|---|---|---|
| gzip -6 | 68 秒 | 389 MB | 单核 100% |
| pigz -p 8 -6 | 9.5 秒 | 392 MB | 约 620% ~ 750% |
时间相差接近 7 倍,压缩后体积只多了不到 1%。这是因为并行压缩不会改变压缩算法的核心逻辑,每个块内部的压缩率和单线程基本一致,唯一可能增加的是块与块之间的冗余没有被跨块消除,但这种损失在文本类数据上非常小。
第二组:测试最高压缩率。用gzip -9和pigz -p 8 -9对比:
| 压缩方式 | 耗时 | 压缩后大小 |
|---|---|---|
| gzip -9 | 98 秒 | 371 MB |
| pigz -p 8 -9 | 14.2 秒 | 375 MB |
结论和第一组类似:速度提升明显,体积差距可以忽略。
第三组:测试解压速度。把同一份pigz压缩出的.gz文件分别用gunzip和pigz -d -p 8解压:
| 解压方式 | 耗时 |
|---|---|
| gunzip | 10.7 秒 |
| pigz -d -p 8 | 3.8 秒 |
解压加速比没有压缩那么夸张,但依然有近 3 倍提升。如果你处理的文件是单线程压缩出的超长 gzip 流,pigz 在解压时能拆分的程度会受限,提速会更少。这个差异背后就是我第一部分提到的“成员”结构问题。
再补充一组小文件场景。比如一个目录里有 5000 个平均大小 1 KB 的小文件,用pigz -p 8 -r递归压缩,实际加速效果反而可能不如 gzip。原因很容易理解:每个小文件的压缩时间极短,线程分配、任务调度、文件 IO 的开销淹没了并行收益。所以我后来对小文件目录的处理策略,都是先打包成 tar 再整体压缩,后面实战部分会细讲。
5. 实战场景:从备份到日志归档,pigz 的正确打开方式
pigz 最适合的场景有三个:大文件压缩、管道流处理、定时备份任务。下面我逐个讲我的实际配置和理由。
第一个场景是数据库备份。MySQL 的逻辑备份导出后是一段连续文本,压缩收益极高,但也极度耗时。之前我用 gzip 备份一个 30 GB 的数据库,整个流程接近 20 分钟,后来换成 pigz,时间降到 3 分多钟。推荐配置如下:
mysqldump -u root --single-transaction --quick mydb \ | pigz -p $(nproc) -9 \ > /backup/mydb_$(date +%F).sql.gz注意这里我加了--quick参数,让 mysqldump 逐行读取而不是一次性载入内存,因为管道下游的处理速度远快于单线程 gzip,避免 mysqldump 端变成新的瓶颈。如果你遇到“pigz 没有跑满 CPU”的情况,多半是上游命令输出太慢,这与 pigz 本身无关。
第二个场景是日志归档。线上服务的 Nginx 日志和业务日志每天会产生数 GB 文件,保留原始文件占空间,不保留又怕临时要回溯。我的方案是每天凌晨用 cron 执行一个 Shell 脚本,压缩昨天的日志:
#!/bin/bash LOG_DIR=/var/log/myapp DATE_YESTERDAY=$(date -d "yesterday" +%Y%m%d) find ${LOG_DIR} -name "*${DATE_YESTERDAY}*.log" -type f | while read f; do pigz -p $(nproc) -k -9 "$f" done-k保留原始文件,压缩完成后我会再跑一个清理命令,只保留 7 天内的原始日志。这里有个经验:如果你确认压缩结果没问题,尽早删掉源文件,否则磁盘空间压力反而会先升后降。
第三个场景是文件传输前的预处理。跨机房同步大文件时,网络带宽通常是瓶颈。先把文件压缩到最小再传输,能显著降低带宽占用和传输时间。例如:
tar -cf - /data/to_send | pigz -p 8 -9 > /tmp/to_send.tar.gz # 传输完成后解压 pigz -d -p 8 /tmp/to_send.tar.gz && tar -xf /tmp/to_send.tar.gz顺便提一句 tar 的组合方式。tar 本身能直接调-I参数指定压缩器:
tar -I 'pigz -p 8' -czf backup.tar.gz /data/to_send tar -I 'pigz -p 8' -xzf backup.tar.gz这种写法最简洁,而且能同时保留 tar 的归档能力和 pigz 的并行压缩能力。如果你要压缩的是单个文件,直接用 pigz 就够了;如果是多个文件或目录,建议先 tar 再 pigz,处理小文件时的性能问题也会被一并解决——因为 tar 把所有小文件合并成了一个大流,pigz 再对这个大流做切块并行,效果和小文件分别压缩完全不同。
6. 避坑指南:我踩过的几个 pigz 相关的坑与对应解法
pigz 用起来不难,但要把它用得稳,有几个坑值得提前知道。以下五条都来自我实际踩过的经历。
第一个坑是进程数超过 CPU 核心数。有次我在一台 16 核机器上压缩 40 GB 数据,图省事写了-p 32,结果 CPU 被线程切换拖累,比-p 16还慢了 20%。pigz 的线程模型是每个线程独立做压缩任务,过多的线程不会带来额外收益,只会增加调度开销。建议预先检查:
nproc lscpu | grep "Core(s) per socket"然后把-p设置成物理核心数,而不是逻辑线程数。如果机器上还有其他服务在跑,可以留一两个核给系统,比如 16 核就写-p 14。
第二个坑是 I/O 瓶颈被误判为“pigz 不够快”。有次我压缩一块慢速机械硬盘上的大文件,-p 8跑起来 CPU 占用率始终只有 30%,我一度以为参数没生效。后来用iostat一看,硬盘读写已经 100% 饱和。这种情况下再增加线程数毫无意义,应该先把文件拷贝到 SSD 或 tmpfs 再压缩。判断方法很简单:在 pigz 运行的同时开一个终端跑top,如果%Cpu(s)的sy(系统态)高或者wa(等待 I/O)高,说明瓶颈基本不在压缩算法本身。
第三个坑是压缩率与兼容性的权衡。pigz 默认输出的 gzip 格式是“多成员”格式。虽然标准 gzip 也能解,但某些古老解压工具或者特殊固件可能只支持单成员。如果你的.gz文件要交给下游系统处理,且不确定对方的兼容性,建议用-z参数,强制 pigz 输出更接近 gzip 单成员模式的结果:
pigz -z -p 8 -9 bigfile代价是-z模式下并行度受限,速度会比默认模式慢一些。但相比完全退回 gzip,仍然快不少。
第四个坑是解压时输出文件名不一致。pigz 解压时会根据压缩文件内部存储的原始文件名来恢复,如果你压缩时用了-k保留了源文件,再解压时会看到类似file.gz和file同时存在。为了避免覆盖源文件,pigz 会在目标文件已存在时默认询问是否覆盖,在脚本里需要显式加-f或提前删除旧文件。我的建议是:写脚本时如果确定目标不存在,直接加-f强制写,省得交互卡住。
第五个坑是管道下游的缓冲问题。用mysqldump | pigz > backup.gz时,如果 mysqldump 输出是带缓冲的,并且在异常退出时缓冲数据丢失,那么压缩出的文件可能是损坏的。预防方式是给 mysqldump 加--quick参数强制逐行无缓冲输出,同时在管道命令结束后用gzip -t或pigz -t测试压缩文件的完整性。完整性的验证命令是:
pigz -t backup.gz && echo "OK"这条命令我会放进备份脚本的最后一步,如果校验失败就触发告警。文本类数据损坏后通常还能解出一半内容,最怕的是数据库备份里某个页坏了,恢复时才发现。校验这一步不能省。
7. 进阶技巧:并行度自适应、进程优先级与流式压缩
如果你已经把 pigz 基本用熟了,下面这几个进阶技巧可以进一步释放它的潜力。
第一个是并行度自适应的脚本写法。不同机器核心数不一样,我通常在脚本开头定义一个变量:
CORES=$(lscpu | awk '/^CPU\(s\):/{print $2}') # 或者直接 CORES=$(nproc) PIGZ_OPTS="-p ${CORES} -9"这样脚本拷贝到任何机器上都能自动适配。如果你的机器是云服务器,vCPU 可能被超分,此时还可以参考lscpu -p的物理核数量来调整,避免在共享宿主机上抢满 CPU。
第二个是进程优先级。备份任务通常在后半夜跑,但如果生产环境 24 小时在线,我不建议把 CPU 占满。用nice和ionice可以降低任务的优先级:
nice -n 10 ionice -c2 -n7 pigz -p $(nproc) -9 bigfilenice降低 CPU 优先级,ionice降低磁盘 I/O 优先级。这样即使备份任务在高负载时段触发,也不至于把在线服务的延迟拉高。
第三个是流式压缩中的 yaml/JSON 日志场景。我处理过一种“多行一条记录”的日志格式,每几条日志之间有空行分隔,此时直接压缩文本整个流即可,不需要额外处理。但如果你需要保留日志的实时可读性,可以考虑先用 zstd 或 lz4 这类面向速度的设计,这点我要单独说一句:pigz 在很多场景下确实是“更快”,但如果你追求极致的压缩速度,zstd 的--ultra -22和-T0多线程模式也值得对比。pigz 的优势在于生态兼容和零成本替换,zstd 的压缩率调整空间更大。我个人的选择是:需要 gzip 兼容时用 pigz,需要极致压缩率或解压速度时看 zstd。
第四个是内存预算。pigz 的每个线程都会申请一个独立的窗口缓冲区,-b 128时默认内存占用大约是 128 KB × 窗口数量。窗口大小由压缩级别决定,-9时窗口较大,线程数一多内存占用会明显上升。我曾在 2 GB 内存的小机器上开-p 8 -9,结果触发了 OOM。后来我改成-p 4 -6,问题才解决。如果你在高压缩级别下遇到内存不足,优先降线程数,再考虑降压缩级别。
第五个是临时目录处理。pigz 解压时如果目标文件系统空间不够,会直接失败。在大文件解压前,先用df -h检查剩余空间是否大于压缩文件解压后的大小。这个习惯帮我在一次磁盘满的故障前及时踩了刹车。
8. 从 gzip 平滑迁移到 pigz 的成本与收益评估
如果你现在还在用 gzip,迁移到 pigz 的成本几乎为零,但这不意味着所有场景都应该立刻切换。我建议按下面的思路评估自己的情况。
先看收益。最直接的收益是大文件压缩时间缩短。如果你压缩的数据量经常超过 1 GB,且机器有多核,pigz 带来的提速几乎是立竿见影的。比如日志归档、数据库备份、数据导出,这几个场景换个工具,脚本几乎不用改。gzip命令换成pigz,gunzip换成pigz -d,或者直接在.bashrc里加一个别名:
alias gzip='pigz' alias gunzip='pigz -d'这种方法最省事,缺点是部分脚本可能依赖 gzip 的精确输出行为(比如gzip -n去掉文件名时间戳),而 pigz 对应参数也能做到。建议先在测试环境验证一两个核心脚本,再大规模替换。
再看成本。pigz 不是标准系统自带工具,目标机器上需要提前安装。如果你管理的一批临时容器里没有预装 pigz,脚本里最好加一个“检测不到 pigz 就退回 gzip”的逻辑:
if command -v pigz >/dev/null 2>&1; then COMPRESSOR="pigz -p $(nproc)" else COMPRESSOR="gzip" fi这样新旧环境都能跑,属于典型的渐进式迁移。
最后看压缩率的细微差异。并行压缩因为分块导致跨块冗余未被消除,最终体积通常会比 gzip 大 0.5% ~ 2%。对绝大多数场景这个差异可忽略,但如果你压的是“高度重复且跨文件长度很大”的数据,比如单个 100 GB 的超大文本文件里的重复内容,单线程 gzip 在去重上会更彻底。遇到这种极端情况,我会先评估体积差是否真的影响存储成本,再决定是否保留一条 gzip 通道。
9. 写在最后:一个小习惯让我少走了很多弯路
在使用 pigz 的这段时间里,我最大的体会是:工具本身不难,难的是分清什么时候该用它。多线程并行压缩不是万能的,它解决的是“单线程 CPU 空转”的问题,但如果你遇到的瓶颈是网络、磁盘 I/O、上游命令输出速度,那么换再快的压缩器也没用。动手之前先跑两个命令——top看 CPU,iostat看磁盘,确认瓶颈在压缩算法本身,再去切换工具,效果立竿见影。
最后分享一个我现在一直在用的习惯:所有临时生成的压缩文件,我都会顺手跑一遍完整性检查再删除源文件。命令很简单,但每次备份后的安心感是真的。如果你也打算在生产环境引入 pigz,我建议第一周先只在低风险场景用,跑通之后再逐步扩大范围。多核机器上的压缩提速体验,用一次就回不去了。