4.7 Linux 压力测试实战
预计阅读时间:22 分钟
📖 目录
4.6:Linux 性能调优 性能调优中分析了 CPU、内存、磁盘 I/O 和网络的观测指标。但观测的前提是制造负载——压力测试让你在受控环境中验证系统瓶颈、评估容量上限和检验调优效果。
本文覆盖四种场景的压测工具:stress-ng(CPU/内存)、fio(磁盘 I/O)、sysbench(数据库)、wrk / hey(HTTP)。
学习目标
- 掌握 stress-ng、fio、sysbench、wrk/hey 四类压测工具的安装与核心用法
- 理解不同压测场景下关键指标(IOPS、QPS、P99 延迟)的含义与参考阈值
- 能够识别压测结果中的常见陷阱(page cache 误导、客户端瓶颈、OOM 等)
- 建立系统化的压测方法论:基线先行、单变量原则、逐步加压、多次取稳
前置知识
- 4.6:Linux 性能调优 Linux 系统性能调优——压测的目的是验证调优效果,先掌握观测指标
- 2.4:进程管理 进程管理——理解 CPU/内存压力下进程状态的变化
- 3.12:系统监控与告警 系统监控与告警——用 Prometheus/Grafana 采集压测期间的指标
- 3.4:Nginx Web服务器 Nginx Web 服务器——HTTP 压测需要目标服务(可选,也可用任意 Web 服务)
1. CPU 压力测试(stress-ng)
stress-ng 是 Linux 上最灵活的 CPU/内存压力生成工具,支持 200+ 种 stress 方法。
# 安装
sudo apt install stress-ng # Ubuntu / Debian
sudo dnf install stress-ng # RHEL / Fedora
# 生成 4 个 CPU 压力线程,持续 60 秒
stress-ng --cpu 4 --timeout 60s
# 混合模式:CPU + 内存 + I/O 同时压
stress-ng --cpu 2 --vm 2 --vm-bytes 1G --hdd 1 --timeout 120s
# 指定 CPU 方法(默认 random)
stress-ng --cpu 0 --cpu-method matrixprod --timeout 30s
stress-ng --cpu 0 --cpu-method all --cpu-method-list # 列出所有方法
观察负载:同时在另一个终端运行 htop 或 mpstat -P ALL 1 查看每核使用率,以及 uptime 观察 load average。
ansible all -i inventory -m shell -a 'stress-ng --cpu 4 --timeout 60s'内存压力
# 生成 2 个虚拟内存 worker,各占 512MB,持续 60 秒
stress-ng --vm 2 --vm-bytes 512M --timeout 60s
# 渐进式内存压测(每步 +256MB,触发 swap 行为)
for size in 256 512 768 1024 1536; do
stress-ng --vm 1 --vm-bytes ${size}M --timeout 10s
free -h | head -2
done
观察指标:free -h(内存使用)、vmstat 1(si/so 换入换出)、cat /proc/meminfo(详细内存状态)。
2. 磁盘 I/O 基准测试(fio)
fio 是事实上的磁盘性能测试标准,支持自定义 IO 深度、块大小、读写比例和延迟分布。
# 安装
sudo apt install fio
# 顺序读测试(块大小 1MB,队列深度 32)
fio --name=seq-read --ioengine=libaio --direct=1 --bs=1M \
--rw=read --iodepth=32 --size=4G --numjobs=1 \
--runtime=30 --time_based --group_reporting
# 随机写测试(块大小 4KB,队列深度 16)
fio --name=rand-write --ioengine=libaio --direct=1 --bs=4K \
--rw=randwrite --iodepth=16 --size=4G --numjobs=2 \
--runtime=30 --time_based --group_reporting
# 混合读写测试(70% 读 + 30% 写)
fio --name=mix --ioengine=libaio --direct=1 --bs=4K \
--rw=rw --rwmixread=70 --iodepth=32 --size=4G \
--runtime=60 --time_based --group_reporting
fio job 文件(可复用配置)
命令行参数多时推荐写成 job 文件,便于团队复用与评审:
# randwrite.ini
[global]
ioengine=libaio
direct=1
runtime=60
time_based=1
group_reporting
[randwrite-4k]
rw=randwrite
bs=4k
iodepth=32
size=4G
numjobs=2
# 运行
fio randwrite.ini
fio 输出关键字段:
| 字段 | 含义 | 参考值(SSD) |
|---|---|---|
| IOPS | 每秒 I/O 操作数 | 随机读 >50K / 随机写 >30K |
| BW(带宽) | 每秒传输量 | 顺序读 >500MB/s |
| lat(p99/p99.99) | 延迟百分位 | 随机读 <2ms(p99) |
--size 应大于物理内存的 2 倍,避免全部被 page cache 缓存导致结果虚高。--direct=1 绕过页缓存直接测试磁盘原始性能。3. 数据库基准测试(sysbench)
sysbench 支持对 CPU、内存、线程、互斥锁和 MySQL/PostgreSQL 做基准测试。
# 安装
sudo apt install sysbench
# CPU 基准
sysbench cpu run
# 内存基准
sysbench memory run
# MySQL 基准测试
# 先准备数据(建测试表 + 插入数据)
sysbench /usr/share/sysbench/oltp_read_write.lua \
--db-driver=mysql --mysql-user=root --mysql-password=yourpass \
--mysql-db=sbtest --table-size=1000000 prepare
# 运行混合读写负载(64 并发,持续 120 秒)
sysbench /usr/share/sysbench/oltp_read_write.lua \
--db-driver=mysql --mysql-user=root --mysql-password=yourpass \
--mysql-db=sbtest --table-size=1000000 \
--threads=64 --time=120 --report-interval=10 run
# 清理
sysbench /usr/share/sysbench/oltp_read_write.lua \
--db-driver=mysql --mysql-user=root --mysql-password=yourpass \
--mysql-db=sbtest cleanup
sysbench 输出关键字段:
| 字段 | 含义 |
|---|---|
| queries | 总查询数 / 每秒查询数(QPS) |
| transactions | 总事务数 / 每秒事务数(TPS) |
| latency(avg / 95th / max) | 平均、P95、最大延迟 |
| errors / reconnects | 错误数与重连数 |
4. HTTP 负载测试(wrk / hey)
wrk — 多线程 HTTP 压测
# 安装
sudo apt install wrk
# 基本用法:4 线程 64 连接,持续 30 秒
wrk -t4 -c64 -d30s http://localhost:8080/
# 带请求头的压测
wrk -t2 -c100 -d60s -H "Authorization: Bearer token123" \
-H "User-Agent: wrk-bench" http://localhost:8080/api
# 使用 Lua 脚本做 POST 压测
# 创建 post.lua:
# wrk.method = "POST"
# wrk.body = '{"key":"value"}'
# wrk.headers["Content-Type"] = "application/json"
wrk -t4 -c64 -d30s -s post.lua http://localhost:8080/api
hey — 更简洁的 HTTP 压测
# 安装(Go 编写,单二进制)
# 从 GitHub releases 下载或 go install
go install github.com/rakyll/hey@latest
# 基本用法
hey -n 10000 -c 50 http://localhost:8080/
# 带 body 的 POST
hey -n 5000 -c 20 -m POST \
-H "Content-Type: application/json" \
-d '{"name":"test"}' http://localhost:8080/api
# 输出包含:总请求数、成功/失败数、QPS、延迟分布(p50/p75/p90/p99/max)
wrk / hey 输出关键字段:
| 字段 | 含义 |
|---|---|
| Requests/sec(QPS) | 每秒请求数 |
| Latency Distribution | p50 / p75 / p90 / p99 / max 延迟 |
| Socket errors | connect / read / write / timeout 错误 |
| Non-2xx / 3xx responses | 非正常 HTTP 状态码 |
ulimit -n 65535 增大文件描述符、用 sysctl -w net.ipv4.ip_local_port_range="1024 65535" 扩大端口范围。5. 综合负载模式
生产环境中瓶颈往往多个维度叠加。模拟真实场景的复合压测:
#!/bin/bash
# 综合压测脚本:CPU + 内存 + 磁盘 + 网络同时施压
# 后台启动各维度压力
stress-ng --cpu 2 --vm 1 --vm-bytes 512M --hdd 1 & STRESS_PID=$!
# 同时跑 HTTP 压测
wrk -t2 -c20 -d120s http://localhost:3000/
# 停止 stress-ng
kill $STRESS_PID
wait
在综合压测时用 4.6:Linux 性能调优 的方法同时监控所有维度:htop(CPU/内存)、iostat -x 1(磁盘)、ss -s(网络连接)、vmstat 1(系统整体)。
浸泡测试(Soak Test)——查内存与连接泄漏
浸泡测试用中高负载(约为容量上限的 60-80%)持续运行数小时甚至数天,专门暴露短时压测看不到的问题:内存缓慢增长、连接泄漏、临时文件堆积、缓存键膨胀。
# 浸泡测试要点(以 8 小时为例)
# 1. 固定负载:wrk -t4 -c64 -d8h(长跑需关注客户端本身的稳定性)
# 2. 每 15 分钟采集一次进程内存,画趋势线
while true; do
ps -o rss= -p $(pgrep -f myapp) | awk '{printf "%s %s\n", strftime("%H:%M"), $1/1024}'
sleep 900
done > /tmp/soak-mem.log
# 3. 判读:内存随时间线性增长且不回落 = 泄漏;
# 内存增长到平台后稳定 = 缓存预热,属正常
# 连接数监测(连接泄漏的典型信号)
ss -s | grep -i estab
# estab 数量随时间单调增长 = 连接泄漏,检查应用是否忘记关闭连接
6. 压测方法论
压测类型速查
不同目标用不同类型的压测,名称混用是压测报告最常见的低级错误:
| 类型 | 目的 | 负载形态 | 典型时长 |
|---|---|---|---|
| 冒烟压测(Smoke) | 验证链路通、配置对 | 1-5 并发,跑通即止 | 1-5 分钟 |
| 基准压测(Baseline) | 建立对比基线 | 固定并发,固定脚本 | 10-30 分钟 |
| 负载压测(Load) | 验证日常峰值流量下的表现 | 按预估峰值并发施压 | 30-60 分钟 |
| 压力压测(Stress) | 找到容量上限与拐点 | 持续加压到系统过载 | 视拐点出现时间 |
| 浸泡测试(Soak) | 发现内存泄漏、连接泄漏 | 中高负载持续运行 | 数小时到数天 |
| 峰值压测(Spike) | 验证突发流量下的弹性 | 瞬间从低并发跳到 2-3 倍峰值 | 按尖峰窗口 |
上线前的标准组合是:冒烟 → 基准 → 负载(1 小时)→ 浸泡(至少 8 小时)。大多数团队只做前三种,漏掉浸泡测试——而内存泄漏这类问题恰恰只在长时间运行后暴露。
- 基线先行:永远先跑一次空载基线
- 单变量原则:每次只改变一个参数
- 多次取稳:每个场景跑 3 次取中位数(系统噪音不可消除)
- 逐步加压:从低并发逐步增加,记录拐点(系统从线性到饱和的转折点)
- 关注尾部延迟:avg 会骗人,p99/p99.9 才是用户体验的关键
- 数据持久化:每次压测结果保存到文件,标注环境配置和内核参数
完整流程:从基线到结论
以"给生产前的新版本 API 服务做容量评估"为例,跑通一遍完整流程:
- 基线先行:空载确认服务无异常日志、CPU idle 大于 95%,用
curl -w记录单请求延迟作为 0 并发参考。 - 单变量加压:固定请求体、固定脚本,只改变并发数。并发序列取 1、2、4、8、16、32、64、128、256(2 倍递增,每档持续 30 秒)。
- 多轮取稳:每个并发档位跑 3 轮,取 QPS 与 P99 的中位数,丢弃明显异常轮次(如恰好赶上 GC 的偶然抖动)。
- 记录拐点:把 (并发, QPS) 记成表格,找到 QPS 停止线性增长的并发点。
- 交叉验证:在 QPS 拐点处同时看服务端 CPU、内存、网络指标,确认瓶颈在哪一层(应用、DB、网络)。
| 并发 | QPS(3 轮中位数) | P99 延迟 | 服务端 CPU | 结论 |
|---|---|---|---|---|
| 1 | 180 | 8ms | 2% | 单请求延迟基线 |
| 8 | 1,400 | 12ms | 15% | 线性增长区间 |
| 32 | 5,200 | 28ms | 55% | 接近线性 |
| 64 | 6,100 | 96ms | 88% | 拐点出现,QPS 增速放缓 |
| 128 | 6,300 | 310ms | 97% | 平台期,P99 急剧恶化 |
结论:该服务容量上限约 6,000 QPS / 64 并发。P99 从 32 并发开始快速恶化,说明排队已经形成——生产应把流量控制在拐点(64 并发)的 60-70% 以下,即约 3,500-4,000 QPS,同时配好限流与熔断。
压测记录模板
每次压测至少记录以下信息,保证结果可复现、可对比:
日期:2025-06-15 14:00
工具:wrk -t4 -c64 -d30s --latency
目标:10.0.0.5:8080 /api/v1/ping(版本 v1.2.3)
环境:8C16G / NVMe SSD / 内核 6.1.0 / sysctl: net.core.somaxconn=4096
服务端负载:CPU 平均 55%,无 GC 异常
结果:QPS 6100,P99 96ms,Socket errors: 0
备注:对比 6-12 基线(QPS 5800),提升 5%
7. 结果解读:数据到底说明了什么
QPS 拐点
QPS 随并发增加呈现三段形态:线性区(系统资源充足,QPS 随并发近似线性上升)、拐点(某个资源先到 100%,QPS 增速放缓)、平台区(QPS 不再上升甚至下降——队列堆积导致请求超时重试,进一步恶化)。拐点所在并发数就是"该配置下的容量上限",平台区是过载区,压测时不要长时间停留。
CPU 饱和
CPU 到 95%+ 且 QPS 不再增长,说明瓶颈在 CPU(计算型负载);CPU 远未打满 QPS 却到平台,说明瓶颈在别处——典型情况是锁竞争、连接数耗尽、数据库慢查询、或压测客户端本身到顶。判断方法:用 mpstat -P ALL 1 看单核是否打满(多线程应用单核 100% 说明锁串行化),用 pidstat -t 看哪个线程占 CPU。
延迟分布
延迟看分布不看均值:
- p50/p75 低、p99 高:多数请求快,少量请求慢——典型的队列效应或 GC 停顿,检查线程池排队与 GC 日志
- p99 与 p50 接近:服务稳定,没有明显长尾,可以继续加压测试极限
- p99 随并发急剧恶化:连接池/线程池打满,请求在排队等资源,这是容量信号的第一个预警
- max 远大于 p99:存在极端偶发(GC、锁等待、网络抖动),结合
top观察峰值时刻的系统状态
# 典型延迟分布随并发升高的变化(单位 ms)
# p50 p75 p90 p99 max
# 并发 8: 8 11 14 28 60 ← 稳定区,长尾小
# 并发 32: 15 22 38 96 250 ← 拐点附近,p99 开始爬升
# 并发 128: 45 120 260 310 980 ← 过载区,p99 接近 max
# 解读:p99-p50 的"尾部差"从 20ms 扩大到 265ms,
# 说明排队等待成为主导因素——此时加机器比调代码更有效
8. 容量规划:从压测结果推算生产配置
容量规划的核心是利用率换算:压测得到单机容量上限后,按目标峰值流量、安全水位和冗余度反推机器数量。公式:
所需实例数 = 目标峰值 QPS × 冗余系数 ÷ (单机容量 × 安全水位)
# 示例:压测得单机容量 6,000 QPS(P99 < 100ms)
# 业务目标:峰值 25,000 QPS,双活机房各承担 50%
# 安全水位 60%(留出突发与故障转移空间),冗余系数 1.5(一备一用)
所需实例数 = 25,000 × 1.5 ÷ (6,000 × 0.6) ≈ 10.4 → 取 12 台,每机房 6 台
| 场景 | 安全水位 | 理由 |
|---|---|---|
| 在线业务(秒级响应) | 40-60% | 突发流量 + 单实例故障转移 |
| 离线批处理 | 70-85% | 可排队、可重试,容忍高峰 |
| 大促前的预留扩容 | 30% 以下 | 确定性高峰前的兜底余量 |
另外两个必须算的账:增长预算(月均流量增长 20% 时,容量至少要撑到下次扩容窗口)和故障预算(N+1 冗余只能扛一台故障,N+2 才能扛两台——按业务可用性目标决定)。
9. 在 CI 中集成压测
把压测放进流水线,防止"上线后才发现性能退化"。要点:压测环境与生产隔离、结果与基线对比、失败自动阻断。
GitHub Actions:每次 PR 跑 wrk 对比基线
# .github/workflows/perf.yml
name: Performance Regression Check
on:
pull_request:
branches: [main]
jobs:
benchmark:
runs-on: ubuntu-latest
services:
app:
image: myapp:pr-build
ports: ["8080:8080"]
steps:
- uses: actions/checkout@v4
- name: Install wrk
run: sudo apt-get install -y wrk
- name: Run benchmark
run: |
mkdir -p reports
wrk -t2 -c32 -d30s --latency http://localhost:8080/api/v1/ping \
> reports/current.txt 2>&1
- name: Compare with baseline
run: |
QPS=$(grep "Requests/sec" reports/current.txt | awk '{print $2}')
echo "Current QPS: $QPS"
# baseline 由上次 main 分支构建产物生成,存于 artifact
if [ "$QPS" -lt "${BASELINE_QPS:-0}" ]; then
echo "::error::QPS dropped below baseline"
exit 1
fi
Jenkins Pipeline:定时回归压测
pipeline {
agent { label 'perf-host' }
triggers { cron('0 2 * * 1') } // 每周一凌晨 2 点
stages {
stage('Deploy test env') {
steps { sh 'docker compose -f perf/docker-compose.yml up -d --build' }
}
stage('Run wrk') {
steps {
sh 'wrk -t4 -c64 -d60s --latency http://perf-app:8080/ > reports/$(date +%F).txt'
}
}
stage('Alert on regression') {
steps { sh 'perf/compare.sh reports/$(date +%F).txt' }
}
}
post { always { sh 'docker compose -f perf/docker-compose.yml down' } }
}
perf-host)、每次压测前先跑 CPU 空载校验、阈值放宽到基线的 ±10% 而非 ±2%。基线管理:让回归有参照物
CI 对比需要一个可靠的基线:把每次 main 分支合入后的压测结果写入仓库的 perf/baseline.json,回归任务只对比"当前 PR"与"最近一次基线",而不是与任意历史值比:
# perf/compare.sh —— 对比当前结果与基线,超标即失败
#!/bin/bash
# 用法:compare.sh 当前结果文件 [基线文件]
set -euo pipefail
CUR="$1"
BASE="${2:-perf/baseline.json}"
cur_qps=$(grep "Requests/sec" "$CUR" | awk '{print $2}')
base_qps=$(python3 -c "import json,sys; print(json.load(open('$BASE'))['qps'])")
# 阈值 ±10%:共享环境波动通常在这个范围内
limit=$(python3 -c "print(int($base_qps * 0.9))")
echo "current=$cur_qps baseline=$base_qps limit=$limit"
if [ "$cur_qps" -lt "$limit" ]; then
echo "::error::性能回归:QPS $cur_qps 低于基线 90%($limit)"
exit 1
fi
# 通过则更新基线(仅 main 分支执行,PR 只读不写)
if [ "${GITHUB_REF:-}" = "refs/heads/main" ]; then
python3 -c "import json; json.dump({'qps': $cur_qps, 'date': '$(date +%F)'}, open('$BASE','w'))"
git add perf/baseline.json && git commit -m "chore: 更新压测基线" && git push
fi
基线文件要随代码评审:谁改了基线、为什么改(服务重构?换机器?),都要在 commit message 里写清楚,否则基线的置信度会慢慢流失,回归检查形同虚设。
10. 压测工具选型对比
| 工具 | 目标层 | 特点 | 适合场景 |
|---|---|---|---|
| stress-ng | 系统(CPU/内存) | 200+ 压测方法,可组合 | 宿主机极限、稳定性测试、散热验证 |
| fio | 磁盘 | IO 深度/块大小/读写比例全可配 | 磁盘选型、RAID 配置、文件系统基准 |
| sysbench | 数据库 | OLTP 标准负载,可 A/B | MySQL/PostgreSQL 配置调优验证 |
| wrk | HTTP | 多线程 + Lua 脚本,高吞吐 | 单接口压力上限、Keep-Alive 长连接压测 |
| hey | HTTP | 单二进制、命令简单 | 快速冒烟压测、CI 轻量回归 |
| ab | HTTP | Apache 自带,全平台 | 临时性简单压测(非 Keep-Alive 场景,与 wrk 差距大) |
| ghz | gRPC | Go 编写,支持 protobuf 负载 | 微服务 gRPC 接口压测 |
| locust | HTTP(分布式) | Python 脚本、分布式施压 | 复杂用户行为流、多机联合压测 |
选择建议:单机快速验证用 wrk;需要模拟真实用户链路(登录→加购→下单)用 locust 写 Python 脚本;要压出数据库瓶颈直接 sysbench;日常巡检把 hey 塞进 CI 脚本最省事。
11. 压测报告模板:把数据变成决策
压测的产出不是一串数字,而是一份能让技术负责人、业务方快速判断"能不能上线、要不要加机器"的文档。先区分两种产出:记录是工具的原始输出,机器可读、可复现;报告是提炼后的结论,人可读、可决策。记录进基线库,报告走评审。
报告骨架
一份合格的压测报告包含以下章节,按读者关心程度从前往后排:
# 压测报告:API 服务 v1.2.3 容量评估
一、结论摘要(给决策者,一页内)
- 结论:可上线。容量上限 6,000 QPS,建议限流阈值 4,000 QPS
- 依据:5 轮压测中位数,见第四节
- 风险:P99 在 64 并发后恶化,建议扩容时同步加缓存
二、测试目标与范围
- 目的:评估 v1.2.3 较 v1.2.2 的性能回归与容量标定
- 范围:/api/v1/ping、/api/v1/order 只读路径,排除登录/支付链路
三、环境与基线
- 硬件:8C16G / NVMe SSD / 内核 6.1.0(与生产同规格)
- 软件:服务 v1.2.3,JVM 参数 -Xms8G -Xmx8G
- 基线:v1.2.2 同环境 QPS 5,800 / P99 92ms
四、结果摘要
- 见正文表:分场景列出 QPS、P99、错误率与瓶颈层
五、瓶颈分析
- 定位:CPU 88% 打满、锁竞争(mpstat 单核 100% + jstack AQS 排队佐证)
- 证据:压测期间日志、监控截图、原始输出链接
六、建议与遗留
- 短期:限流 4,000 QPS 并接入 Prometheus 告警
- 中期:连接池 200 → 300 后重测验证
- 遗留:登录链路尚未压测,排期 TICKET-1024
七、附录
- wrk 参数、3 轮原始数据、基线文件版本号
结果摘要表
| 场景 | 并发 | QPS | P99 | 错误率 | 结论 |
|---|---|---|---|---|---|
| 回归基准(与 v1.2.2 同参数) | 64 | 6,100 | 96ms | 0% | 通过(基线 5,800,+5%) |
| 日常峰值模拟 | 32 | 5,200 | 28ms | 0% | 通过(低于 SLO 100ms) |
| 压力上限 | 128 | 6,300 | 310ms | 0.2% | 过载区,不用于容量标定 |
| 浸泡 8 小时 | 64 | 6,050 | 98ms | 0% | 通过(内存曲线平稳) |
| 峰值尖峰(Spike) | 4→256 瞬时 | 6,000 | 180ms | 0.1% | 有损但可接受,限流兜底生效 |
报告误区
- 只贴原始输出:把 wrk 的 stdout 原样贴进周报,读者要自己翻字段——应该先给结论,原始数据放附录
- 不写环境:没有硬件/内核/配置信息的 QPS 数字没有任何对比价值,两个月后连自己都说不清当时压的什么
- 报喜不报忧:只写峰值 QPS,不写拐点前的 P99 恶化——上线的判断依据恰恰是尾部延迟
- 没有遗留项:一份没有"下步待办"的报告等于告诉读者"压测到此结束",无法驱动后续调优
- 数据造假式取整:只挑 3 轮里最好的一轮当结果——中位数才是稳定的代表值,单次最好值只说明"能跑到",不说明"稳定在"
归档与频率:重大变更、新版本、扩容前后必须出完整报告;日常每周跑一次基线对比,只更新结果摘要表。报告按 reports/2025-06/ 月度归档,回答"这台机器现在还行不行"时直接翻基线。
常见错误
| 问题 | 现象 | 解决方案 |
|---|---|---|
| fio 结果虚高 | 随机读 IOPS 远超硬件标称值 | 测试文件大小未超过物理内存 2 倍,数据全部落在 page cache 中。加 --direct=1 绕过缓存,或增大 --size 使其超过可用内存。 |
| wrk 连接超时 | 高并发下出现大量 Socket errors / timeout | 压测客户端自身资源耗尽。执行 ulimit -n 65535 增大文件描述符上限,sysctl -w net.ipv4.ip_local_port_range="1024 65535" 扩大临时端口范围。 |
| stress-ng OOM 被杀 | stress-ng 进程被 OOM killer 终止,dmesg 可见 oom-killer 日志 | 分配的内存超过系统可用量。降低 --vm-bytes,或加 --vm-keep 让 worker 保持已分配内存而非反复申请释放。 |
| sysbench 连接被拒 | FATAL: unable to connect to MySQL server | 数据库未启动、端口不对、用户密码错误、或 max_connections 不足。检查 MySQL 状态及 my.cnf 配置。 |
| 压测期间系统卡死 | 系统响应极慢甚至无法登录 | 压测负载过高导致系统过载。先用 stress-ng --cpu 1 --timeout 5s 小负载验证工具正常,再逐步加压;提前设置 timeout 或在远程终端预设 kill 命令。 |
| 压测结果波动大 | 同一参数多次结果差异超过 20% | 共享 Runner、超线程、散热降频、cgroup 限制都会引入噪音。固定专用压测机、关闭超线程、每次先跑空载校验、每场景 3 轮取中位数。 |
| P99 与基线差异大但 QPS 相同 | 延迟曲线整体漂移 | wrk 必须加 --latency 才有百分位输出;时长太短(<30s)时预热期占比过高。统一用 60s + --latency,先跑 10s 预热。 |
| 压测机先到顶 | 单机 wrk 压不出目标 QPS,客户端 CPU 先 100% | 施压能力不足。确认 ulimit 与端口范围后仍不够,就换多机分布式施压(locust 主从模式),客户端 CPU 超过 80% 即视为结果不可信。 |
最佳实践
- 压测环境与生产隔离:在独立的测试机器或容器中压测,避免干扰生产流量,也避免生产数据影响测试结果。
- 先建基线再对比:每次调优前后各跑一次相同参数的压测,用 A/B 对比验证效果,避免"调了感觉快了"的主观判断。
- 关注尾部延迟而非平均值:avg 会被大量低延迟请求拉低,p99 / p99.9 才反映最慢 1% 用户的真实体验。
- 记录完整环境信息:每次压测结果保存时标注内核版本、CPU 型号、磁盘型号、内核参数(sysctl)和负载情况,确保结果可复现。
- 逐步加压找拐点:从低并发开始,按 2 倍递增,观察 QPS 从线性增长转为平台期的拐点——那就是系统的容量上限。
- 压测也要告警:长时间浸泡测试时把服务端的 CPU/内存/连接数接入 Prometheus 告警,泄漏或 OOM 第一时间被发现,而不是等压测结束翻日志。
练习题
- fio 实操:分别测试你机器上 SSD 的顺序读、随机写、70/30 混合读写的 IOPS 和带宽。记录结果并对比参考阈值,判断磁盘性能是否正常。
- HTTP 压测对比:用 wrk 和 hey 对同一个本地 Web 服务发起 100 并发压测 30 秒,对比两者的 QPS 和 P99 延迟输出格式有何不同。
- 综合压测脚本:编写一个 shell 脚本,同时运行 stress-ng(2 CPU + 1 VM 512M)和 wrk(4 线程 32 连接 60 秒),压测结束后自动打印各维度的监控摘要(用 htop 批处理模式或 awk 解析 iostat 输出)。
- 容量规划计算:压测得单机容量 8,000 QPS,业务峰值 40,000 QPS,安全水位 60%,冗余系数 1.5,计算所需实例数并说明如何分摊到两个机房。
- 报告撰写:基于本章的完整流程做一次 30 分钟压测,按"11. 压测报告模板"的骨架产出报告(含结论摘要、结果表、瓶颈分析),给出"可上线/有条件通过/不通过"的结论。
- 基线回归演练:把 wrk 脚本和 compare.sh 放进 CI,人为改慢一个接口(如加
sleep 50ms),验证流水线能否在 PR 阶段阻断合并。 - 报告评审:找同事互换审查上一题的报告,重点核对:环境信息是否完整、结论与数据是否一致、遗留项是否可执行。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释压力测试的目的和关键指标 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成压力测试工具使用、测试脚本编写 | 在终端实际执行 |
| 原理掌握 | 能说出压力测试的并发模型和资源消耗原理 | 画出流程图 |
| 故障排查 | 能独立排查测试环境问题、测试结果异常、性能瓶颈 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要为压力测试配置监控和基线对比 | 对比不同方案 |
本章总结
压测的本质是"用受控的负载验证假设":stress-ng 看系统极限、fio 看磁盘能力、sysbench 看数据库吞吐、wrk/hey 看 Web 服务容量。四类工具对应四层栈,任何一层的瓶颈都会拖垮整体。记住压测的铁律:基线先行、单变量、逐步加压、看 P99 不看均值——没有基线的压测是自嗨,没有复现记录的压测无法指导容量规划。
延伸阅读
- 4.6:Linux 性能调优 Linux 系统性能调优(观测指标与方法论)
- 3.12:系统监控与告警 系统监控与告警(用 Prometheus + Grafana 验证压测效果)