简介:面向需要在 CentOS 等 Linux 环境中开展性能压力测试的运维与开发人员,这份离线安装资料围绕 stress-1.0.4 展开,重点解决无外网或受限网络环境下压测工具及相关依赖部署不便的问题,并兼顾 sar 性能观测命令的配套使用需求。压缩包共 43 个文件,约 46.66MB,主要由 rpm 依赖包、源码构建文件、说明文档与配置脚本组成,既包含可直接离线安装的软件包,也保留 stress 源码编译所需的构建文件,内容组织上兼顾依赖补齐、安装验证与压测实践,便于根据服务器环境选择安装方式。已有 2598 人学习下载。读者可据此搭建 CPU、内存、I/O 等基础压测环境,配合 sar 采集系统负载数据,进一步理解 stress 参数配置、依赖补齐、源码编译与常见排错思路,适合作为 Linux 性能测试入门、离线部署与压测实验的参考材料。
1. 内网机器睡不着觉的那晚:stress 离线装机到底解决什么问题
机房在四楼,网线只通到内网交换机,yum install一敲就是 timeout。这种环境做 linux 压力测试,第一步不是写压测命令,而是先把包弄进去。stress 离线安装解决的就是这件小事:把一个小体量的压测工具塞进没有外网出口的 CentOS 机器,用手头现有的资源把 CPU、内存、磁盘 IO 的负载拉起来,验证新到货机器的散热、电源和老旧硬件的隐性故障。它适合三类人:验收服务器的运维、排查偶发卡死的后端开发、需要在内网复现高负载问题的测试同学。工具本身很简单,麻烦的全在包和依赖上。
2. 先把依赖摸清楚:stress 的压力模型、参数语义与三种离线包来源
2.1 stress 怎么造压力:四条 worker 路径的实际差别
stress 的工作模型很不花哨:主进程按参数 fork 出一批 worker 子进程,每个 worker 进入一种死循环,主进程负责收集退出状态(wait()),--timeout到点后给所有子进程发终止信号。它没有复杂的调度,也不做负载建模,所以压出来的曲线是「方波」而不是「正弦波」——这一点必须先接受,否则拿它去做业务相似负载的仿真会失望。
四条路径各自的循环体决定了它能测什么:
| worker 类型 | 参数 | 循环体做什么 | 压的是什么 |
|---|---|---|---|
| CPU | --cpu N | 反复调用sqrt(),内部做忙等计算 | 单核算力、散热、主频降频行为 |
| 内存 | --vm N | malloc后逐页写入,再free | 内存带宽、页回收、OOM 边界 |
| IO | --io N | 反复调用sync() | 内核回写路径、脏页排队 |
| 磁盘 | --hdd N | 在临时目录写文件再 unlink | 真实落盘带宽、inode 与元数据开销 |
值得单独说一句--io:它压的不是磁盘本身,是sync()这条系统调用路径。很多人在虚拟机上一跑--io 8,发现 iostat 的%util纹丝不动,就断定工具没用——其实脏页早被内核刷下去了,磁盘根本没形成队列。要打出磁盘 IO,得靠--hdd。这个区别在后面的避坑里还会再撞一次。
另一组容易被忽略的参数是节奏控制:--backoff N让每个 worker 启动间隔 N 微秒,避免瞬间 fork 出一堆进程把fork本身变成压力源;--vm-hang N让内存 worker 分配完挂起 N 秒再释放,用它能把「持续占用」和「反复分配释放」两种模式区分开。默认的--vm-hang 0是不挂起,也就是最凶的 malloc/free churn。
2.2 一台没网的机器能给你什么:glibc、gcc、rpm 的底牌盘点
动手前先花五分钟盘家底,这五分钟通常能省掉后面半小时的报错。
# 1. 系统版本和架构,决定了你该拿 el7 还是 el8、x86_64 还是 aarch64 的包 cat /etc/centos-release uname -m # 2. glibc 版本,跨发行版拿包翻车基本都翻在这 rpm -q glibc ldd --version | head -1 # 3. 有没有编译工具链,决定源码编译这条路走不走得通 for c in gcc make ld; do printf '%-6s: ' "$c"; command -v $c >/dev/null && $c --version | head -1 || echo MISSING; done # 4. 本机已有的仓库,看看是不是已经藏了一个本地源 yum repolist all 2>/dev/null | head -20这四条命令输出完,路线基本就定了:gcc齐全就走源码编译,缺工具链但手上有 ISO 就先搭本地源,两样都不占就只能靠一台同版本的有网机器把 rpm 打下来。
rpm -q glibc那一步别跳。CentOS 7 的 glibc 是 2.17,CentOS 8 是 2.28,从 8 上抓的包扔到 7 上,装的时候会甩一句libc.so.6(GLIBC_2.28) is needed,看着像缺文件,本质是符号版本对不上,补文件没用,只能换包。
2.3 离线包的三种来源与选型取舍
第一种,直接取发行版仓库里的二进制 rpm。stress 不在 CentOS 的 base 仓库里,它在 EPEL 里,包名形如stress-1.0.4-1.el7.x86_64.rpm。找一台同版本、同架构、能出网并且已经配了 EPEL 的机器,yumdownloader或者yum install --downloadonly把包拉到本地目录,再拷进内网。这条路的优点是快、装完即用;缺点是包跟着发行版走,el7的包塞不进el8,必须版本对齐。
第二种,源码包自己编。上游发布的是stress-1.0.4.tar.gz,两百多 KB,configure+make两步就完。优点是完全不挑发行版,只要 glibc 版本别低得离谱;缺点是目标机得有 gcc 和 make,而这恰恰是很多最小化安装的机器缺的东西。
第三种,先搭本地源,再用 yum 装。手上有 CentOS 安装 ISO 的话,把 ISO 挂到/mnt当 file 协议仓库,gcc、make、sysstat这些编译和观测要用的包一次性装齐。三种里这种最稳,因为它顺手把后面观测指标要用的sysstat、pidstat也解决了,不用再单独折腾。
提示:选型的第一原则是「目标机的 glibc 版本说了算」,不是「哪个包新用哪个」。压测机往往就是生产机,版本错配引入的库冲突比压不出效果更麻烦。
3. 两条落地路线:rpm 直装与源码编译的可复制步骤
3.1 rpm 直装:装前预检与装后验证
拿到 rpm 之后别急着-ivh,先问它想要什么。
# 把包放到目标机的固定目录,后面所有操作都基于这个路径 mkdir -p /opt/soft && cd /opt/soft # 预检一:这个包依赖哪些能力(capability) rpm -qpR stress-1.0.4-1.el7.x86_64.rpm # 预检二:逐个确认依赖在本机是否已满足,输出为空说明全齐 rpm -qpR stress-1.0.4-1.el7.x86_64.rpm \ | awk '!/^rpmlib/ && !/^\//' \ | while read -r cap; do rpm -q --whatprovides "$cap" >/dev/null 2>&1 || echo "MISSING: $cap" donerpm -qpR读的是包头的依赖声明,不需要安装就能看。第二段用awk把rpmlib(...)这类 rpm 自身的内部能力过滤掉——它们不是真依赖,留着会刷一屏假阳性。剩下逐条喂给rpm -q --whatprovides,有输出就是本机已有包提供了这个能力,没输出才是真缺。这个循环跑完一行MISSING都没有,安装就是十拿九稳的事。
# 正式安装,-v 显示过程,-h 显示进度条 rpm -ivh stress-1.0.4-1.el7.x86_64.rpm # 验证三连:包在不在、命令在哪、参数认不认 rpm -q stress command -v stress stress --help 2>&1 | head -15rpm -q stress输出stress-1.0.4-1.el7.x86_64说明包数据库里已经登记;command -v确认/usr/bin/stress在 PATH 上;stress --help把 usage 打出来,说明动态链接没坏——有些残缺环境里二进制在、一跑就error while loading shared libraries,--help这一下正好能提前暴露。
最后跑一个三十秒的小压测,确认真的能拉负载:
# 2 个 CPU worker,跑 30 秒后自动退出,--verbose 打印每个 worker 的 pid 和退出状态 stress --cpu 2 --timeout 30s --verbose参数含义:--cpu 2是 fork 两个 cpu worker,不是「用 2 个核」,实际占用取决于调度器把它们扔到哪;--timeout 30s到点后主进程向所有子进程发SIGTERM,worker 收到后打一行exited with status;--verbose建议在第一次验证时都带上,它会把 pid 打出来,后面pidstat要按 pid 抓数据时用得上。
3.2 源码编译:configure 之前的三个前置条件
源码这条路的前置条件就三个:gcc、make、以及能正常工作的 ld。判断方法在 2.2 里已经给过,这里补一个常见误判——gcc在 PATH 上不代表能编译,很多最小化安装的机器只装了gcc的元包,缺glibc-devel,一编就报stdio.h: No such file or directory。
# 补一个更硬的检查:试着编一个最小 C 程序 printf '#include <stdio.h>\nint main(){printf("ok\\n");return 0;}\n' > /tmp/t.c gcc /tmp/t.c -o /tmp/t && /tmp/t || echo "工具链不完整,需要 glibc-devel"这个自检比看版本号靠谱得多。头文件、链接器、启动文件任何一环缺失,这里立刻暴露。
工具链齐了就开始编:
# 解压到 /usr/local/src,这是放源码的常规位置 tar -xzf /opt/soft/stress-1.0.4.tar.gz -C /usr/local/src cd /usr/local/src/stress-1.0.4 # --prefix 指定安装根,装到 /usr/local 下不污染系统目录 ./configure --prefix=/usr/local # -j 的取值别超过核数,超了只会互相抢内存 make -j"$(nproc)" # 安装需要写 /usr/local/bin 的权限,普通用户配合 sudo 或改 --prefix 到家目录 make install /usr/local/bin/stress --help 2>&1 | head -5--prefix这个参数值得多说一句:没有 root 的机器上,改成--prefix=$HOME/.local一样能装,装完把$HOME/.local/bin加进 PATH 就行。压测常常是在别人负责的机器上临时做,能不动系统目录就不动,事后清理也简单,rm -rf对应目录即可,不留包管理器的残留记录。
make -j的数字不是越大越好。压测机本身可能已经在跑业务,编译期把核占满会让业务抖动,编译和压测是两件事,别混在一起。
3.3 缺工具链的兜底方案:拿安装 ISO 搭一个本地 yum 源
最尴尬的局面是:机器没网、没 gcc、手上只有一个 CentOS 安装 ISO。这个局面其实好解,ISO 里的Packages目录本身就是一个完整的仓库。
# 挂载 ISO,/dev/sr0 是虚拟光驱的常规设备名,ISO 文件则用 -o loop mkdir -p /mnt/iso mount -o loop /opt/iso/CentOS-7-x86_64-DVD-2009.iso /mnt/iso # 物理光驱场景换成:mount /dev/sr0 /mnt/iso # 确认仓库元数据在不在,repodata 目录存在才说明这是个可用源 ls -d /mnt/iso/repodata仓库定义文件写起来就四行,关键在于baseurl用file://协议指向挂载点:
cat > /etc/yum.repos.d/local-iso.repo <<'EOF' [local-iso] name=Local ISO Repository baseurl=file:///mnt/iso enabled=1 gpgcheck=0 EOF # 清缓存重建,然后只启用本地源装包,避免 yum 去够外网超时 yum clean all yum --disablerepo='*' --enablerepo='local-iso' install -y gcc make glibc-devel--disablerepo='*'这一步是血泪经验:内网机器上通常还留着几个指向外网的 repo 文件,不清掉的话 yum 会先尝试连它们,每个超时几十秒,装一个包等十分钟。显式禁用再启用本地源,整个过程几秒钟结束。
gpgcheck=0是因为没导入 ISO 的 GPG key,属于内网自用场景的常规取舍;如果合规上要求校验,把 key 从 ISO 里导出来rpm --import之后改回gpgcheck=1就行。
装完glibc-devel之后回到 3.2 的编译流程,整条链路就通了。这套本地源还有一个附带好处:sysstat(提供pidstat、sar)、tar、gzip这些观测和打包工具都能一并装齐,后面第 4 章要用的观测手段不再缺胳膊少腿。
4. 压测方案怎么写:CPU、内存、IO 三类负载的参数标定与观测口径
4.1 压 CPU:worker 数跟核数的对齐方式
CPU 压测最容易犯的错是「数字拍脑袋」。--cpu 4在 4 核机器上是满载,在 32 核机器上是挠痒痒。先把核数取出来:
# 逻辑核数,也就是调度器眼里的 CPU 个数 nproc # 物理核数(每颗物理 CPU 的 core 数之和),排除超线程后的真实并行度 lscpu | awk '/^CPU\(s\)|^Core\(s\) per socket|^Socket\(s\)/{print}'nproc给的是逻辑核数,包含超线程。压测标定一般用两档:--cpu $(nproc)打满逻辑核,观察频率是否掉到基频以下、温度是否冲上限;--cpu $(( $(nproc) / 2 ))对应物理核数(在开启超线程且核数翻倍的机器上大致成立),观察有没有单核跑满导致的长尾。
# 打满全部逻辑核,持续 5 分钟,把每个 worker 的启动/退出都打出来 stress --cpu "$(nproc)" --timeout 300s --verbose参数说明:--timeout的单位固定是秒,写5m会被当成非法参数直接报 usage,这是新手第一个卡点。--verbose在这里的用处是确认 worker 数量真的等于nproc——如果机器上有人配了 cgroup 的cpu.max或者用了容器限额,你会看到 worker 都起来了但%CPU上不去,这时候问题不在 stress。
判定 CPU 压测是否有效的标准不是「进程在跑」,而是三条同时成立:vmstat的r列(运行队列)稳定大于等于核数;id接近 0 而sy不高(sy高说明瓶颈在系统调用不在算力);us占到 90% 以上。只满足第一条,可能是在等锁;只满足第三条,可能是被限流了。
4.2 压内存:总量、增量与留多少余量
内存压测的核心参数只有两个:--vm(worker 数)和--vm-bytes(每个 worker 分配多少)。总压力等于两者相乘,这个乘法关系是很多人漏算的地方——写--vm 4 --vm-bytes 2G,压的是 8G,不是 2G。
先算清楚本机有多少可用的字节数:
# 用字节输出,避免 MB/GB 换算误差 free -b | awk '/^Mem:/{printf "total=%d\navailable=%d\n", $2, $7}' # 留 20% 余量后的单 worker 字节数(4 个 worker 平分) free -b | awk '/^Mem:/{printf "per_worker=%d\n", int($7*0.8/4)}'$7是available列,比free列更准,因为它把可回收的 page cache 也算进去了。留 20% 余量不是保守,是必须——压测机上通常还跑着 sshd、监控 agent、日志采集,把内存压到一根毛都不剩,最先被 OOM killer 挑中的往往就是它们,机器直接失联,你连结果都收不回来。
# 4 个 worker,每个 1GiB(1073741824 字节),持续 5 分钟 stress --vm 4 --vm-bytes 1073741824 --timeout 300s --verbose注意这里写的是纯字节数。为什么不用1G这种写法,第 5 章会专门讲,那是一个能让整个压测白做的坑。
如果要测「内存常驻」而不是「分配释放抖动」,加上--vm-hang:
# 分配后挂起 60 秒再释放,模拟长驻内存对象 stress --vm 4 --vm-bytes 1073741824 --vm-hang 60 --timeout 300s--vm-hang 60的效果是 worker 分配完内存后 sleep 60 秒,期间内存一直被占着。这个模式更接近真实业务的堆占用,也更容易把机器推到 OOM 边缘——因为释放被延后了,峰值占用会叠加。
4.3 压磁盘 IO:--hdd和--io到底选哪个
回到 2.1 的结论:--io压的是sync()调用路径,--hdd压的是真实落盘。要做磁盘验收,用--hdd。
# 2 个 hdd worker,每个写 2GiB 的临时文件,持续 5 分钟 stress --hdd 2 --hdd-bytes 2147483648 --timeout 300s --verbose--hdd-bytes同样是纯字节数。这个参数决定每个 worker 单个临时文件的大小,写满之后 unlink 再重来。文件大小要大于目标盘的写缓存,否则全程都在内存里转,iostat 上看不到多少wMB/s。一般取目标盘容量的 1%~5%,或者干脆固定 2GiB。
# 混合场景:CPU 打满 + 磁盘持续写,观察 IO 等待对算力的挤压 stress --cpu "$(nproc)" --hdd 2 --hdd-bytes 2147483648 --timeout 300s这个组合是验收里最有用的一档。单压 CPU 时机器可能很稳,一旦叠上磁盘写,vmstat的wa列会告诉你真相:wa超过 20 说明有相当比例的 CPU 时间在等 IO,这时候机器的实际吞吐是要打折的。新机器验收如果只跑单压,这个折扣是看不出来的。
4.4 观测口径:三组数据怎么读才算读懂了
压测命令本身只有一行,判断结果靠的是旁边的观测。三组数据足够覆盖绝大多数场景。
第一组,vmstat,看全局。
# 每秒采样一次,共 10 次 vmstat 1 10关键列:r是运行队列长度,持续大于核数说明 CPU 排队;si/so是换入换出,非 0 说明内存已经压到 swap,这时候内存压力的结论要打问号;wa是 IO 等待占比;us+sy接近 100 而id接近 0 才算 CPU 打满。
第二组,pidstat,看进程。
# 按 pid 抓 stress 所有子进程的 CPU 和内存,pidstat 来自 sysstat 包 pidstat -p "$(pgrep -d, stress)" 1 5pgrep -d,把多个 pid 用逗号拼起来喂给-p,一次抓全。输出里的%CPU是单核百分比(100 表示占满一个核),4 核机器上四个 worker 各自接近 100 才叫打满。如果 sysstat 装不上,退而求其次用top -b -n 1 -p $(pgrep stress | head -1)。
第三组,iostat,看磁盘。
# -x 扩展统计,2 秒一次共 5 次,重点看 %util 和 await iostat -x 2 5%util接近 100 说明设备饱和;await是平均等待毫秒数,机械盘上到几十毫秒就开始影响体验,SSD 上到几毫秒就该警觉。这两个数在--hdd和--io下的表现差异极大,正好可以用来验证你选的参数是不是压到了想压的地方。
注意:观测本身有开销。
vmstat 1和iostat -x 2加起来对结果的影响可以忽略,但如果把采样间隔设成 0.1 秒,采样进程自己就会变成负载的一部分,测出来的数就不干净了。
5. 五个真实翻车点:从 vm-bytes 单位到 OOM 杀掉 sshd 的排查路径
5.1 现象:内存 worker 起来了,内存却纹丝不动
现象:stress --vm 4 --vm-bytes 1G跑起来,top里能看到四个 stress 进程,但free的used几乎没变化,vmstat的si/so全是 0,机器一点都不卡。
原因:--vm-bytes的解析走的是纯数字路径,只认字节数。写1G时,解析器读到G就停了,最终拿到的值是1——每个 worker 分配 1 字节。进程确实在跑,也确实在做 malloc/free,压的是分配器的元数据开销,跟内存容量完全没关系。这个坑极其隐蔽,因为命令不报错,进程不退出,看起来一切正常。
解决:一律换算成字节数,或者用awk/shell 算。1GiB = 1073741824,2GiB = 2147483648。稳妥的做法是让脚本自己算:
# 从 free 取可用字节,按 80% 和 worker 数算出每个 worker 的字节数 workers=4 per=$(free -b | awk -v w="$workers" '/^Mem:/{printf "%d", int($7*0.8/w)}') echo "per_worker_bytes=$per" stress --vm "$workers" --vm-bytes "$per" --timeout 300s以后再看到「内存压不上去」,第一反应先echo一下传进去的数字,比怀疑内核快得多。
5.2 现象:压测跑到一半 SSH 断连,机器彻底失联
现象:stress --vm 8 --vm-bytes <接近物理内存的值>跑到第三分钟,终端卡死,重连提示连接被拒绝,ping 通但 SSH 上不去,只能去机房按电源。
原因:内存压到没有余量,内核的 OOM killer 触发。它的评分机制是「谁占的内存多就挑谁」,听起来应该杀 stress,但实际被杀顺序取决于oom_score_adj,sshd、监控 agent 这些常驻进程的战绩往往比新起的 stress 更高,因为它们活得久、算分时占优。结果就是压测工具没事,管理通道被端了。
解决:三层防护。第一层,压测总量不超过available的 80%,这个在 4.2 里已经算过。第二层,压测前给关键进程降权,把 sshd 保护起来:
# 给 sshd 一个极低的 OOM 分数,让它不容易被选中 pgrep -x sshd | while read -r p; do echo -1000 > /proc/"$p"/oom_score_adj 2>/dev/null; done第三层,如果是长跑压测,别在交互式 SSH 里挂着跑,用nohup或者systemd-run放到后台,输出重定向到文件,断连了也不影响。
# 后台长跑,输出落盘,断连不中断;--scope 让它跑在独立 cgroup 里方便整体清理 systemd-run --scope --unit=soak-test -p MemoryMax=80% \ stress --vm 4 --vm-bytes 1073741824 --timeout 3600s-p MemoryMax=80%是给 cgroup 加内存上限,让内核对 stress 这个 cgroup 先做回收,压力不会溢出到系统全局。
5.3 现象:--hdd跑了十分钟,iostat 上没有写流量
现象:stress --hdd 4 --hdd-bytes 2147483648跑着,iostat -x里的wMB/s一直是零点几,%util个位数,磁盘根本没被压到,反而是free的内存在往下掉。
原因:--hdd的临时文件写在/tmp下。很多服务器(尤其是容器宿主机和云主机)的/tmp挂的是 tmpfs,本质是内存文件系统。写进去的字节全在 RAM 里,一次磁盘 IO 都没发生,反而把内存吃掉了一大块。这个现象在内存压测和 IO 压测混着跑的时候特别迷惑,容易得出「IO 性能极好」的错误结论。
解决:先确认/tmp的挂载类型,再决定怎么绕。
# 看 /tmp 是 tmpfs 还是真实块设备 findmnt -T /tmp -o TARGET,SOURCE,FSTYPE,SIZE # 如果是 tmpfs,找一块真实盘出来(比如数据盘挂载点 /data) df -hT | awk '$2!="tmpfs" && $2!="devtmpfs"{print $1, $2, $7}'确认是 tmpfs 之后,有两条路:一是把压测目录切到真实盘上,--hdd本身不支持指定路径,但可以cd到目标目录再跑(部分打包版本的临时文件是相对当前工作目录创建的,跑之前先用一条小命令确认实际落点);二是干脆换stress-ng,它提供了--temp-path参数,能显式指定落盘目录。离线环境装stress-ng依赖更多,如果只是验收磁盘,dd其实也够用,但dd是单线程顺序写,跟真实业务的并发随机写差得远,别拿它当唯一依据。
5.4 现象:--timeout到点了,ps里还是一片 stress 进程
现象:带了--timeout 60s,一分钟过去,主进程退了,ps aux | grep stress还能看到一堆 worker。
原因:--timeout到期后主进程向子进程发SIGTERM,worker 收到信号后要走到下一个检查点才退出。CPU worker 的循环体极短,响应很快;但内存 worker 如果正卡在malloc大块内存或者 page fault 上,SIGTERM的处理会被推迟。更常见的情况是主进程自己被kill -9了——比如 SSH 断连触发 SIGHUP,主进程直接死,子进程被 init 收养,成了孤儿继续跑。
解决:清理用pkill,别用kill逐个找。
# 先看还有多少残留 pgrep -a stress # 温和终止,给 5 秒缓冲 pkill -TERM -x stress; sleep 5 # 还剩下就强杀,注意 -x 是精确匹配进程名,别把 grep 自己带上 pkill -KILL -x stress-x这个参数很关键。不带的话pkill stress会匹配命令行里含stress的所有进程,包括正在编辑的vim stress.log,那就要出事故了。压测机上跑着别的服务时,精确匹配是保命的习惯。
5.5 现象:rpm -ivh报GLIBC_2.28 not found
现象:从另一台机器上拷了个 rpm 过来,装的时候报libc.so.6(GLIBC_2.28)(64bit) is needed by stress-...。
原因:包是从更高版本的发行版上取的。CentOS 7 的 glibc 是 2.17,包在编译时链接到了 2.28 才有的符号,本机的动态库提供不了这些符号版本。缺的不是文件,是符号的版本标签,所以哪怕你把/lib64/libc.so.6从别的机器拷过来也没用,反而会搞坏系统。
解决:换包,不要试图补库。
# 确认本机 glibc 版本,据此决定该拿哪个 el 版本的包 rpm -q glibc # 如果没有匹配的 rpm 可用,直接走源码编译(第 3.2 节), # 源码在目标机上编译时链接的是本机 glibc,天然不会有这个问题判断依据很简单:el7的包只能在 el7 上用,el8的包只能在 el8 上用,跨大版本一律走源码。这条规则在离线环境里比什么都重要,因为一旦装错,卸载时rpm -e可能因为依赖关系被卡住,反而留下烂摊子。
6. 把一次性压测变成例行验收:阶梯加压脚本与结果沉淀
单次压测最大的问题是结论不可比。今天压 5 分钟说「很稳」,明天换个参数压 5 分钟说「有点抖」,两次数据没有共同基准,讨论就变成玄学。我的做法是把压测做成固定档位的阶梯,每档固定时长、固定采样、固定落盘,机器之间的结果可以直接横向比。
#!/bin/bash # soak.sh —— 阶梯加压:25% / 50% / 75% / 100% 四档,每档 5 分钟 # 用法:./soak.sh /var/log/soak set -u LOG_DIR="${1:-/var/log/soak}" mkdir -p "$LOG_DIR" LOG="$LOG_DIR/soak-$(date +%Y%m%d-%H%M).log" CORES=$(nproc) echo "# host=$(hostname) cores=$CORES start=$(date '+%F %T')" > "$LOG" for pct in 25 50 75 100; do n=$(( CORES * pct / 100 )) [ "$n" -lt 1 ] && n=1 echo "=== $(date '+%F %T') cpu ${pct}% workers=$n ===" >> "$LOG" # 后台起压测,前台做采样,避免采样被压测阻塞 stress --cpu "$n" --timeout 300s --quiet & spid=$! for _ in $(seq 1 10); do sleep 30 vmstat 1 1 | tail -1 >> "$LOG" done wait "$spid" 2>/dev/null # 每档结束记录一次内存和负载余量,作为档间恢复的判断依据 free -m | awk '/^Mem:/{printf " free_mb=%s avail_mb=%s\n", $4, $7}' >> "$LOG" done echo "# done $(date '+%F %T')" >> "$LOG"脚本的逻辑不复杂,但有几个设计取舍值得说。stress放后台、采样放前台,是因为--timeout期间主进程一直占着终端,前台采样会被压住;sleep 30每档采 10 次,五次能覆盖整档的后半段,避开启动瞬间的抖动;每档结束记一次free,是为了看档位回落时内存有没有回到基线——回不去说明有泄漏或者 page cache 没释放,这比压测期间的峰值更值得警惕。
结果沉淀成一张表,比盯着屏幕看更有说服力:
| 档位 | worker 数 | r 均值 | us% | sy% | wa% | 可用内存 MB | 判定 |
|---|---|---|---|---|---|---|---|
| 25% | 2 | 2.0 | 24 | 1 | 0 | 6200 | 正常 |
| 50% | 4 | 4.1 | 49 | 2 | 1 | 6100 | 正常 |
| 75% | 6 | 6.3 | 73 | 3 | 2 | 5950 | 正常 |
| 100% | 8 | 9.8 | 92 | 6 | 3 | 5700 | 队列溢出 |
最后一行的r=9.8大于 worker 数 8,说明出现了额外排队,可能是后台的监控 agent 在抢 CPU,也可能是超线程下的调度争抢。这个信息单跑一次满载是看不出来的,只有阶梯对比才能定位到「从哪一档开始不线性」。
从那以后我每次进场压测前,都会强制走一遍rpm -qpR和free -b这两步,参数里的字节数一律用命令算、不用手敲,跑完必pkill -x扫一遍残留。这三件事花不到两分钟,但省下来的返工时间远不止两小时。希望帮到你。
本文还有配套的精品资源,点击获取