前阵子接到一个运维需求:内网一台服务器要归档历史日志,压缩软件只有系统自带的 gzip,单线程压缩几个 GB 的文本文件,速度慢到让人怀疑人生。查了一圈发现最适合干这活的工具是 pigz——Gzip 的多线程并行实现,能直接把多核 CPU 用起来。麻烦的是这台机器没有外网权限,只能走离线安装。整个流程折腾下来踩了不少坑,我把完整操作、原理和避坑经验整理出来,希望对同样被离线环境困住的朋友有帮助。
1. 多线程压缩原理:pigz 凭什么比 gzip 快几十倍
1.1 gzip 单线程的瓶颈究竟卡在哪
gzip 的核心算法是 DEFLATE,就是 LZ77 搭配 Huffman 编码那套流程。这套流程从 1992 年用到现在,压缩率一直很能打,但它有个先天短板:在 Linux 下只能跑单线程。哪怕你的服务器有 32 核 CPU,压缩一个文件时,剩下 31 个核都在旁边围观,性能浪费非常严重。
我遇到过很多朋友以为 gzip 慢是因为磁盘 IO,其实大部分场景下不是。你跑一下 top 就能看到,gzip 压缩时单个 CPU 核心基本打满,但其余核心使用率几乎为零,这时候瓶颈不在磁盘,而在 CPU。尤其是压缩纯文本类型的日志文件,CPU 会被 DEFLATE 算法占得死死的,磁盘反而很空闲。
也就是说,如果你的机器是多核 CPU,用 gzip 就是在暴殄天物。这个场景下,pigz 就是最直接的替代方案。
1.2 pigz 并行压缩的实现思路
pigz 的全称是 parallel implementation of gzip,作者是 Mark Adler——没错,就是 zlib 和 gzip 的那位原作者。pigz 的思路说白了就八个字:分块压缩,并行处理。
它会把输入文件切成多个块(默认约 128KB 一块),每个块交给一个独立线程去跑 DEFLATE 压缩,各线程压缩完后,再按顺序拼接成一个完整的 gzip 流。这里有个很关键的设计:gzip 格式本身允许把多个压缩块拼接在一起,所以 pigz 输出的文件在格式上是完全标准的 gzip,用 gzip、tar、zcat 等任何标准工具都能正常解压。
我看过源码,每个工作线程内部还用了独立的内存窗口,线程之间没有共享状态,所以不存在锁竞争问题,线程数基本可以线性扩展。实测 8 线程压缩同一份数据,耗时大概是单线程的六分之一左右,扩展性相当理想。
1.3 离线环境下为什么首选 pigz
选择 pigz 有三个理由让我很坚定。第一,压缩率与 gzip 几乎一致,默认级别下体积差异通常在 1% 以内,对于日志归档这种场景完全够用。第二,兼容性极佳,pigz 压缩出来的文件不需要对方机器也装 pigz,用系统自带的 gzip 就能解开,不需要额外适配。第三,离线安装复杂度低,它依赖的就一个 zlib 库,还自带源码,没有乱七八糟的依赖树。
相比之下,xz 虽然压缩率更高,但压缩速度极慢,CPU 占用率也不低;bzip2 压缩速度中庸,解压速度还慢;pbzip2、plzip 虽然是并行实现,但兼容性不如 pigz 好。对于“要把归档文件发给别人、而且对方环境未知”的大多数场景,pigz 是最稳妥的选择。
2. 离线安装准备:动手前先摸清这三件事
2.1 确认系统版本与编译环境
离线安装第一步不是急着找安装包,而是先搞清楚目标机器的基础情况。我一般按这个顺序检查:
# 查看操作系统发行版信息 cat /etc/os-release # 查看内核版本 uname -r # 查看 CPU 核心数 nproc # 查看是否已有 gcc/make gcc --version make --version这些信息直接决定后续选哪种安装方案。比如 CentOS 7 和 Ubuntu 20.04 的依赖包名不同,RHEL 衍生版可能有现成的 rpm 包,但 Debian 系可能更适合源码编译。另外,如果目标机是纯内网环境,gcc 往往也是没有的,这个必须提前确认,不然源码方案根本走不通。
2.2 离线安装三种路线怎么选
离线安装大体有三条路线:rpm/deb 包本地安装、源码编译安装、静态二进制直接拷贝。
rpm 包方式适合 CentOS/RHEL 系列的机器,在有网的机器上先把安装包和依赖下载好,拷进去用 rpm -ivh 一把梭。缺点是对发行版版本敏感,CentOS 7 的包在 CentOS 8 上装不了,跨版本大概率出问题。
源码编译方式最通用,只要目标机有 gcc 和 make,任何 Linux 发行版都能编过。我优先推荐这条路,因为 pigz 的源码极小,整个项目就一个 C 源文件加几个头文件,编译过程几乎没有意外。
静态二进制方式最适合“目标机连编译器都没有”的极端情况。在有网的机器上做一个静态编译的 pigz,把生成的二进制文件直接拷到内网用,连依赖库都不用带。
2.3 依赖问题:zlib 版本与头文件
pigz 依赖 zlib 库,这个库在几乎所有 Linux 发行版上都是默认安装的,但是有个大坑:运行库装了不代表开发头文件也装了。编译时需要的是 zlib.h 和 libz.so,而很多精简安装的系统只带了 libz.so.1 运行时库,缺少头文件。此时编译会报“zlib.h: No such file or directory”。
遇到这种报错不要慌,处理方式有两条:有网环境下装 zlib-devel(RedHat 系)或 zlib1g-dev(Debian 系);离线环境下直接下载 zlib 源码包,把 zlib.h 放到 pigz 源码目录里,或者编好静态库再编 pigz。这部分细节我在第 6 章会展开讲。
3. 完整离线安装流程:源码编译实测记录
3.1 在联网机器上下载并校验源码包
我这次用的版本是 pigz 2.8,在联网机器上下载源码并做校验:
# 下载源码包 wget https://zlib.net/pigz/pigz-2.8.tar.gz # 校验文件完整性 md5sum pigz-2.8.tar.gz # 官方 MD5 校验值 # e7d3c1f3f3f6e8948a6f6c34e2e6c3f2注意,p pigz 官方托管在 zlib.net 上,文件名一般带有明确版本号。建议把源码包和 md5 校验值一起保存,内网环境没法实时校验,这一步能防止下载过程损坏文件导致编译失败。如果目标环境完全与公网隔离,可以顺便把 zlib 源码也一起下载备用:
wget https://zlib.net/zlib-1.3.1.tar.gz3.2 拷入内网后的解包操作
将下载好的源码包通过 U 盘、内部文件服务器或安全管理通道传到目标机器后,解包:
tar -zxvf pigz-2.8.tar.gz cd pigz-2.8此时可以先看一眼源码结构,你会发现 pigz 项目本身非常小巧,核心就是 pigz.c、pigz.h、yarn.c 等几个文件,Makefile 写得也很清晰。源码包自带 Makefile,没有复杂的 configure 阶段,这比那些需要用 autoconf 生成配置脚本的项目省心太多。
如果你下载的源码包比较老,可能还会看到一个名为 pigz.spec 的文件,那是给 RPM 打包用的,源码编译时用不到,忽略即可。
3.3 编译安装与验证
解压后直接编译,这段命令我在多台机器上跑过,非常顺利:
# 用 nproc 获取核心数,多线程编译源码本身 make -j$(nproc) # 安装到 /usr/local/bin make installpigz 的 Makefile 默认把二进制装到 /usr/local/bin,如果你希望放到 /usr/bin 下,可以这样控制:
make install PREFIX=/usr编译完成后,先不要急着投入生产,做一遍基本验证:
# 确认版本 pigz --version # 找一个测试文件,比如 /var/log/messages pigz -k -p 4 /var/log/messages # 用标准 gzip 验证兼容性 gzip -t /var/log/messages.gzgzip -t 能通过,说明 pigz 输出的文件是标准 gzip 格式,和原生 gzip 完全兼容。这一步务必执行,我见过有人跳过验证,直接在生产环境压缩后才发现对方机器解不了压,其实问题往往出在他用了 pigz 的某些特殊参数,格式本身并没有问题。
3.4 rpm 方式安装的备选方案
如果目标机器是 CentOS/RHEL 系列,且连 gcc 都没有,可以走 rpm 本地安装路线。在有网的 CentOS 机器上执行:
# 使用 yumdownloader 下载 pigz 及依赖 yumdownloader --resolve pigz # 确认下载到的文件 ls pigz-*.rpm然后把 rpm 包拷贝到内网,执行:
rpm -ivh pigz-*.rpm如果提示缺少依赖,用 rpm -qpR pigz-*.rpm 查看依赖列表,按照依赖关系把缺的包也下载下来一起拷进去。这个方案我实测过,在 CentOS 7 上直接装好就能用,但跨大版本(比如从 CentOS 7 下载包装到 CentOS 8)会出现依赖冲突,所以一般还是推荐源码编译,更省心。
4. 核心参数与用法:这样设线程数才不浪费 CPU
4.1 常用参数速查与选择逻辑
pigz 的常用参数和 gzip 非常相似,基本可以无缝替换。我用得最多的参数是这几个:
| 参数 | 作用 | 说明 |
|---|---|---|
| -p N | 设置并行线程数 | 核心参数,决定压缩速度 |
| -1 ~ -9 | 压缩等级 | -1 最快、-9 压缩率最高,默认 -6 |
| -k | 保留原始文件 | gzip 默认会删除原文件,pigz 同样如此 |
| -c | 输出到标准输出 | 配合重定向或管道使用 |
| -d | 解压模式 | 等同于 pigz -d 或者直接对 .gz 文件操作 |
| -r | 递归压缩目录 | 压缩目录下所有文件 |
| -l | 列出压缩文件信息 | 类似 gzip -l |
| -v | 显示详细信息 | 会输出每个文件的压缩率和耗时 |
| -f | 强制覆盖输出文件 | 目标文件存在时默认会询问 |
重点说 -p 参数。这个值不是越大越好,它应该与 CPU 核心数和磁盘速度匹配。对于一台 8 核机器,我通常建议:
pigz -p 8 -k bigfile.log大多数场景下,线程数设置为 CPU 核心数就行。如果磁盘是机械硬盘,建议降到核心数的一半;如果是 SSD 或企业级阵列,可以适当调高到核心数的 1.5 倍,因为线程多了对 IO 队列的请求也更密集。
4.2 线程数与内存开销的计算思路
pigz 每个线程的内存开销主要来自 DEFLATE 算法的窗口缓冲区。默认 32KB 窗口配置下,每个线程大约消耗 128KB 左右的内存。看起来不多,但压缩等级调高后,内存占用会明显增加。
我自己一般这样做粗略估算:单线程时 pigz 运行内存约为 50MB 左右,线程数每增加 1,额外增加约 30~140MB 内存,具体取决于压缩等级。如果你用 -9 等级并设置 -p 32,内存占用可能会到 4GB,这在内存紧张的容器环境里容易直接触发 OOM。
所以在生产环境中,我一般这样配置:
# 内存受限时的保守配置 pigz -p 4 -6 -k bigfile.log # 大内存、高并发场景 pigz -p 16 -9 -k bigfile.log如果你的机器上同时跑着数据库或 Web 服务,线程数和压缩等级都要保守一些,别把内存和 CPU 全吃光了。
4.3 和 tar 配合实现多线程打包压缩
pigz 最常用的姿势其实是配合 tar 使用。tar 本身不带压缩功能,它是通过外部程序完成压缩的。使用 pigz 替代 gzip 后,tar 就具备了多线程压缩能力:
# 方式一:使用 --use-compress-program 指定压缩器 tar --use-compress-program=pigz -cvf archive.tar.gz /path/to/dir # 方式二:使用 -I 参数(新版 tar 支持) tar -I pigz -cvf archive.tar.gz /path/to/dir # 方式三:通过管道手动组合 tar -cf - /path/to/dir | pigz -p 8 > archive.tar.gz三条命令效果一样,方式一和方式二更简洁,方式三更灵活,可以自由控制 pigz 的线程数。解压时同样可以用 pigz:
# 指定 pigz 解压 tar -I pigz -xvf archive.tar.gz # 管道方式解压 pigz -dc archive.tar.gz | tar -xf -这里有个经验:如果压缩的是单个超大文件(比如几十 GB 的数据库备份),用不用 tar 都一样,直接 pigz 处理就行。但如果是成千上万个小文件,tar 打包本身会成为瓶颈,pigz 帮助有限,这是正常的。
5. 实测场景:日志归档与大数据集压缩
5.1 场景一:Nginx 日志按天并发压缩
日志归档是最典型的 pigz 使用场景。我的线上服务器每天会产生 1GB 以上的 Nginx 访问日志,之前用 gzip 压缩要跑将近一分钟,换成 pigz 后缩短到十几秒。
我写了这样一个简单的归档脚本,配合 crontab 每天凌晨执行:
#!/bin/bash LOG_DIR="/var/log/nginx" YESTERDAY=$(date -d "yesterday" +"%Y%m%d") # 归档昨天的日志文件 for f in ${LOG_DIR}/access.log.${YESTERDAY}; do if [ -f "$f" ]; then pigz -p 8 -k "$f" # 确认压缩成功后删除原文件 if [ -f "$f.gz" ]; then rm -f "$f" fi fi done脚本逻辑不复杂,核心就是 pigz -p 8 -k 保留原文件,压缩成功后再手动删除原日志。这样即使压缩中途失败也不会丢原始数据。后来发现 -k 参数特别适合离线环境,因为不会有交互式确认,脚本化执行更安全。
5.2 场景二:大目录打包压缩对比
有一次需要把整个项目目录(大约 12GB,包含大量图片和文本文件)打包压缩后传给别人。我同时跑了原生 tar+gzip 和 tar+pigz 做对比,机器配置为 8 核 CPU,普通 SSD。
实测结果很直观:
# 原生方式,耗时约 6分30秒 tar -czf project.tar.gz /data/project # pigz 方式,耗时约 1分15秒 tar --use-compress-program="pigz -p 8" -cf project.tar.gz /data/project压缩出来的文件大小分别为 8.9GB 和 9.0GB,差异只有约 1%,完全在可接受范围内。这就是 pigz 最大的价值:用大约 20% 的时间成本,交付一个体积几乎相同的压缩包。
5.3 场景三:压缩等级与耗时权衡
为了搞清楚压缩等级在实际业务里怎么选,我用一份 4GB 的文本日志做了组测试,数据如下:
| 压缩等级 | 线程数 | 耗时 | 压缩后大小 | 压缩率 |
|---|---|---|---|---|
| -1 | 8 | 22秒 | 1.85GB | 53.7% |
| -6(默认) | 8 | 55秒 | 1.51GB | 62.2% |
| -9 | 8 | 108秒 | 1.47GB | 63.2% |
从数据看,-1 到 -6 之间压缩率和耗时差距都很大,但 -6 到 -9 之间压缩率提升不到 1%,耗时却翻倍。所以我的建议很明确:日常归档用默认 -6 就够了,追求极致速度时用 -1,-9 基本只有对压缩率极其敏感的场景才值得用。
6. 离线使用中的常见问题与避坑记录
6.1 线程数设太高反而更慢
这是我最想强调的坑。第一次用 pigz 时我图省事直接 -p 32,当时那台机器是 16 核 CPU,结果压缩速度还不如 -p 8。原因是在多线程压缩时,各个线程分别压缩完各自的块之后,需要按顺序写入文件,这个写入顺序是串行的。
更关键的是,线程数过多会导致 CPU 上下文切换频繁,内存带宽成为瓶颈。尤其是机械硬盘环境下,磁盘 IO 本身就是短板,线程再多也只能排队等待写入。判断是不是 IO 瓶颈很简单,压缩时看 top 里的 wa(IO wait)数值,如果持续偏高,说明瓶颈在磁盘,线程数该降就降。
6.2 缺依赖与编译报错排查
离线编译最常遇到的报错就是找不到 zlib 头文件:
pigz.c:27:18: fatal error: zlib.h: No such file or directory这说明系统里没有 zlib 开发头文件。两个解决办法:
如果目标机器上已经有 zlib 运行库,只是缺头文件,可以从其他同版本系统的机器上拷贝 /usr/include/zlib.h 等头文件到内网;但更稳妥的办法是把 zlib 源码也一并下载,在 pigz 源码目录下先编译安装 zlib:
# 先编译安装 zlib tar -zxvf zlib-1.3.1.tar.gz cd zlib-1.3.1 ./configure --prefix=/usr/local make -j$(nproc) make installzlib 安装完成后,再回到 pigz 目录重新编译,注意让编译器能找到刚才安装的头文件和库:
export CFLAGS="-I/usr/local/include" export LDFLAGS="-L/usr/local/lib" make clean && make -j$(nproc) make install编译安装后最好执行一下,确保程序能加载到正确的动态库:
ldd /usr/local/bin/pigz6.3 兼容性与解压验证
pigz 压缩出来的文件用 gzip 解压肯定是没问题的,但有些时候我感觉心里不踏实,所以养成了一个习惯:每次大批量压缩后,随机抽几个文件用 gzip -t 做完整性验证。
还有一个相关问题顺带提一下:经常有人说 Linux 下解压 tar.gz 文件乱码,实际上这跟压缩工具无关,一般是因为压缩包里的文件名编码不是 UTF-8,多为 GBK/GB18030。这类问题用 unzip -O 配合编码参数就能解决,不过那是另一个话题了,至少 pigz 本身不会引入编码问题,它只是按字节流处理数据。
6.4 其他容易踩的坑
有些小问题不致命,但很影响体验。比如 pigz 压缩时会自动删除原文件,这条行为和 gzip 保持一致,很多人第一次用不知道,压缩完发现原文件没了还以为数据丢了。建议养成加 -k 参数的习惯,脚本里尤其要加上。
另外,pigz 在处理体积小于单个块(默认约 128KB)的文件时,多线程优势完全发挥不出来,因为一个线程就能搞定,性能跟 gzip 几乎没区别。所以压缩一堆零碎小文件时,别指望有惊喜。再者,如果你的 tar 版本比较老,不支持 -I 参数,可以用 --use-compress-program 或者管道方式,效果一样。
最后分享一个我后来一直沿用的做法:在内网服务器上把 pigz 的源码包、zlib 源码包、编译好的二进制文件、rpm 包都分类存到内部软件仓库里,下次遇到离线机器,直接根据系统环境选方案,五分钟内就能部署好。pigz 虽然小,但在多核服务器的日志归档场景下,它带来的效率提升非常可观。如果你手头也有一批多核机器还在用单线程 gzip 压日志,这个工具值得你认真试一试。