内网Linux压力测试:stress离线安装与资源压测实战
简介:面向 Linux 运维与性能测试入门读者的 stress 离线安装包,针对 CentOS 服务器无法联网、缺少编译依赖导致压测工具装不上等问题,提供一套可直接在本地部署的素材。压缩包共 43 个文件、约 46.66MB,主体为 stress-1.0.4 源码及配套构建脚本(configure、install-sh、m4 等 Autotools 文件);另附 11 个 rpm 包,可补齐 gcc-g++ 4.8.5 等编译环境,省去逐台机器查找依赖的麻烦。readme、news、html 手册与 changelog 等文档保留了版本变更和参数说明,便于对照查阅。已有 2598 人下载学习。该工具体积小、依赖少,在单机上即可模拟多进程 CPU 满载、内存分配与同步 IO 压力,常用于上线前的稳定性验证与教学实验。借助这份材料,读者能完成源码编译与安装验证,掌握多维度压测的常用参数组合,并配合 sar 命令采集系统指标、对照压测前后的负载变化,形成从部署到观测的完整流程。
1. 内网交付前的那轮压测,为什么绕不开 stress
内网一台机器要交付,验收单上写着“提供 Linux 压力测试数据:CPU 满载 30 分钟、内存占用 70% 各跑一轮”,可这台机器连不上外网,执行 yum 安装就直接报找不到有效源。stress 就是为这种场面准备的:一个几百 KB 的命令行小工具,不依赖服务端、不需要 agent,执行后就能按 -c、-i、-m、-d 造出 CPU、IO、内存、磁盘四类负载。离线安装它本身不难,难的是把依赖凑齐、把参数压得足够有代表性,而不是看起来能跑。这篇按我给内网机器做验收的顺序,把包怎么备、怎么装、怎么压、哪里容易出问题一次写清,给要做容量基线和稳定性验证的运维、测试同学照着用。
2. stress 的负载模型:四类 worker 分别模拟哪种资源压力
在动手打包之前,先把 stress 的工作模型弄清楚。很多人装完只会敲一句stress --cpu 4,看到 load average 上去了就交差,结果压出来的数据和真实业务的瓶颈毫无关系,验收被退回重做。这一章把 stress 的进程模型、参数语义和资源类型的对应关系拆开讲,后面第三章的离线包选型、第四章的参数组合都建立在这套模型上。
2.1 主进程只做三件事:解析参数、fork worker、回收
stress 的代码量很小,主流程非常直白:主进程解析 argv,按-c/-i/-m/-d指定的数量分别 fork 出对应类型的 worker 进程,然后 waitpid 等它们退出。每个 worker 是一个死循环,循环体就是某类资源的最小操作单元——CPU worker 反复计算sqrt(),内存 worker 反复 malloc、memset、free,IO worker 反复调sync(),磁盘 worker 反复 write、fsync、unlink 临时文件。--timeout到点后主进程通知所有 worker 退出,回收后自己结束。
这个模型直接决定了它的能力边界:stress 能精确控制“有几个进程在争抢某类资源”,但它不产生任何业务语义的负载,也完全不采集指标。所以判断压测是否达标,观测部分必须另外接 vmstat、iostat、pidstat,而且阈值要在压之前就写死,不能压完再解释。
几个容易被忽略的通用参数值得先记住:
-v打开详细输出,每轮打印 worker 的 pid 和当前动作,第一次跑某项压测时建议加上;-q静默模式,放进脚本时用,避免日志被刷爆;-n只打印将要 fork 什么、不真正执行,相当于试运行,用来验证拼出来的参数对不对;--backoff N让 worker 错开 N 微秒启动,避免所有 worker 在同一瞬间拉高负载;-t/--timeout接受s/m/h/d后缀,超时自动收工,是防止忘记杀进程的最后保障。
其中-n这个参数在实际操作中价值很高。内网机器上误敲一个--vm-bytes 32G,如果机器没有开 swap 保护,轻则 OOM 杀掉业务进程,重则直接触发 panic 导致机器失联。先-n跑一遍,把要生成的负载规模打出来核对,再真跑,是个成本极低的好习惯。
2.2 四类负载对应哪四种故障面
stress 的四类参数不是随便选的,每一类对应一种不同的系统瓶颈,验收目标不同,选项也完全不同。下面这张表是我在写压测方案时固定会附的一页,先确定测什么,再去调参:
| 参数 | 制造的现象 | 主要观测指标 | 适合验证什么 |
|---|---|---|---|
--cpu N | N 个进程在用户态持续计算 | us 占比、load average、每核频率与温度 | 调度能力、散热与降频、CPU 配额是否生效 |
--vm N --vm-bytes X | N 个进程反复分配并写入 X 内存 | free/available、si/so、OOM 日志 | 内存水位、swap 策略、OOM killer 行为 |
--io N | N 个进程循环调用 sync() | bi/bo、wa、磁盘队列深度 | 全局回写压力、文件系统在脏页回收下的表现 |
--hdd N --hdd-bytes X | N 个进程写临时文件 | 磁盘写带宽、await、%util | 磁盘写吞吐上限、空间耗尽风险 |
最常被误用的是--io。sync()是系统级调用,它把整个系统的脏页往块设备刷,并不产生属于某个进程的写入量。所以在只有一块盘的机器上,--io 4的写入压力并不会比--io 1大多少,真正的瓶颈在块设备层而不是进程数。想要稳定的写吞吐,应当用--hdd让 worker 持续写文件,--io更适合用来观察“脏页同时回刷时系统响应如何变慢”。
另一处常见误解是拿--cpu的结果当作应用性能结论。CPU worker 跑的是纯浮点开方,缓存局部性极好,几乎不碰内存和锁,和大多数 Java、Go 服务的负载特征差得很远。它验证的是“这台机器在高主频占用下会不会降频、会不会失联”,不是“业务能扛多少 QPS”。这个界线划清楚,验收报告才不会被质疑。
2.3 离线之前必须确认的三件事
在线环境装 stress 一条命令的事,离线环境要先回答三个问题,否则备好的包拿进去一定装不上或者参数对不上。
第一是 CPU 架构。x86_64的包放到aarch64上,rpm -ivh会直接报package is not installable或者架构不匹配。第二是发行版大版本,不同大版本之间 glibc 的符号版本不同,跨大版本拷包是离线安装最常见的失败原因。第三是 stress 自身的版本,不同版本支持的参数集不一样,老版本可能根本没有--vm-keep或--hdd-noclean,脚本照着新参数写就会报unrecognized option。
确认方式很土但有效:在内网机器上先跑三条只读命令,把信息带回来再决定备什么包。
# 1. 架构、发行版大版本、内核 uname -m && cat /etc/redhat-release && uname -r # 2. 机器上是否已经装了(可能只是 PATH 没带上) rpm -qa | grep -i '^stress' || echo "not installed" # 3. 已装 glibc 的版本,用于比对包的依赖 rpm -q glibc这三条命令的输出决定了后面所有动作:uname -m决定下载哪个架构的 rpm,redhat-release决定依赖树从哪个大版本拉,rpm -q glibc用来和联网机上rpm -qpR输出里的libc.so.6(GLIBC_2.xx)做对照。只要 glibc 要求的符号版本高于目标机现有版本,包就一定装不上,这时候不必逐条处理依赖报错,直接回联网机重拉对应大版本的包。
提示:把这三条命令的输出连同
rpm -qpR的结果一起贴进工单或记录里。后面不管隔多久再装第二台、第三台机器,都不用重新走一遍摸索。
3. 把离线包凑齐:从依赖树到 rpm 落地的完整链路
离线安装的失败,绝大多数不是发生在“装”的那一步,而是发生在“备”的那一步——依赖少拉了一层,或者拉的是架构不对的包,等到内网才暴露。这一章把整条链路拆成四段:联网机上拉全依赖、落地前校验、内网安装、无 rpm 可用时的源码兜底。每段都给出可直接复制的命令,并说明每行在做什么、参数怎么改。
3.1 在联网机上用 yumdownloader 拉全依赖树
主力方案是yumdownloader --resolve。它会把目标包以及它依赖的包一起下载到指定目录,--resolve是递归解析依赖的关键,不带这个参数只会下载一个孤零零的主包。
# 准备工具:yum-utils 提供 yumdownloader / repotrack sudo yum install -y yum-utils createrepo # 建一个干净的目录,避免和别的包混在一起 mkdir -p /tmp/stress-offline cd /tmp/stress-offline # --resolve 递归解析依赖;--destdir 指定落盘位置 yumdownloader --resolve --destdir=/tmp/stress-offline stress # 看结果:包名、大小、数量必须现场核对,不能只看没报错 ls -lh /tmp/stress-offline如果源里同时有stress和stress-ng,注意这是两个不同的项目,参数并不通用。stress体积小、参数简单,适合快速造负载;stress-ng功能多得多(支持--cpu-method、--metrics等),但依赖也更多。验收只需要“把 CPU 和内存压上去”,用前者就够,包也小,离线搬运的成本低。
另一条路是repotrack,它会把整棵依赖树连同所有可能需要的包全部下载下来,好处是绝不会因为少解析一层而漏包,坏处是数量可能膨胀到几十个,内网搬运和安装都变慢。
# repotrack 拉全量依赖树,-a 指定架构,-p 指定目录 repotrack -a x86_64 -p /tmp/stress-full stress还有一种更贴近系统安装习惯的做法:yum install --downloadonly。它按“即将安装”的视角把包下载下来,缺点是如果本机已经装过同名包,它不会重复下载,容易造成“联网机上看着有、拷进去发现少一个”的误判。
# --downloadonly 只下载不安装,适合本机干净、没历史包袱的场景 sudo yum install --downloadonly --downloaddir=/tmp/stress-offline stress三条命令按场景选:机器干净、只装一两个包用--downloadonly;要完整可控用yumdownloader --resolve;怀疑依赖有隐藏层级、宁可多拿不可漏拿,用repotrack。我一般在正式备包时先用yumdownloader,装完再做一次rpm -qpR交叉验证,确认没有遗漏。
3.2 拷贝之前先做三项校验
包下载完直接打成 tar 拷进内网,是最常见的返工原因。花两分钟做三项校验,能省掉后面一整天。
cd /tmp/stress-offline # 1. 完整性:校验包内摘要,输出 digests OK 才算文件没损坏 rpm -K *.rpm # 2. 依赖清单:逐条看 Requires,重点盯 libc.so.6(GLIBC_xxx) 和 so 文件名 for p in *.rpm; do echo "== $p"; rpm -qpR "$p"; done # 3. 文件清单:确认二进制落在哪个路径,后面 PATH 问题一眼能看出来 rpm -qpl *.rpm | grep -E '/usr/(s?bin)/' # 4. 生成校验和清单,随包一起拷,内网再校验一次 sha256sum *.rpm > SHA256SUMSrpm -K的输出要分清:digests OK表示内容摘要对得上;digests signatures OK表示签名也验过了;如果只有digests OK而没验签名,说明本机没导入对应公钥,这种情况下包本身没坏,但来源可信度需要另行确认。离线环境通常没法在线导入公钥,我的做法是只依赖 sha256 清单做完整性校验,把来源确认放在备包环节完成。
rpm -qpR这一步最关键。输出里出现libc.so.6(GLIBC_2.28)(64bit)这类带符号版本的依赖时,一定要去目标机上rpm -q glibc比对。这一步不做,内网安装时就会看到一个非常唬人的报错,然后开始逐个补包、反复试错。
最后把目录打包。压缩包命名带上日期,后面回滚、复用都方便:
tar czf stress-offline-$(date +%F).tar.gz -C /tmp stress-offline ls -lh stress-offline-*.tar.gz3.3 内网落地:先试运行,再一次性事务安装
到了内网机器上,第一件事不是安装,是校验和解包。
# 1. 解开并校验,和 SHA256SUMS 对不上就直接停手 tar xzf stress-offline-2024-01-01.tar.gz cd stress-offline sha256sum -c SHA256SUMS # 2. 试运行:检查依赖是否全满足,不实际写入任何文件 rpm -ivh --test *.rpm--test是离线安装里最有价值的一个选项。它会把依赖检查、文件冲突检查全跑一遍,但不动系统。看到Failed dependencies就在这里解决,比装到一半失败、留下一堆半装状态的包干净得多。
试运行通过后,安装的关键是把所有 rpm 放进同一条命令里:
# 关键点:*.rpm 作为一次事务提交,rpm 会自动排列安装顺序 sudo rpm -ivh *.rpm很多人习惯for f in *.rpm; do rpm -ivh $f; done,这在有包间依赖时会失败,因为循环是逐个提交事务的,先装被依赖方就会报缺少依赖。把包放在同一条命令行上,rpm 会视为一个事务,自动排序。
如果机器上原来装过同名包、版本不同,-ivh会报package already installed。这时候有三个选择:确实要升级用rpm -Uvh;要覆盖同版本用rpm -ivh --replacepkgs;只是文件缺失想补回来用rpm -ivh --replacepkgs --force。--force不要无脑加,它会同时打开--replacepkgs、--replacefiles、--oldpackage三个开关,把版本回退也放行,容易埋下隐患。
还有一条更接近 yum 习惯的路子:把这些包做成一个本地仓库,用 yum 装,好处是依赖解析和后续卸载都更省心。
# 把目录做成 repo,再让 yum 从本地装 cd /tmp/stress-offline createrepo . sudo tee /etc/yum.repos.d/stress-local.repo >/dev/null <<'EOF' [stress-local] name=stress local repo baseurl=file:///tmp/stress-offline gpgcheck=0 enabled=1 EOF # 只启用本地源,避免误连不存在的网络源导致超时 sudo yum --disablerepo='*' --enablerepo='stress-local' install -y stress--disablerepo='*'这一步在内网环境几乎是必须的。默认源不可达时,yum 会卡在连接超时上很久才报错,禁用全部再只开本地,安装瞬间完成。
3.4 没有合适 rpm 时,走源码离线编译
有些场景下 rpm 走不通:目标机架构特殊、发行版大版本对不上、或者需要某个只有新版本才有的参数。这时候源码编译是最稳的兜底方案,代价是需要机器上有编译工具链。
工具链本身也可以离线解决——用系统安装 ISO 做本地源,这是内网环境的常用做法:
# 挂载安装 ISO 作为本地源,解决 gcc/make 缺失 mkdir -p /mnt/iso mount -o loop /path/to/system.iso /mnt/iso sudo tee /etc/yum.repos.d/local-iso.repo >/dev/null <<'EOF' [local-iso] name=local iso baseurl=file:///mnt/iso gpgcheck=0 enabled=1 EOF sudo yum --disablerepo='*' --enablerepo='local-iso' install -y gcc make工具链就位后,源码包(事先在联网机上下好、一并拷进来的 tar.gz)按标准三步走:
tar xzf stress-*.tar.gz cd stress-*/ # --prefix 装到独立目录,不污染系统路径,也方便整体删除 ./configure --prefix=/usr/local/stress make -j"$(nproc)" sudo make install # 验证:直接打完整路径,绕开 PATH 问题 /usr/local/stress/bin/stress --version--prefix指定独立目录这个习惯值得坚持。源码装的程序不受 rpm 数据库管理,散落在/usr/local/bin里以后很难说清哪些是包管理的、哪些是手工编的。统一放一个前缀下,卸载就是rm -rf,干净。
另外make -j"$(nproc)"里的核数不要超过实际可用核,内网小规格机器上并行编译反而更慢,甚至因为内存不足被 OOM 杀掉编译器进程,报出一个和代码毫无关系的错。
4. 参数组合实战:把 CPU、内存、IO 压到有说服力的程度
包装好了,下面才是真正决定验收能不能过的一步:参数怎么配。stress 的参数不多,但默认值有一堆隐含行为,不弄清楚就会压出“看起来很忙、实际没压到点子上”的数据。这一章按 CPU、内存、IO/磁盘三条线给出可直接抄的命令,每条后面说明关键参数的作用和调整方法。
4.1 CPU 压测:核数、错峰与超时怎么配
最简单的形态是让 worker 数量等于逻辑核数:
# --cpu 等于逻辑核数,--timeout 30 分钟,-v 输出每轮状态 stress --cpu "$(nproc)" --timeout 30m --verbose--cpu N里的 N 是 worker 进程数,不是核数。当 N 等于逻辑核数时,每个核上一个忙循环,top里 us 会接近 100%,load average 稳定在 N 附近。如果 N 超过核数,多出来的进程只能排队等调度,load 会继续升高但 us 不再涨,这种超配有时是想要的(验证调度公平性、验证 CPU 配额),有时只是白烧电。
判断压测是否真的压上去了,看三个地方而不是只看一个:
# 单独开窗口观测:r 列有排队、us 高、id 低 vmstat 1 30 # 看每个 stress worker 分到多少 CPU 时间,是否均匀 pidstat -p "$(pgrep -d, stress)" 1 10vmstat输出的第 13 到 16 列依次是 us、sy、id、wa。CPU 压测达标的样子是 us 高、id 接近 0、wa 不涨。如果 wa 反而涨了,说明压测期间有别的 IO 在干扰,这时候得出的 CPU 数据不可信。
需要渐进加压、观察系统在负载爬升过程中的表现时,用--backoff把 worker 错开:
# 8 个 worker,每个比前一个晚 200 毫秒启动,负载呈阶梯上升 stress --cpu 8 --backoff 200000 --timeout 10m -v--backoff的单位是微秒,所以 200 毫秒要写200000。这个参数对观察“负载爬升到什么程度开始出现调度延迟”特别有用。
注意:
--timeout一定要加。手动跑的压测,一旦中途去处理别的事情,worker 会一直跑下去,把整台机器的资源占满。加了超时,最坏情况也只是多跑一会儿。
4.2 内存压测:默认值是 256M,以及 --vm-keep 的必要性
内存这块的隐含行为最多,也最容易压出错误结论。默认情况下每个 vm worker 分配 256M,反复分配、写入、释放。这个循环会导致两个后果:一是 CPU 也被带着烧起来,二是内存占用在观测上呈锯齿状,看起来“没压满”。
# 2 个 worker,每个持住 1G,--vm-keep 让内存不被释放 stress --vm 2 --vm-bytes 1G --vm-keep --timeout 10m--vm-bytes 1G是每个 worker 的分配量,总量是它乘以--vm。这里最容易犯的错是按“总量”去理解,写了--vm 4 --vm-bytes 4G,实际是 16G,直接把机器压死。写参数时养成习惯:--vm乘以--vm-bytes才是总量。
--vm-keep决定内存是“持住”还是“反复分配释放”。不带它,worker 每轮都会 free 再 malloc,free 之后内存页归还,观测到的占用会来回波动,而且这套循环本身消耗大量 CPU。带它,worker 分配后一直持有,free -m的 available 会稳定下降,这才是真正在压内存水位。
还有一个参数--vm-hang N,作用是在分配完之后睡眠 N 秒再释放,用来模拟“内存被长期占用”的场景。它和--vm-keep的区别是:前者仍然会释放,只是延后;后者在进程存活期间一直持有。
压内存之前先看两个数:
# 可用内存和 swap 使用情况,决定 --vm-bytes 的上限 free -m # 当前 swap 倾向,0 表示尽量不使用 swap cat /proc/sys/vm/swappiness我的经验值是--vm乘以--vm-bytes不超过 available 的 70%。留 30% 不是保守,是因为页缓存、内核 slab、以及 sshd 之类的常驻进程都需要内存,压到 95% 以上时 OOM killer 一旦被触发,它挑谁下手是不确定的。
4.3 IO 与磁盘:sync() 和写文件是两件事
--io和--hdd常被混为一谈,实际机制完全不同。
# 4 个 worker 循环 sync(),制造全局回写压力 stress --io 4 --timeout 5m # 2 个 worker 各写 512M 临时文件,制造真实写吞吐 cd /data/scratch && stress --hdd 2 --hdd-bytes 512M --timeout 5m--io N的 worker 循环调用sync(),这是全局操作,把所有脏页刷到块设备。所以它的压力不随 N 线性增长,压的是“脏页回写这条路径”。适合验证文件系统在持续回刷下的元数据操作延迟。
--hdd N --hdd-bytes X才是真正的写吞吐压测。每个 worker 在当前工作目录下创建临时文件并持续写入,默认大小是 1G,--hdd-bytes可以改。这里有两个必须注意的点:一是工作目录,在/下跑就是把根分区往满里写;二是默认行为——worker 退出时会删掉临时文件,但被kill -9或机器断电打断时,残留文件会留在原地。
# 跑完检查有没有残留,stress 的临时文件名通常带 stress 前缀 ls -lh /data/scratch | grep -i stress--hdd-noclean会让 worker 退出时不删文件,一般只在你需要保留现场做分析时才加。默认别加,否则跑几次磁盘就满了,而且报错会以“No space left on device”的形式出现在完全无关的业务日志里,排查方向很容易跑偏。
磁盘压测的观测重点是 await 和 %util:
# -x 输出扩展统计,重点看 await(平均等待毫秒)和 %util iostat -x 1 20%util接近 100% 而await同时大幅上升,说明设备已经饱和;%util高但await平稳,多半是固态盘在并行处理,还有余量。这两个指标必须一起看,单看%util会误判。
4.4 组合压测的顺序与隔离
有人喜欢一条命令把四类负载全开:
# 不推荐:四类负载同时上,出问题无法归因 stress --cpu 4 --vm 2 --vm-bytes 1G --io 2 --hdd 1 --hdd-bytes 512M --timeout 10m这样压出来的数据几乎没法用。CPU 被内存 worker 抢走,磁盘 await 被 IO worker 的 sync 干扰,最后只能得出“机器很卡”这个结论。正确的做法是逐项隔离,每项单独跑一轮,每轮之间留出冷却时间让负载自然回落。
如果确实需要组合(比如验证“高 CPU 下的内存分配延迟”这种复合场景),用 cgroup 把压测进程框住,避免它把整机拖垮:
# systemd-run 起一个受限作用域:内存上限 2G、CPU 配额 200% systemd-run --scope -p MemoryLimit=2G -p CPUQuota=200% \ stress --cpu 2 --vm 1 --vm-bytes 1G --vm-keep --timeout 5mMemoryLimit让超出的部分触发 cgroup 级别的回收而不是全系统 OOM,CPUQuota=200%表示最多用满两个核。这样做的好处是压测进程被关在笼子里,即使参数写错了,受影响的也只是这个 scope,sshd 和监控 agent 还能正常响应。
5. 离线安装与压测的避坑记录:五个真实翻车现场
下面这五条都是我在内网机器上实际遇到过的,顺序大致按“装的时候”到“压的时候”排列。每条按现象、原因、解决三段写,遇到同类问题时可以直接对号入座,不用重新推一遍。
5.1 装完了却提示 command not found
现象:rpm -ivh全部成功,rpm -qa | grep stress也能查到包,但敲stress提示找不到命令。
原因:三种可能。一是二进制不在默认 PATH 里,源码编译到/usr/local/stress/bin时必然如此;二是装的是stress-ng,可执行文件名是stress-ng而不是stress;三是登录 shell 的环境变量被改过,PATH 里没有/usr/bin。
解决:先用绝对路径验证二进制是否存在,再定位 PATH 问题。
# 从 rpm 数据库直接查文件清单,不依赖 PATH rpm -ql "$(rpm -qa | grep -i '^stress')" | grep bin # 确认 PATH 内容,看 /usr/bin 在不在 echo "$PATH" | tr ':' '\n'绝对路径能跑通就说明是 PATH 问题,加软链或者写全路径都行。绝对路径也跑不通,说明装的根本不是你要的那个包。
5.2 rpm 报 Failed dependencies: libxxx.so
现象:rpm -ivh报一串Failed dependencies,全是libxxx.so.1()(64bit)之类的 so 文件名。
原因:依赖树没拉全。yumdownloader在某些源配置下只解析一层依赖,间接依赖会漏;另一种情况是拉的是另一个大版本的包,它链接的 so 版本和目标机上的不一致。
解决:不要逐个去网上找 so 文件硬凑,回到联网机重拉。先用rpm -qpR把所有包的依赖列出来,和rpm -qa的输出做差集,缺什么补什么。
# 目标机上导出已装包名,带回联网机做比对 rpm -qa --qf '%{NAME}\n' | sort > /tmp/installed.txt如果是 so 版本不一致(比如要libfoo.so.2但机器上只有libfoo.so.1),说明大版本对不上,这种情况下补包是补不出来的,只能换成对应大版本的 stress 包,或者走源码编译。
5.3 内存压测跑到一半 ssh 断了,机器失联
现象:--vm参数跑起来几分钟后,ssh 会话卡死,重新连也连不上,只能走带外管理重启。
原因:内存压到接近物理上限,触发 OOM killer,它按评分挑进程杀,sshd 或者关键系统进程被选中。另一种可能是压测把 swap 打满,系统进入持续的换页抖动,任何进程都难以获得 CPU 时间。
解决:压之前先算清楚上限,把总量压在 available 的 70% 以内,并且用 cgroup 兜底。
# 先看真实可用量 free -m | awk '/^Mem:/ {print "available:", $7 " MB"}' # 用 cgroup 限制,超限只影响 scope 内的进程 systemd-run --scope -p MemoryLimit=1G -p MemorySwapMax=0 \ stress --vm 1 --vm-bytes 900M --vm-keep --timeout 5mMemorySwapMax=0这一项很关键,它禁止这个 scope 使用 swap,把压力限制在物理内存范围内,避免整机进入换页。另外压测尽量通过nohup或systemd-run在后台起,不要挂在 ssh 会话下——会话断了进程收到 SIGHUP,压测中断在半路,数据就成了半截。
5.4 --hdd 把根分区写满,业务日志全部报错
现象:磁盘压测跑完之后,业务开始报No space left on device,df -h显示根分区 100%。
原因:--hdd在当前工作目录下写临时文件,如果执行时人在/或者/root,文件就落在根分区。默认大小 1G 乘上 worker 数,几轮下来空间就没了。更麻烦的是进程被强杀时不会清理,文件残留下来,df一直显示满的,但du找不到大文件——因为文件被删了但句柄还被占着,或者残留文件在隐蔽目录里。
解决:压测必须切到专用目录,最好是单独挂载的数据盘。
# 建专用目录并切进去,再跑压测 mkdir -p /data/scratch && cd /data/scratch stress --hdd 2 --hdd-bytes 512M --timeout 5m # 跑完清理残留 rm -f /data/scratch/stress.* 2>/dev/null如果已经出现df满而du找不到的情况,用lsof找被删但仍被占用的文件:
# 找出已删除但仍被进程持有的文件 lsof +L1 2>/dev/null | head -20找到进程后重启它或者让它重新打开日志文件,空间才会真正释放。
5.5 超时到了但负载没降,进程还在跑
现象:--timeout 5m明明到了,uptime里的 load 还是很高,ps一看 stress 进程还在。
原因:三种情况。一是用了--vm-hang,worker 分配完内存在睡眠,醒来才检查退出标志,会滞后;二是主进程被kill -9干掉,worker 变成孤儿继续跑;三是脚本里用了timeout命令又叠加了 stress 自己的--timeout,两者信号处理打架。
解决:先看进程树,再决定杀谁。
# 看 stress 主进程和 worker 的父子关系 ps -ef | grep -i '[s]tress' # 按名字整体清理,先 TERM 后 KILL pkill -TERM -f 'stress --' ; sleep 3 pkill -KILL -f 'stress --' 2>/dev/null || true清理顺序很重要:先发 TERM 让主进程有机会回收 worker,等几秒再上 KILL。直接 KILL 主进程会留下一堆孤儿 worker,它们还在烧 CPU,但已经没有父进程管理了,pkill -f按命令行匹配才能一网打尽。之后再看一眼 load,确认是在下降而不是继续爬。
6. 进阶:把压测包成带阈值的校验脚本
单次手动跑只能说明“这一刻压上去了”,交付要的是可重复、有判定标准的结论。我现在的做法是把采样和压测放进同一个脚本:压测在后台跑,采样器同步记录,结束后算出平均 CPU 使用率和最小可用内存,和阈值比对给出通过或不通过。
#!/usr/bin/env bash # 用法: ./verify.sh <持续秒数> <CPU worker 数> <内存 worker 数> set -euo pipefail DUR=${1:-60} CPUW=${2:-$(nproc)} VMW=${3:-1} OUT="/var/tmp/stress-$(date +%Y%m%d%H%M%S)" mkdir -p "$OUT" # 采样器:多采 5 秒,保证覆盖压测结束后的回落过程 vmstat 1 "$((DUR + 5))" > "$OUT/vmstat.log" & SAMPLER=$! stress --cpu "$CPUW" --vm "$VMW" --vm-bytes 512M --vm-keep \ --timeout "${DUR}s" -q & SPID=$! wait "$SPID" 2>/dev/null || true wait "$SAMPLER" 2>/dev/null || true # 跳过表头两行,算 CPU 空闲均值;us+sy 的反面就是 idle awk 'NR > 2 && $15 ~ /^[0-9]+$/ {idle += $15; n++} END {if (n > 0) printf "平均 CPU 使用率: %.1f%%\n", 100 - idle / n}' \ "$OUT/vmstat.log" # 压测期间的最小可用内存 awk 'NR > 2 && $4 ~ /^[0-9]+$/ {if (min == "" || $4 < min) min = $4} END {if (min != "") printf "最小空闲内存: %d MB\n", min / 1024}' \ "$OUT/vmstat.log" echo "采样明细: $OUT/vmstat.log"脚本里有两个地方值得说明。vmstat的列号是固定的:第 13 到 16 列依次是 us、sy、id、wa,所以取$15就是 idle 百分比,用 100 减掉就是实际占用。多采 5 秒是为了让日志覆盖压测退出后的回落段,方便确认“负载是真的降下来了”而不是采样恰好截断在高峰。wait后面跟|| true,是因为进程被信号终止时wait会返回非零,加了set -e的脚本会直接退出,后面的统计就白做了。
判定阈值我会写进同一份验收单:CPU 压测平均使用率不低于 85%、内存压测期间最小可用内存不低于 200 MB、全程 sshd 可响应。三项里任何一项不达标就重跑,而不是改阈值。这个习惯是被返工逼出来的——早年有一次为了“让数据好看”,把阈值从 85% 调到 70%,结果机器上线一周就因散热降频被投诉,回头看那份报告,数据是真的、结论是假的。
从那以后我每次交付压测数据,都强制走一遍:先-n试运行核对参数,再带--timeout和 cgroup 限制跑主流程,最后把vmstat原始日志和判定结果一起归档,谁复核都能自己算一遍。希望帮到你。
本文还有配套的精品资源,点击获取