wrk、ApacheBench 与 hey 轻量 HTTP 压测工具手册
先解释一次“压得很快,业务却没成功”
网关扩容评审会上,压测端报出 12 万 Requests/sec,应用成功计数却接近零。继续对时间线,入口状态码几乎全是 401:脚本漏带认证头,工具测到的是一条很短的拒绝路径。另一次测试把 hey -q 20 -c 10 当成总共 20 QPS,入口实际接近 200 QPS。数字都是真的,解释却是错的。
轻量 HTTP 工具的价值,是用很少的配置快速回答一个窄问题。ApacheBench(ab)用有限并发请求做冒烟和小基线;wrk让多个线程通过 epoll、kqueue 等事件通知机制维护大量 HTTP/1.x 连接;hey用并发 worker 发请求,还能显式启用 HTTP/2。它们都不是“真实用户模拟器”,也不会替团队证明业务成功、发压机未饱和或系统仍有容量余量。
涉及登录态、多步骤事务、动态关联、开放到达率、分布式发压或自动阈值判定时,应转向 k6、JMeter 或受治理的性能平台。继续给轻量 CLI 叠脚本,往往只会得到一个难以复现的私有测试框架。
第一次操作只对回环地址或已经书面授权的隔离环境执行。域名可访问不等于允许压测;共享环境还需要批准目标、来源、时间窗、方法、并发、目标 QPS、停止条件和负责人。
开始前建立实验单,至少写下:
experiment_id: http-baseline-001
target: https://perf.example.com/api/items
owner: <team-name>
approved_window: <EVENT_TIMESTAMP>/<EVENT_TIMESTAMP>
allowed_methods: [GET]
max_concurrency: 100
max_target_qps: 500
stop_when:
error_rate: "> 1% for 60s"
p99: "> 800ms for 60s"
database_connections: "> 80%"还要确认测试端与服务端时间同步,压测机 CPU、内存、网络、文件描述符和临时端口可以被观测。测试接口如果会写数据,应使用隔离租户、幂等请求和可删除数据;若无法证明可回滚,就只做只读冒烟。
初学者必须先区分四个量:
连接数是同时保持的 TCP/HTTP 连接规模,wrk 的 -c 表达这一层。并发 worker 或请求数是同时推进的工作单元,hey 的 -c 和 ab 的 -c 属于这一层,但实现并不相同。持续时间规定实验运行多久,不保证在这段时间里形成固定 QPS。
到达率规定每秒计划发出多少请求。原版 wrk 和 ab 没有原生目标到达率;hey 的 -q 又是每 worker 限速,不能直接当总 QPS。
wrk:从官方源码构建
wrk 的官方构建说明以类 UNIX 源码构建为主。容量基线不能跟随默认分支漂移,应同时固定官方 tag 和它指向的提交。下面以官方 4.2.0 tag 及提交 a211dd5a7050b1f9e8a9870b95513060e72ac4a0 为例:
set -eu
WRK_TAG='4.2.0'
WRK_COMMIT='a211dd5a7050b1f9e8a9870b95513060e72ac4a0'
git clone --depth 1 --branch "$WRK_TAG" --single-branch https://github.com/wg/wrk.git
cd wrk
test "$(git rev-parse HEAD)" = "$WRK_COMMIT"
make
./wrk --version
printf 'wrk source revision: tag=%s commit=%s\n' "$WRK_TAG" "$(git rev-parse HEAD)"git rev-parse HEAD 是源码身份,wrk --version 是二进制自报版本,两者都要进入实验记录。只记录后者无法区分同一版本字符串下的不同补丁或源码提交;tag 被异常移动时,test 会在构建前终止。升级 wrk 时先从官方 tag 列表选择目标,再更新 tag、完整 commit 和团队镜像中的构建记录,不要只改其中一个值。
项目会按构建脚本准备 LuaJIT 和 OpenSSL。需要使用系统库时显式指定路径,并把库版本、编译器和链接方式一并纳入基线:
make WITH_LUAJIT=/usr WITH_OPENSSL=/usrLinux 或 macOS 发行版可能提供 wrk 包,但包版本、补丁和 OpenSSL 链接方式由发行方决定。使用包管理器后仍应核对版本和 TLS 行为。Windows 开发机可使用 WSL 或隔离 Linux 发压机,不要把非官方移植版当成与官方构建等价的基线。
ApacheBench:安装 Apache 客户端工具
ab 属于 Apache HTTP Server 工具链。Debian/Ubuntu 常见包名是 apache2-utils,Fedora/RHEL 系常见包名是 httpd-tools;安装后必须以 ApacheBench 2.4 参数文档和本机帮助共同确认能力:
sudo apt-get update && sudo apt-get install apache2-utils
# Fedora/RHEL 使用:sudo dnf install httpd-tools
ab -V
ab -h团队镜像应固定发行版和包版本,而不是只记录“已安装 ab”。如果 HTTPS 测试需要客户端证书或指定密码套件,还要确认该构建链接的 TLS 库支持目标能力。
hey:使用官方二进制或 Homebrew
hey 的官方仓库给出 Linux、macOS、Windows amd64 二进制;macOS 还可使用:
brew install hey
hey -hhey 当前帮助中没有独立的版本输出参数。下载预编译文件时,将下载地址、release/tag 和 SHA-256 一起放入团队工具清单;使用 Homebrew 时记录 formula 锁定信息。不要用来源不明的同名包替代官方二进制,也不要从 User-Agent 字符串猜测 release 版本。
安装完成后,把三个工具放在同一台空闲压测机上只用于小规模对照。正式容量实验不要让多个发压进程相互争用 CPU 和网卡。
先固定共同请求
三个工具要比较同一个对象,必须固定 URL、方法、请求头、请求体、超时、重定向、压缩和连接复用。下面先使用无凭证的只读接口:
GET /health HTTP/1.1
Host: perf.example.com
Accept: application/json
X-Load-Test: http-baseline-001X-Load-Test 让入口日志和链路系统能够筛选实验流量,但它不是授权机制。目标侧仍要使用网络白名单、网关规则或专用环境限制来源。
wrk:线程、连接和持续时间
wrk -t4 -c100 -d30s --timeout 2s --latency \
-H "Accept: application/json" \
-H "X-Load-Test: http-baseline-001" \
https://perf.example.com/health-t4 是总工作线程数,-c100 是保持打开的总 HTTP 连接数,每个线程分摊约 connections / threads 条连接。-d30s 只规定测试时长;它不表示 30 秒内均匀到达,也不表示每秒 100 个请求。--timeout 2s 表示在指定时间内未收到响应就记录 timeout,--latency 打印详细延迟分布。
wrk 的连接通常在收到响应后继续发送下一次请求。这是闭环负载:服务变慢,单条连接下一次请求也会变晚,实际吞吐会自然下降。它适合回答“给定连接并发下系统能完成多少”,不适合单独回答“外部每秒固定到达 500 个请求时排队会怎样”。原版 wrk 没有 wrk2 的 -R 参数,不得混写。
只需固定方法、路径、请求头或请求体时,Lua 脚本开销通常较低;若每次请求动态拼装内容、执行复杂逻辑或启用 response() 回调,官方明确指出发压能力会下降。脚本化测试必须与静态请求基线分开记录。
ab:请求总数、并发和 Keep-Alive
ab -n 3000 -c 20 -k -s 2 \
-H "Accept: application/json" \
-H "X-Load-Test: http-baseline-001" \
https://perf.example.com/health-n 3000 是计划执行的总请求数,-c 20 是同时执行的请求数。-k 显式开启 HTTP Keep-Alive;ab 默认不启用,因此遗漏 -k 会把连接建立和 TLS 握手成本大量混入结果。-s 2 是 socket 超时秒数。
-t 30 表示最多运行 30 秒,并在内部隐含 -n 50000。它不是精确持续 30 秒的恒定速率模型,也不是不限请求数。动态页面响应长度会变化时,ab 默认可能将与首个成功响应长度不同的结果计为失败;只有确认长度变化是合法业务行为后才使用 -l,不能用它掩盖截断响应。
hey:worker、持续时间和每 worker QPS
hey -z 30s -c 10 -q 20 -t 2 \
-H "Accept: application/json" \
-H "X-Load-Test: http-baseline-001" \
https://perf.example.com/health-c 10 是 10 个并发 worker。-z 30s 到时停止,并忽略 -n。最需要警惕的是 -q 20:它表示每个 worker 每秒 20 次查询,所以本例配置的理论总目标上限约为 10 × 20 = 200 QPS,并非 20 QPS。实际值还会受到响应时间、超时、调度和发压机能力影响。
未设置 -z 时,-n 默认控制总请求数;总请求数不能小于并发 worker 数。-t 是每个请求的超时秒数,0 表示无限等待,容量实验不应使用无限超时。-cpus 控制使用的 CPU 核数,改变它意味着改变发压端配置,结果不能直接与旧基线混合。
协议、TLS 和连接行为不能混比
wrk 面向 HTTP 压测并依赖 OpenSSL 支持 HTTPS,但官方没有宣称 HTTP/2 或 HTTP/3 能力。ab 官方承认并未完整实现 HTTP/1.x,也不应当作 HTTP/2 工具。hey 可用 -h2 显式启用 HTTP/2:
hey -h2 -z 30s -c 10 -q 20 https://perf.example.com/healthHTTP/2 的 worker、请求并发和底层连接数不是同一个量,多路复用结果不能与 wrk/ab 的 HTTP/1.x 连接基线横向排名。ab 支持 -f 选择其构建可用的 TLS 协议、-Z 选择密码套件、-E 提供包含证书链和私钥的 PEM 客户端证书;wrk 和 hey 的基础 CLI 没有同等细粒度的 TLS 参数。报告必须记录最终协议、TLS 协商、Keep-Alive 和是否经代理,而不是只写 URL 为 HTTPS。
从工作单元推导连接压力
wrk 的线程是发压机执行单元,连接才是持续占用目标 socket、网关连接表和本机临时端口的对象。-t4 -c100 不是 400 条连接,而是 4 个事件循环共同管理 100 条连接;每条连接收到响应后继续推进请求。线程过少可能让一个事件循环先饱和,线程过多又会增加调度和统计合并成本,因此线程数应结合发压 CPU 核数建立基线,而不是与业务并发做一一映射。
ab 的 -c 表示同时推进的请求。未启用 -k 时,请求完成通常伴随连接关闭,1000 个请求可能制造接近 1000 次 TCP 建连;启用 Keep-Alive 后,同一并发槽可以在连接上连续发送请求。它的单机实现和官方文档列出的解析缺陷意味着高负载时可能先测到 ab 自身,所以服务端未饱和而客户端 CPU 已满时必须停止给服务端下容量结论。
hey 的 -c 是 worker 数,worker 通过 HTTP transport 获取和复用连接。HTTP/1.1 下并行请求通常需要多条可用连接;启用 -h2 后,多 worker 请求可以复用较少的 TCP/TLS 连接并在一个 HTTP/2 会话内多路复用。于是“10 workers”既不能直接换算为 10 条 TCP 连接,也不能与 wrk 的 -c10 宣称等价。诊断连接耗尽时,应同时观察客户端 socket、服务端 accept/active connection、HTTP 版本和每连接并发流。
先用一个只监听 127.0.0.1 的临时服务观察请求数和 TCP 连接数。脚本使用随机临时目录和随机端口,不接触项目目录;退出当前 shell 时只清理自己创建的目录和进程。需要 Node.js 18+、curl 和待验证的压测工具。
LAB_DIR=$(mktemp -d "${TMPDIR:-/tmp}/http-lightweight.XXXXXX")
LAB_PID=""
cleanup() {
test -z "${LAB_PID:-}" || kill "$LAB_PID" 2>/dev/null || true
test -z "${LAB_PID:-}" || wait "$LAB_PID" 2>/dev/null || true
case "$LAB_DIR" in "${TMPDIR:-/tmp}"/http-lightweight.*) rm -rf -- "$LAB_DIR";; esac
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
cat > "$LAB_DIR/server.mjs" <<'EOF'
import http from 'node:http';
const counters = { requests: 0, businessOk: 0, authRejected: 0, connections: 0 };
const sockets = new Set();
const server = http.createServer(async (req, res) => {
counters.requests++;
if (req.url === '/stats') {
const body = JSON.stringify({ ...counters, activeSockets: sockets.size });
res.writeHead(200, { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body) });
return res.end(body);
}
if (req.url === '/auth') {
counters.authRejected++;
return res.writeHead(401, { 'content-length': '0' }).end();
}
if (req.url.startsWith('/slow')) await new Promise(resolve => setTimeout(resolve, 200));
counters.businessOk++;
const body = 'ok\n';
res.writeHead(200, { 'content-type': 'text/plain', 'content-length': Buffer.byteLength(body) });
res.end(body);
});
server.on('connection', socket => {
counters.connections++;
sockets.add(socket);
socket.on('close', () => sockets.delete(socket));
});
server.listen(0, '127.0.0.1', () => console.log(`PORT=${server.address().port}`));
EOF
node "$LAB_DIR/server.mjs" > "$LAB_DIR/server.log" &
LAB_PID=$!
for _ in $(seq 1 50); do grep -q '^PORT=' "$LAB_DIR/server.log" && break; sleep 0.1; done
PORT=$(sed -n 's/^PORT=//p' "$LAB_DIR/server.log")
test -n "$PORT" || { cat "$LAB_DIR/server.log"; exit 1; }
BASE="http://127.0.0.1:$PORT"
curl -fsS "$BASE/stats"首次输出应类似 {"requests":1,"businessOk":0,"authRejected":0,"connections":1,"activeSockets":1}。数值允许因 curl 连接关闭时机略有差异,字段必须齐全。
正向实验:业务成功和连接复用同时成立
先让 ab 用 5 个并发工作单元完成 100 个 Keep-Alive 请求,再读取服务端计数:
ab -n 100 -c 5 -k "$BASE/ok"
curl -fsS "$BASE/stats"成功证据要同时出现:ab 的 Complete requests 为 100、Failed requests 为 0、Non-2xx responses 不出现,服务端 businessOk 增加 100。connections 通常只增加接近并发数的量,而不是增加 100,这才证明请求在少量 TCP 连接上复用。连接总数还包含两次 curl /stats,不要机械断言它一定等于 5。
然后去掉 -k,只改变连接复用变量:
curl -fsS "$BASE/stats"
ab -n 100 -c 5 "$BASE/ok"
curl -fsS "$BASE/stats"第二轮仍应成功 100 次,但 connections 接近增加 100。若把这两轮吞吐直接比较成“服务优化前后”,实际比较的是短连接与复用连接,TCP 建连、端口和可能的 TLS 握手成本已经变了。
反向实验:稳定制造三种误判
第一种误判是把 hey -q 当成全局限速。下面配置是每 worker 2 QPS、10 个 worker、持续 3 秒,理论上限约 60 次,不是 6 次:
hey -z 3s -c 10 -q 2 "$BASE/ok"
curl -fsS "$BASE/stats"调度和起停边界会让完成数略有浮动;若入口大约每秒收到 20 次,正好证明总目标近似 q × c。
第二种误判是把高吞吐错误页当业务容量:
wrk -t1 -c10 -d3s --latency "$BASE/auth"
curl -fsS "$BASE/stats"wrk 会报告大量 Non-2xx or 3xx responses,服务端 authRejected 增长而 businessOk 不增长。即使 Requests/sec 很高,也只能说明拒绝路径很快。
第三种误判是把并发当固定到达率:
wrk -t1 -c20 -d5s --latency "$BASE/slow"每个响应固定等待约 200ms;闭环模型中 20 条连接理论完成速率约为 20 / 0.2 = 100 QPS,实际值还包含调度开销。把延迟改大,完成 QPS 会自行下降,因为一条连接收到响应后才继续推进。这个结果不能推导“外部仍以 100 QPS 到达时队列也安全”。
完成实验后执行 exit 或让脚本所在 shell 正常结束,trap 会停止服务并校验临时目录前缀后清理。若中途复制命令到另一个 shell,应在原 shell 执行 cleanup;不要按进程名批量结束 Node 进程。
从工具输出还原证据
wrk 的 Requests/sec 是完成请求数除以实际运行时间;Socket errors 要按 connect/read/write/timeout 分类;Non-2xx or 3xx responses 是 HTTP 状态异常,不是 socket 异常。Latency 与 Req/Sec 的 Avg、Stdev、Max 是采样统计,启用 --latency 后再看详细分位数。
ab 的 Requests per second 同样是完成请求数除以总时间。它输出两个 Time per request:第一个包含并发因子,公式为 concurrency × time × 1000 / completed;第二个是 time × 1000 / completed。两者名称相同但含义不同,报告必须写清使用哪一个。Failed requests 与 Non-2xx responses 也要分开判断。
hey 默认输出总览、延迟分布、阶段耗时、状态码分布和错误分布。-o csv 可保留逐请求数据:
hey -n 1000 -c 20 -o csv https://perf.example.com/health > hey-result.csv进入隔离环境后,再对同一构建依次使用低、中、高三到四档并发,每档包含预热、稳定采样和恢复观察,每次只改变一个变量。同时记录实际完成 QPS、可用分位数、状态码与 socket 错误、发压机 CPU/网卡/文件描述符/临时端口,以及服务端入口、队列、连接池、CPU、GC 和下游饱和度。停止后确认业务计数、临时数据和服务指标恢复;只删除含凭证或敏感响应的工作副本,按制度保留脱敏审计报告。
把压测当作实验代码,而不是散落在聊天记录里的命令。建议项目保留:
performance/http-lightweight/
README.md
targets.example.yaml
scripts/
smoke.ps1
baseline.sh
wrk/
request.lua
evidence/
README.md真实目标与凭证不进入 Git。targets.example.yaml 只保留占位域名和上限模板;执行脚本从受控环境变量读取目标,并在运行前做 allowlist 校验。示意逻辑如下:
case "$TARGET_HOST" in
localhost|127.0.0.1|perf.example.com) ;;
*) echo "target is not allowlisted" >&2; exit 2 ;;
esac这段校验只是最后一道防误操作,不能替代网络层限制。更可靠的做法是让压测 runner 只能解析或访问隔离压测网段,由网关校验来源 IP、测试窗口和 X-Load-Test 标记。
每次实验建立不可变证据目录,至少包含:Git commit、应用镜像 digest、工具版本/校验值、完整脱敏命令、发压机规格、DNS/IP、协议、TLS、Keep-Alive、开始结束时间、原始输出、服务端指标快照和结论。没有这些上下文,两个 Requests/sec 数字不可比较。
轻量测试可以作为人工或受控流水线任务,但默认不应在每次提交时自动访问共享环境。流水线必须要求目标环境枚举值、审批和最大负载硬限制;用户输入的任意 URL 不得直接拼入命令。
固定请求体和方法
ab 使用文件发送 POST 或 PUT,并显式设置内容类型:
ab -n 100 -c 5 -p payload.json -T application/json \
https://perf.example.com/api/itemshey 可用 -m、-d 或 -D:
hey -n 100 -c 5 -m POST -D payload.json -T application/json \
https://perf.example.com/api/itemswrk 通过 Lua 脚本固定方法、body 和 header。若 body 每次动态生成,必须单独测量脚本开销,不能与静态 GET 基线比较。
区分短连接和复用连接
ab 默认短连接,使用 -k 开启 Keep-Alive;hey 默认复用连接,使用 -disable-keepalive 禁用。两种模式回答的问题不同:短连接会放大 TCP/TLS/入口 accept 成本,复用连接更接近多数现代客户端。报告必须把连接策略列为实验变量。
判断容量拐点
将并发逐档提高。如果 QPS 继续增长、尾延迟稳定且错误为零,说明尚未观察到当前链路瓶颈;如果 QPS 不再增长而 p99、排队或错误快速上升,容量拐点已经出现。随后要用服务端与发压端指标定位瓶颈,不能仅凭一张工具输出判断是应用、数据库还是网络。
交叉验证而不是工具竞速
ab 可用于低并发冒烟,wrk 可验证 HTTP/1.x 高连接吞吐,hey 可做 QPS 约束和 HTTP/2 对照。三者参数模型不同,合理用法是验证结论方向是否一致,不是把默认参数运行结果排成性能排行榜。
返回很高 QPS,但业务没有成功
吞吐很高,服务日志却主要是 301、401、403 或 404。 检查 wrk 的 Non-2xx or 3xx responses、ab 的 Non-2xx responses、hey 的状态码分布。 错误页比真实业务链路短,或工具没有携带正确 Host、认证和请求体。 先用单请求核对响应语义,再恢复低档负载。 业务成功计数应与压测端完成请求量大体一致。
提高并发后 QPS 不再增长
并发翻倍,QPS 持平,发压机单核或网卡已满。 同时查看发压端 CPU、网络、文件描述符、临时端口和 socket 错误。 生成器饱和、Lua 回调开销、TLS 成本或本机资源限制。 减少脚本逻辑,固定 CPU 与网络规格,分布到更多隔离发压节点。 增加发压能力后,若服务端 QPS继续上升,旧结论测到的是生成器上限。
ab 报大量长度失败
Failed requests 的 length 数量增加,但状态码正常。 抽样比较响应,确认是否属于时间戳、请求 ID 等合法动态长度。 ab 以首个成功文档长度为基准。 合法动态响应才使用 -l;截断或异常响应必须修服务。 同步检查响应内容和服务端错误,不能只看失败数归零。
hey 的实际 QPS 远高于预期
配置 -q 20 -c 10,入口接近 200 QPS。 计算 q × c,并查看真实完成速率。 把每 worker QPS 误解为全局 QPS。 根据目标总速率反算每 worker 值,先在隔离环境低档验证。 入口观测与 hey 完成速率应在允许误差内。
三个工具结果差异巨大
同一 URL 的吞吐和延迟无法对齐。 逐项核对 HTTP 版本、Keep-Alive、重定向、压缩、超时、并发含义、TLS 和网络位置。 比较的其实是不同请求或不同负载模型。 固定共同请求和连接行为,每次只改变一个变量。 先在低并发下确认状态码、body 和服务端请求量一致。
压测目标应使用显式 allowlist,而不是任意 URL 参数。建议同时实施四层控制:任务表中的授权目标、runner 的主机名枚举、网络出口 ACL、入口网关的来源与时间窗规则。DNS 解析结果也要记录,防止同一域名切到非预期环境。
代理会改变连接复用、TLS 终止、DNS、限流和缓存路径。ab 提供 -X 代理参数,hey 提供 -x host:port;wrk 基础 CLI 没有同等通用代理参数。经代理与直连是两种实验,不能混合统计。企业根证书、mTLS 和 SNI 失败时先用单请求客户端确认信任链,不要使用“跳过证书验证”换取一个看似成功的压测结果。
Basic Auth、Bearer Token、Cookie 和客户端私钥不得出现在仓库、终端录屏、进程列表或原始报告。ab 的 -A username:password 只是 Base64 传输,必须叠加 TLS;-E 使用的 PEM 还包含私钥,应由临时文件注入并在实验后安全删除。优先使用短期、只读、仅压测环境有效的凭证,并保留签发与撤销审计。
压测账号不能拥有管理、删除或跨租户权限。若写接口不可避免,目标侧应按测试标记和租户限制影响范围,并准备独立清理程序。客户端限速不是安全边界,服务端仍需设置熔断、限流和停止开关。
团队应把“谁都能运行一条命令”升级为受治理的性能实验:
性能负责人维护工具镜像、版本、脚本模板和证据格式;业务 owner 定义成功语义、测试数据和停止条件;平台 owner 提供隔离环境和观测。任何共享或生产近似环境测试都需要目标、窗口、上限和回滚审批。审批的是一组具体参数,不是长期有效的“允许压测”。基线使用固定发压机规格、网络位置和工具构建。变更任一条件时建立新基线,不覆盖旧数据。
报告同时给出吞吐、分位延迟、错误率、资源饱和度和业务正确性。平均响应时间不能作为唯一结论。脚本进入代码评审,重点审查目标 allowlist、请求副作用、凭证来源、最大并发、最大时长和停止逻辑。每季度演练停止开关、撤销凭证和测试数据清理;无人负责的脚本从 runner 移除。
工具选型也应制度化:单接口低并发冒烟可用 ab;HTTP/1.x 高吞吐连接基线优先 wrk;需要 Windows 入口、HTTP/2 或简单速率约束时使用 hey。需要开放到达率、复杂场景、阈值断言、分布式执行和趋势管理时,转向 k6、JMeter 或专业平台。
闭环负载会在系统变慢时自动“收油”
wrk、ab 和 hey 的并发执行都主要由有限工作单元等待响应再继续推进。响应变慢时,后续请求发出也变慢,这会降低实际到达率,掩盖开放流量下持续进入系统的排队压力。现象是尾延迟上升而完成 QPS 下降,看起来“系统自己限流了”。
判断时同时画出计划速率、实际入口速率、完成速率和在途请求。若目标是验证固定外部到达率下的排队与崩溃边界,应改用支持开放模型或恒定到达率的工具,不能从 -c 推导固定 RPS。
发压机饱和会伪造服务端容量上限
当并发增加但服务端 CPU、连接池和下游都未饱和,而发压机 CPU、网卡或 socket 错误已到顶,瓶颈在生成器。wrk 的逐请求 Lua 操作、ab 自身实现、hey 的 worker 调度和 TLS 都可能消耗客户端能力。
建立“发压能力空载基线”:对本机静态响应服务逐档测试,记录工具在相同协议和响应大小下的上限。正式实验中让发压端 CPU长期低于团队设定阈值,并用第二台发压机复验;新增发压节点后吞吐继续线性上升,说明此前不能宣称服务端已达容量上限。
协议差异会把工具比较变成错误结论
HTTP/1.1 多连接、HTTP/2 多路复用、短连接 TLS 握手和 Keep-Alive 长连接测量的是不同成本。hey 的 -h2 结果不能与 wrk 的 HTTP/1.x 默认结果直接比较;ab 未完整实现 HTTP/1.x,更不适合作为协议完整性裁判。
证据中固定并记录 ALPN/HTTP 版本、TLS 版本、密码套件、连接数、Keep-Alive、代理和重定向。性能结论写成“在指定协议与连接模型下”,不要写成“工具 A 比工具 B 快”。
缓存预热与测试数据会改变被测系统
重复请求同一 URL 可能命中 CDN、网关、应用缓存和数据库 Buffer Pool,得到的是热点读上限;每次生成随机参数又可能制造无界缓存项和高基数日志。写请求还会污染数据库、消息队列和搜索索引。
把冷启动、预热后稳定态和缓存失效场景拆成独立实验。每次记录数据集版本、缓存状态和预热过程;随机数据来自有界样本池,实验后按 experiment_id 清理并验证对象数量归零。
停止条件必须能脱离压测进程执行
只靠操作者看到终端后按 Ctrl+C 太慢,也无法处理终端断连。目标侧应有独立限流或流量开关,监控系统根据错误率、p99、连接池、队列和数据库压力触发人工或自动停止;runner 同时设置最大并发、最大 QPS 和硬超时。
停止后先保全证据,再撤销临时凭证、关闭入口规则、清理测试数据并确认指标恢复。若一次实验无法回答“谁能停、多久能停、停后如何证明恢复”,它还不具备进入共享环境的条件。
目标、来源、时间窗、方法、最大并发、最大 QPS 和停止条件已获授权。已区分连接数、并发 worker、持续时间与到达率。wrk 的 -c 被解释为总连接,未混用 wrk2 的 -R。
ab 的 -n、-c、-t、-k 和两项 Time per request 没有误读。hey 的 -q 按每 worker QPS 计算,-z 设置后不再依赖 -n。HTTP 版本、TLS、Keep-Alive、代理、重定向和压缩已固定并记录。
单请求已验证状态码、响应语义、Host、认证和测试标记。阶梯压测每次只改变一个变量,并包含预热、稳定采样和恢复观察。同时保存发压端、服务端和业务正确性证据,没有只看平均值或 Requests/sec。
已排除 CPU、网卡、文件描述符、临时端口、TLS 和脚本导致的生成器饱和。allowlist、网络 ACL、临时凭证和目标侧停止开关均已生效。原始命令、版本、镜像 digest、机器规格、时间、结果与结论可复现。
测试数据、临时规则和凭证已清理或撤销,服务指标已经恢复。
