免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

pigz离线安装与实战:多线程压缩让日志归档快数倍

pigz离线安装与实战:多线程压缩让日志归档快数倍 前阵子接到一个运维需求内网一台服务器要归档历史日志压缩软件只有系统自带的 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-develRedHat 系或 zlib1g-devDebian 系离线环境下直接下载 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. 核心参数与用法这样设线程数才不浪费 CPU4.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-programpigz -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包含大量图片和文本文件打包压缩后传给别人。我同时跑了原生 targzip 和 tarpigz 做对比机器配置为 8 核 CPU普通 SSD。实测结果很直观# 原生方式耗时约 6分30秒 tar -czf project.tar.gz /data/project # pigz 方式耗时约 1分15秒 tar --use-compress-programpigz -p 8 -cf project.tar.gz /data/project压缩出来的文件大小分别为 8.9GB 和 9.0GB差异只有约 1%完全在可接受范围内。这就是 pigz 最大的价值用大约 20% 的时间成本交付一个体积几乎相同的压缩包。5.3 场景三压缩等级与耗时权衡为了搞清楚压缩等级在实际业务里怎么选我用一份 4GB 的文本日志做了组测试数据如下压缩等级线程数耗时压缩后大小压缩率-1822秒1.85GB53.7%-6默认855秒1.51GB62.2%-98108秒1.47GB63.2%从数据看-1 到 -6 之间压缩率和耗时差距都很大但 -6 到 -9 之间压缩率提升不到 1%耗时却翻倍。所以我的建议很明确日常归档用默认 -6 就够了追求极致速度时用 -1-9 基本只有对压缩率极其敏感的场景才值得用。6. 离线使用中的常见问题与避坑记录6.1 线程数设太高反而更慢这是我最想强调的坑。第一次用 pigz 时我图省事直接 -p 32当时那台机器是 16 核 CPU结果压缩速度还不如 -p 8。原因是在多线程压缩时各个线程分别压缩完各自的块之后需要按顺序写入文件这个写入顺序是串行的。更关键的是线程数过多会导致 CPU 上下文切换频繁内存带宽成为瓶颈。尤其是机械硬盘环境下磁盘 IO 本身就是短板线程再多也只能排队等待写入。判断是不是 IO 瓶颈很简单压缩时看 top 里的 waIO 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 压日志这个工具值得你认真试一试。
返回列表