做性能测试这几年,我猜每个测过Linux服务器的人都用过或者至少听过stress这个名字。它就是用来给机器“上强度”的,让CPU满载、内存吃紧、磁盘写入不停,把系统拉到一个临界状态,然后看看它会怎么表现。很多测试团队拿它做稳定性验证、容量摸底、故障场景模拟,效果直接、配置简单,一条命令就能把某类资源压到极限。
这篇文章会把这几年用stress攒下的经验完整梳理一遍,从常用参数到混合压测场景,再到源码层面的解释。我会尽量把参数背后的原理讲清楚,也会把那些踩过的坑写在最后面。无论你是刚接触性能测试的萌新,还是准备搭建压测环境的开发、运维工程师,照着做基本不会跑偏。
1. stress工具到底是什么,为什么性能测试离不开它
1.1 一个压力生成器能解决什么问题
stress的全名是“A simple workload generator”,简单说就是一个可自定义的负载生成工具。它通过启动多个worker进程,用不同的计算方式消耗系统资源。你可以指定让多少个进程去死循环做浮点运算,也可以指定分配多少内存并持续读写,还可以让进程不断触发磁盘写入和sync。
它最常用的场景有这么几类:
- 配置验证:新上架的物理机、刚装好的云主机,先拉满CPU跑几分钟,看看散热降频、系统负载、内核日志有没有异常。
- 稳定性验证:让系统在较高负载下持续跑几小时,观察是否能保持稳定,不出现进程被杀、内存耗尽、IO卡死等问题。
- 容量摸底:在压测前先通过stress制造背景负载,再叠加真实业务请求,了解系统在资源争抢时的表现。
- 故障演练:主动让CPU过载、内存不足,观察监控告警是否触发、自动伸缩是否按预期扩缩容。
我经常跟团队里测试同学说的一句话是:stress不是用来刷性能数据的,它的核心价值在于把一个复杂的系统问题,简化成单变量问题。比如怀疑应用在CPU满载时超时,就用stress先把CPU打满,再跑一段业务流量;如果超时出现,问题就在CPU资源争抢,如果没出现,那就可以排除这个因素。
1.2 有stress-ng,为什么还要学stress
接触过系统压测的人多半也听说过stress-ng。stress-ng是stress的增强版,支持几百种stressors,还带了更强的统计报告能力。但我不建议新手一上来就抓stress-ng,原因有几个。
stress足够简单,简单到不容易出错。它的参数可控、行为可预测,做基准验证时不会产生莫名其妙的干扰因子。而stress-ng功能太多,很多人连参数都没吃透,压到一半发现行为跟预期不一样,排查起来反而麻烦。
另外,很多老系统的软件源里预装的就是stress,或者需要安装的是stress而不是stress-ng。比如某些企业内网的离线环境,迁移个包都要审批,能用系统自带源里现成的工具,就不要额外引入依赖。还有一个实际原因:你去看网上大量经典的性能测试文档,用的都是stress,照着实践更方便。
当然,如果你已经能够熟练使用stress做基础压测,后续再去接触stress-ng也不迟。我个人的建议是:先用stress把原理和场景吃透,再升级工具,这样哪怕换到任何一个压测工具,思路都是一样的。
2. 核心参数逐个拆解,记住这四个维度就够了
2.1 完整参数速查表
stress的参数其实不多,核心就围绕CPU、内存、IO、磁盘这四个维度。先看一张速查表,有需要的时候直接回来对着敲。
| 参数 | 作用 | 常用示例 |
|---|---|---|
| -c, --cpu N | 生成N个CPU worker,每个worker持续做浮点运算 | stress --cpu 4 |
| -m, --vm N | 生成N个内存worker,分配内存并持续读写 | stress --vm 2 |
| --vm-bytes B | 每个内存worker分配的内存大小,支持K/M/G单位 | stress --vm 2 --vm-bytes 512M |
| --vm-hang N | 内存worker在分配后暂停N秒,测试内存占用效果 | stress --vm 1 --vm-bytes 1G --vm-hang 60 |
| --vm-keep | 内存worker保持已分配的内存,不断释放再重新分配 | stress --vm 2 --vm-bytes 1G --vm-keep |
| -i, --io N | 生成N个IO worker,持续触发sync系统调用 | stress --io 4 |
| -d, --hdd N | 生成N个磁盘worker,持续写文件 | stress --hdd 3 |
| --hdd-bytes B | 每个磁盘worker写入的总字节数,默认1G | stress --hdd 1 --hdd-bytes 2G |
| -t, --timeout N | 压测运行N秒后自动退出 | stress --cpu 4 --timeout 60 |
| --backoff N | 启动worker时增加N微秒等待,错峰启动 | stress --cpu 4 --backoff 100 |
| -q, --quiet | 安静模式,只输出运行结果,不输出过程信息 | stress --cpu 4 -q |
| -v, --verbose | 详细模式,输出每一个worker的启动信息 | stress --cpu 4 -v |
这几个参数看着简单,但组合起来玩法很多。比如你想模拟一个“既吃CPU又吃内存”的服务,可以一条命令写多个参数同时生效。stress默认会为每个维度fork独立的进程,所以CPU和内存压力是并行的,不会互相阻塞。
2.2 CPU与内存参数的使用细节
先聊CPU。stress的CPU worker,其实就是让一个进程不断做浮点运算、循环计算平方根,把CPU的一个逻辑核心跑满。你指定--cpu 4,它会创建4个进程,如果有4个逻辑核,机器整体负载就会接近100%。
这里有个容易踩的点:如果机器有超线程,逻辑核是物理核的两倍,--cpu参数应该按逻辑核数量来评估。比如一台8核16线程的机器,想让CPU全核满载,得用--cpu 16。很多人在4核8线程的机器上跑--cpu 4,用top一看负载只有50%,误以为stress没压力,其实是worker数量没给够。
内存worker是另一个容易出问题的点。--vm N中的N是指worker进程数,每个worker默认会分配256MB内存,你可以用--vm-bytes改大小。比如--vm 2 --vm-bytes 1G,就是2个进程,每个分配1GB,总共占用2GB。
需要注意的是,stress分配内存后会做读写操作,但默认情况下,分配完内存后一段时间就会释放,然后重新分配。如果你希望内存一直保持占用状态,要加上--vm-keep。还记得有一次我做内存逐出测试,忘记加--vm-keep,内存占用率一直在周期性波动,监控图看起来像锯齿一样,后来才意识到是worker在分配-释放循环。
2.3 磁盘和IO参数的使用细节
--io参数的实现方式跟字面理解稍有不同。IO worker不是直接写文件,而是反复调用sync()系统调用,也就是把页面缓存里的数据刷到磁盘上。这种压力对磁盘写入和内核IO子系统都有一定负担,适合模拟“大量数据落盘”的场景,但不会产生实际文件占用。
如果你要模拟磁盘空间的持续写入,得用--hdd。hdd worker会创建文件,反复写入数据,并持续增大文件尺寸。默认每个worker会在当前目录创建一个临时文件,写到--hdd-bytes指定的大小后,会重置再继续写。比如--hdd 2 --hdd-bytes 1G,表示2个worker,每个分1GB写一遍,循环往复。
这两个参数很容易被搞混,我做一个对比:
- --io:触发sync系统调用,模拟缓存刷盘压力,对文件系统压力大。
- --hdd:实际写临时文件,模拟持续磁盘写入,对磁盘IO和文件系统都有压力。
实际压测中,如果我要模拟数据库频繁刷脏页的场景,一般用--io;如果要模拟日志系统持续写盘,会选--hdd,而且通常会把hdd worker的目录指到一个独立的挂载盘,避免把系统盘写满。
3. 从零开始的stress压测实操
3.1 环境准备与安全红线
动手之前先确认三件事:确认stress已安装、确认有足够磁盘空间、确认测试目标安全。
安装方面,大部分Linux发行版都支持包管理器直接安装。以CentOS/RHEL系为例,可以这样:
yum install stress -yDebian/Ubuntu系:
apt install stress -y如果没有外网,可以把编译好的二进制拷贝进内网,或者用离线仓库安装。stress依赖很少,编译安装也不费劲,后面源码章节会讲到它的构建方式。
安全红线这部分,建议每一条都记下来:
- 不要在生产环境的业务机器上随便跑stress。如果要跑,必须先跟团队确认维护窗口,并确保有监控、有退出机制。
- 内存worker的分配量不要超过物理内存,否则会触发OOM Killer,把系统关键进程杀掉。
- hdd任务要注意磁盘剩余空间,虽然默认文件大小1G,但如果目录写满,可能影响其他依赖该磁盘的服务。
- 建议始终带上--timeout,即使再熟练也不要裸跑,因为压测现场经常会被打断,一个失控的stress可能让机器长时间高负载。
3.2 单一资源压测的实操演练
先说最基础的CPU压测。我现在只测试一台8核16线程的机器,想确认全核跑满时系统负载是多少,可以用:
stress --cpu 16 --timeout 60 --verbose运行后,用另一个终端看top,你会看到16个进程占满CPU。同时看load average,正常会接近16。如果有打开vCPU监控面板,CPU使用率曲线应该是平的100%。
接下来是内存压测。测试机有32G内存,我想压到20G占用,可以这样:
stress --vm 5 --vm-bytes 4G --vm-keep --timeout 120这里5个worker,每个分配4G,共20G,同时开启--vm-keep让内存持续占用。配合free -h观察Memused的变化,会发现内存占用迅速上升并稳定在20G左右。期间我还习惯打开dmesg看一下,确认没有触发OOM相关的内核日志。
磁盘写入压测,建议先切换到目标磁盘的目录再执行:
cd /data/test stress --hdd 4 --hdd-bytes 2G --timeout 60这会在/data/test目录下创建临时文件,4个worker同时写,文件总量最终约8G。配合iostat -x 1观察磁盘的写吞吐量和util%指标,如果util接近100%,说明磁盘已经被打到高水位。
3.3 混合场景与持续时长控制
单维度压测只是热身,真实业务往往同时消耗多种资源。比如一个典型的Web服务,既要处理大量请求占CPU,又要缓存数据占内存。想模拟这种混合负载,stress可以一条命令并行压多个维度。
stress --cpu 8 --vm 2 --vm-bytes 2G --hdd 2 --hdd-bytes 1G --timeout 300这条命令同时在8核CPU上跑浮点运算、用4G内存做读写、向磁盘连续写2G数据。跑起来以后,可以用top按CPU排序,你会看到CPU worker进程各自占满一个核,而vm和hdd进程在特定资源上发起压力。
持续时长的控制,一般建议先做短时间快速验证,再逐步加长。我通常这样安排:
- 快速冒烟:60秒,确认压测命令没报错,资源用量符合预期。
- 标准验证:10到30分钟,观察负载是否稳定、日志是否有异常。
- 长时稳定性:4小时甚至更久,主要看有没有内存泄漏、磁盘碎片化或者内核软锁定这类长时间才会暴露的问题。
时间越长,越要留意机器散热和性能降频。我经历过一台服务器压测到第20分钟CPU频率明显下降,导致性能数据一路走低。这种情况不是stress的问题,而是硬件的散热策略导致CPU主动降频,需要配合频率指标判断,否则很容易误判成系统性能有问题。
3.4 配合监控工具解读压测数据
stress本身只负责制造压力,不负责记录指标,所以要回答“机器到底扛不扛得住”这个问题,必须配合监控工具观察系统状态。我常用的是这么几组:
- top:看CPU占用、负载、内存总量,首选快速判断。
- mpstat -P ALL 1:看每个CPU核心的使用率,确认压力是否均匀分布在所有核上。
- vmstat 1:看系统的运行队列、内存换页、IO等待状态。
- iostat -x 1:看磁盘的每秒读写字节数、util利用率、等待队列长度。
- dmesg:观察内核日志,排查死锁、OOM、硬件报错等。
举个例子,压CPU时运行mpstat,如果出现某个核心100%,其他核心空闲,说明你的worker数量配少了或者进程被绑核了。压内存时看vmstat,如果si和so两列持续不为0,说明内存已经不足,开始频繁换页,测试结果就会掺杂很多干扰因素。压磁盘时看util,util长期90%以上但吞吐不高,则大概率是IO队列堆积,而不是磁盘真正的高吞吐能力不足。
4. 源码说明:追到源码层面理解stress
4.1 源码结构和构建方式
stress的源码是用C写的,整体结构非常精简,很适合作为理解“压力工具工作原理”的入门材料。官方源码包里主要是两个文件:stress.c和stress.h,另外带一个Makefile。
构建方式很简单:
tar -zxvf stress-1.0.4.tar.gz cd stress-1.0.4 make编译完成后,当前目录会生成stress可执行文件。因为依赖很少,这个工具在绝大多数Unix-like系统上都能直接编译通过,这也是很多嵌入式测试团队选它做压力脚本的原因。
从代码结构来看,它经历了这么几个阶段:
- 解析命令行参数,确认要启动哪些维度的worker、各自的数量和配置。
- fork子进程,每个子进程进入对应维度的worker循环。
- 父进程监控子进程状态,处理信号和超时退出。
- 所有worker退出后,父进程汇总统计信息并打印结果。
4.2 main函数怎么把参数变成进程
看源码会先注意到main函数里那一大串getopt相关逻辑。它从argc/argv里循环读取选项,碰到'c'就把--cpu的参数存到forkcpu变量,碰到'm'存到forkvm,碰到'd'存到forkhdd,碰到'i'存到forkio,碰到't'存到timeout。这个过程没有什么神秘之处,关键在参数解析完后,程序会基于这些变量开始fork。
代码的大致逻辑是:
while ((ch = getopt(argc, argv, "c:m:d:i:t:...")) != -1) { switch (ch) { case 'c': forkcpu = atoi(optarg); break; case 'm': forkvm = atoi(optarg); break; ... } } /* 派发CPU worker */ for (i = 0; i < forkcpu; i++) { if (fork() == 0) { stress_cpu(); exit(0); } }这里值得注意的一点是:stress对每个维度分别fork若干进程,而不是在同一个进程里跑线程。这种设计有意避免了线程间的同步和资源共享,让每个worker独立占用资源,行为更接近真实多进程应用的资源消耗。代价是进程数量多,但对压力测试来说完全不是问题。
父进程fork完所有worker后,会进入一个wait循环,持续回收子进程的退出状态。超时退出的逻辑也比较直接:主进程里有一个alarm定时器,到时间后向自身发送SIGALRM信号,信号处理函数里把所有worker进程杀死,然后统一回收。
4.3 四大worker的核心实现
源码里最值得读的部分,其实是每个worker内部的负载循环。它们不依赖外部库,就是纯计算、纯内存操作、纯系统调用,代码量都不大,却能达到稳定的压力效果。
先看CPU worker,它干的事情就是一个死循环里反复做浮点运算:
void stress_cpu(void) { double x = 1.0; while (1) { x = sqrt(x * x + 1.0); } }如果熟悉CPU指令集,会发现这个循环里大量使用浮点运算指令,能让CPU的浮点单元长时间处于繁忙状态。配合多个进程同时跑,多个核心很快被打满。虽然这跟真实应用的计算模式有差异,但用来考察CPU散热、频率策略、系统调度已经足够了。
内存worker的关键是分配内存并持续访问。它主要做了这么几步:按--vm-bytes的大小malloc一块内存,然后循环读取和写入这块内存;如果指定了--vm-keep,就保持该内存不释放,一直执行读写循环;如果没指定,则分配、访问一段时间后释放,然后重来。
void stress_mem(void) { while (1) { buf = malloc(bytes); memset(buf, 0, bytes); /* 循环读写 */ free(buf); } }这个逻辑对应着内存带宽占用和内存容量占用两种效果。无论哪种,只要worker数量够多,系统的内存子系统就会被持续拉动。
IO worker的实现跟我前面说的一样,核心是循环调用sync():
void stress_io(void) { while (1) { sync(); } }别小看这个简单调用,sync会把页缓存中的脏数据刷到磁盘,大量进程同时执行时,会让IO请求队列长期有任务,达到制造IO压力的效果。
磁盘worker则相对复杂一点。它会创建临时文件,然后循环做lseek和write操作。每个周期写一小块数据,直到文件大小达到--hdd-bytes,再重置偏移继续写。这种“随机偏移+顺序写”的组合,能同时触发磁盘寻道和写入,比单纯大文件连续写在模拟真实负载时更接近应用行为。源码核心类似:
void stress_hdd(void) { fd = open(temp_file, O_CREAT | O_RDWR, 0600); while (1) { lseek(fd, 0, SEEK_SET); while (written < bytes) { write(fd, buf, bufsize); stats->bytes_written += bufsize; } ... } }4.4 从源码看stress的局限
读源码能更清醒地认识stress的工具边界。它的设计目标是简单可控,所以做不了精细的带宽控制。
一个重要局限是:stress不会限制资源使用的速率。一旦启动,它就会尽可能多地占用资源,直到系统达到上限。这意味着你不能用stress模拟“每秒50% CPU”这种低速负载,做不到阶梯式调整。
另一个局限在于,stress使用的负载模型相当原始。CPU worker只做浮点运算,代表的是一类计算密集型任务;IO worker只做sync,写磁盘也用的是顺序写模型。真实业务负载的种类远比这复杂。所以很多团队在压测应用时,会用stress做背景噪声,再搭配真实业务流量做混合压测,而不是纯靠stress评估应用性能。
还有一个实际痛点:stress的进程管理比较粗放,每个worker都是独立进程,没有亲和性设置,也没有动态伸缩能力。这意味着你没法在压测过程中平滑地“增加两个worker”或者“只把CPU压力降一半”。要变化只能停止任务换参数重启。
5. 实战踩坑记录与问题排查速查
5.1 我踩过的典型事故现场
第一个坑是内存参数太大直接触发OOM。有一次我在一台只有4G内存的云主机上执行了stress --vm 4 --vm-bytes 2G,理想中应该分配8G,但物理机只有4G,系统瞬间进入OOM状态,连ssh都掉线了。重启后才恢复。从那以后,我养成了一个习惯:压测前先free -h看可用内存,再计算总内存占用是否超限。
第二个坑是忘记在后台运行stress,导致整个测试会话被卡住。ssh连接一旦断开,stress随之被终止,白跑一场。正确做法是结合nohup或者tmux运行,并且通过日志文件确认结果:
nohup stress --cpu 8 --timeout 3600 > /var/log/stress_test.log 2>&1 &第三个坑是hdd临时文件没有清理干净。stress的hdd worker会在指定目录留下临时文件,如果中途强制kill,文件可能残留,悄悄占掉磁盘空间。跑完记得查看目录大小,手动删除残留临时文件。
第四个坑把stress跑在了服务所在机器上,没有设置timeout。虽然是测试环境,但该机器上还有一个共享数据库实例,结果数据库响应开始变慢,别人找我排查才发现是stress占满了资源。这次之后,团队定了个规矩:所有压测命令必须带timeout,且必须在申请好的测试机或容器里执行,不能随手找台机器就开跑。
5.2 问题排查速查表
把我在实际工作中遇到的高频问题整理成了表格,遇到症状可以直接对照处理。
| 症状 | 可能原因 | 解决思路 |
|---|---|---|
| CPU使用率达不到100% | --cpu数量小于逻辑核心数 | 按nproc结果重新设置worker数量 |
| CPU核心使用率不均 | 部分worker被内核调度到同一核心 | 绑定numactl或taskset后再次尝试,或增加worker数量 |
| 内存占用远低于预期 | 没有加--vm-keep,内存在持续释放重分配 | 补上--vm-keep参数 |
| 内存占用高于预期导致系统无响应 | --vm-bytes总分配超过物理内存 | 立即kill stress,释放内存,按物理内存重新规划 |
| 磁盘容量迅速变小 | hdd临时文件写满目标目录 | 检查目录,清掉临时文件,限制--hdd-bytes |
| 压测过程中系统负载飙高但业务无明显影响 | 压测强度过低或压测资源与业务不重叠 | 确认目标资源维度再调整参数 |
| 压测结束后load average长时间居高 | 大量内核线程仍在回收/刷盘 | 观察IO指标,等待几分钟后再评估,必要时sync清理缓存 |
| 中途ssh断开导致压测停止 | stress运行在前台,随会话终止 | 使用nohup或tmux把压测任务放入后台 |
5.3 几个提升效率的小习惯
最后分享几个自己的操作习惯。
第一个是压测前先把基线数据打底。跑stress前,先记录空闲状态下的CPU频率、内存可用量、磁盘IOPS、负载值。压测过程中对比这些基线数据,能更精确地判断压力是否真的加上去了。没有基线,跑出来的“满载”可能只是你想象的满载。
第二个是尽可能用脚本封装压测命令。比如写一个简单的shell脚本,接受worker数量和时长两个参数,自动记录开始时间、调用stress、结束后把dmesg和top采样存到文件里。这样一来可以避免手动敲错参数,二来测试过程可以重复执行,也方便团队其他人复用。
#!/bin/bash CPU_WORKERS=16 TIMEOUT=300 LOG_DIR=/data/stress_logs mkdir -p "$LOG_DIR" echo "[$(date)] start stress cpu=$CPU_WORKERS timeout=$TIMEOUT" >> "$LOG_DIR/run.log" nohup stress --cpu "$CPU_WORKERS" --timeout "$TIMEOUT" --verbose > "$LOG_DIR/stress_$(date +%s).log" 2>&1 &第三个是压测结束后的检查流程。不要看到stress退出就认为测试完成,至少要看三样东西:dmesg里有没有OOM或硬件报错、top里的负载是否已回落、目标磁盘的临时文件是否清理。这三样都正常,才敢说一次压测真正有效。
第四个是组合参数的顺序问题。如果同时压CPU和内存,建议把内存参数放在前面,让内存先分配到位,再叠加CPU计算压力。这主要是为了让测试初始阶段的内存波动可控,不至于在CPU已经满载时,内存worker还在频繁malloc/free,产生额外的调度干扰。
结尾:关于stress使用的最后一点心得
用了stress好几年,回过头来看,它其实不是一个“强大”的工具,而是一个“正确”的工具。它用最简单的设计解决了压测中最常见的四类资源问题,不掺入多余功能,这让它的行为极其容易被预判。性能测试里最怕的就是工具本身成为变量,而stress在这一点上几乎不会给你添乱。
如果让我给第一次用stress的人一句建议,那就是先理解你想让系统呈现什么状态,再决定参数,不要盲目复制命令。是想要CPU满核、内存高占用、磁盘持续写,还是所有资源一起争抢,这个意图不同,参数组合就完全不同。另外,始终保持敬畏心,压测是一项有破坏力的操作,越熟练越要控制风险。
以后如果你从stress迁移到stress-ng,就会发现这套理解框架完全通用。所有压力测试工具本质上都在做同一件事:制造可预测的资源竞争环境,帮你在系统真正出问题之前,把问题找出来。