hey 并发 Worker、QPS 限速与 HTTP/2 压测手册
-q 20 不是整个进程每秒二十次
hey 用并发 worker 发送 HTTP 请求,支持按时长运行、逐请求 CSV、连接复用开关、代理和显式 HTTP/2。最容易误读的参数是 -q:它表示每个 worker 每秒的查询上限。hey -q 20 -c 10 的理论总目标上限接近二百 QPS,不是二十。
这仍不是严格的开放到达率。worker 受响应时间、超时、Go 调度、连接池和发压机资源影响,实际入口速率可能低于 q × c。当目标变慢时,有限 worker 仍可能等待请求完成。hey 适合轻量速率约束和 HTTP/2 对照,不应替代需要精确到达率、复杂事务、分布式执行与自动门禁的性能平台。
安装时要保存二进制来源而不是猜版本
hey 官方仓库提供 Linux、macOS、Windows amd64 二进制入口,macOS 也可通过 Homebrew 安装:
brew install hey
hey -h当前帮助没有稳定的独立版本输出参数。不能从 User-Agent 字符串猜 release,也不能只记文件名。下载预编译文件时,保存仓库、下载 URL、对应 tag 或提交、SHA-256、操作系统与架构;使用 Homebrew 时保存 formula 解析结果和制品哈希。来源不明的同名二进制不进入团队 runner。
command -v hey
sha256sum "$(command -v hey)"
hey -h > hey-help.txt官方仓库当前没有像成熟发布平台那样为每个场景提供长期兼容承诺,因此更要把二进制和帮助文本放进实验基线。升级在独立 runner 镜像中完成,用 localhost 契约比较参数、默认值、HTTP/2、CSV 字段和退出码后再切换;回退恢复镜像 digest,而不是临时下载另一个文件。
-n、-z、-c 和 -q 共同决定运行
hey -z 30s -c 10 -q 20 -t 2 \
-H "Accept: application/json" \
-H "X-Load-Test: hey-baseline" \
https://perf.example.com/health-c 10 创建十个并发 worker。-z 30s 到时停止,并让 -n 失效。-q 20 给每个 worker 二十 QPS 的上限,因此理论总上限约为 10 × 20 = 200 QPS。-t 2 是单请求超时秒数;0 表示无限等待,容量实验不应让 worker 无界挂起。
未指定 -z 时,-n 控制总请求数,默认值来自当前帮助;请求总数不能小于 worker 数。-cpus 改变 Go 运行时可使用的 CPU 核数,也改变了发压端实验条件。旧结果使用两核,新结果使用八核,即使命令其他部分相同也不是同一基线。
| 参数 | 控制对象 | 验收时需要补充的事实 |
|---|---|---|
-c | 并发 worker 数 | 不直接等于 TCP 连接数 |
-q | 每个 worker 的 QPS 上限 | 总目标约为 q × c |
-z | 会话持续时间 | 设置后忽略 -n |
-n | 无 -z 时的总请求数 | 不能小于并发数 |
-t | 单请求超时秒数 | 零是无限等待 |
-cpus | 发压进程可使用的 CPU 核数 | 改变后建立新客户端基线 |
理论上限不是已完成速率。报告同时保存 hey 的完成统计、入口观测到的实际 QPS 和服务端业务成功率。三者不一致时,先解释差异,再谈容量。
worker、请求与连接不能画等号
hey 的 worker 通过 Go HTTP transport 获取和复用连接。HTTP/1.1 下,并行在途请求通常需要多条可用连接,但连接会随 Keep-Alive、服务端关闭、代理和错误变化。十个 worker 不能简单声明为十条永久 TCP 连接。
-disable-keepalive 禁止不同请求复用连接,会放大 TCP/TLS、客户端临时端口和服务端 accept 成本:
hey -n 1000 -c 20 https://perf.example.com/health
hey -n 1000 -c 20 -disable-keepalive https://perf.example.com/health这两轮不是工具性能复测,而是连接模型对照。服务端应同步记录新建连接、TLS handshake 与业务请求。关闭复用后 QPS 下降,可能完全来自握手与端口成本。
代理使用 -x host:port,同样改变连接池、DNS、TLS 终止、缓存和限流。直连与经代理的结果分开归档。-disable-compression 和 -disable-redirects 也改变请求路径与传输量,必须作为实验变量,而非“让结果更稳定”的隐藏开关。
-h2 改变的是协议和连接复用语义
hey 可显式启用 HTTP/2:
hey -h2 -z 30s -c 10 -q 20 https://perf.example.com/healthHTTP/2 允许多个请求流复用较少的 TCP/TLS 连接。此时 worker 数、在途请求数、stream 数和底层连接数分属不同层级。不能把 -h2 -c10 与 wrk -c10 的 HTTP/1.x 连接结果直接排名,也不能把请求多路复用自动解释为服务端效率更高。
对照前固定 URL、Host、请求头、body、TLS、代理、重定向和压缩,记录实际 ALPN/HTTP 版本与服务端连接/stream 指标。HTTP/2 结果更差时,可能是服务端每连接调度、流控、头压缩、网关实现或客户端本身;只看总耗时无法区分。
-h2 是强制选择实验入口,不应在目标不支持时用降级结果凑数。先用单请求客户端核对协商和响应语义,再启动负载。
方法、body 和认证先过单请求契约
hey 支持 -m 指定方法、-d 内联 body、-D 读取文件、-T 设置 Content-Type:
hey -n 100 -c 5 -m POST -D payload.json -T application/json \
-H "X-Load-Test: hey-write-baseline" \
https://perf.example.com/api/items请求进入压测前,先用单请求验证状态码、Content-Type、响应业务字段、Host、认证和测试标记。hey 输出状态码与错误分布,却不会理解订单是否真的创建、幂等是否成立或返回 JSON 是否代表业务成功。服务端按 experiment_id 计数和对账,才是业务语义的权威来源。
-a username:password 提供 Basic Auth,必须配合 TLS,且命令行仍可能泄露凭证。自定义 Authorization 或 Cookie 也会进入历史、录屏和进程信息。使用只对压测环境有效的短期只读凭证,由 runner 注入并在结束后撤销;原始 CSV、日志和错误输出不得保存敏感请求头。
写请求使用隔离租户、幂等键和有界数据集。随机参数来自有限样本池,避免制造无界缓存项、高基数日志和无法清理的业务对象。
本地实验让 q × c 变成可观察结果
下面的回环服务按相对秒记录请求量,不处理真实数据:
LAB="$(mktemp -d "${TMPDIR:-/tmp}/hey-lab-XXXXXXXX")"
chmod 700 "$LAB"
cat > "$LAB/server.mjs" <<'JS'
import http from 'node:http';
const started = Date.now();
const buckets = new Map();
let ok = 0;
const server = http.createServer((req, res) => {
if (req.url === '/stats') return res.end(JSON.stringify({ ok, buckets: Object.fromEntries(buckets) }));
const second = Math.floor((Date.now() - started) / 1000);
buckets.set(second, (buckets.get(second) ?? 0) + 1);
ok++;
res.writeHead(200, { 'content-length': '3' });
res.end('ok\n');
});
server.listen(0, '127.0.0.1', () => console.log(`PORT=${server.address().port}`));
JS
node "$LAB/server.mjs" > "$LAB/server.log" 2>&1 &
PID=$!
for _ in $(seq 1 50); do grep -q '^PORT=' "$LAB/server.log" && break; sleep 0.1; done
PORT="$(sed -n 's/^PORT=//p' "$LAB/server.log")"
BASE="http://127.0.0.1:$PORT"
hey -z 3s -c 10 -q 2 "$BASE/ok"
curl -fsS "$BASE/stats"配置是每个 worker 二 QPS、十个 worker,稳定秒桶应接近每秒二十次,总完成量大约六十,而不是六。启动和停止边界、调度和读取 stats 会造成浮动,因此门禁看稳定桶与数量级,不死记精确请求数。
再把 -c 改为五而保持 -q 2,稳定入口应接近每秒十次。若仍接近二十,说明运行的二进制、参数解析或观测窗口与预期不符。实验结束只清理自己持有的 PID 和随机目录:
hey -z 3s -c 5 -q 2 "$BASE/ok"
curl -fsS "$BASE/stats"
kill "$PID" 2>/dev/null || true
wait "$PID" 2>/dev/null || true
rm -rf -- "$LAB"这个实验验证参数语义,不证明生产目标能承受同样速率。进入共享环境仍要从低档阶梯上升并由独立停止机制保护。
CSV 让逐请求证据可复算
默认输出包含总览、延迟分布、阶段耗时、状态码和错误分布。-o csv 输出逐请求数据:
hey -n 1000 -c 20 -o csv https://perf.example.com/health > hey-result.csvCSV 适合复算分位、按时间观察启动阶段和关联服务端窗口,但文件也会扩大证据体积。先保存 help、完整脱敏命令、发压机时钟和工具哈希,再保存 CSV;缺少这些上下文,逐请求数据仍不可比较。
状态码分布必须和错误分布分开。快速 401、403 或 404 可能得到很高吞吐,却没有业务成功。服务端入口量、业务成功数与 hey 完成量大体对齐后,延迟分布才有解释价值。
阶段耗时能提示 DNS、connect、TLS、server processing 或 transfer,但具体字段受版本与协议影响。HTTP/2 多路复用下,逐请求 connect 或 TLS 时间也不能简单相加为服务端成本。报告应围绕同一请求路径与连接模型解释。
实际速率低于目标先查谁在等待
实际入口 QPS 低于 q × c 时,先看响应时间和 timeout。worker 若仍在等待前一个慢请求,可用工作单元不足;增加 worker 可能接近目标,也会增加并发与连接压力,已经不是只修正速率。
随后检查发压机 CPU、-cpus、网卡、文件描述符、临时端口、DNS、TLS 和代理。服务端资源没有饱和而发压机 CPU 已满,说明生成器限制了实际速率。用第二台同规格 runner 复验;总吞吐随生成器增加而上升,旧结果不能作为服务端容量上限。
实际速率高于团队预期时,先重算每 worker q × c。客户端限速也不是安全边界:调度边界、多个进程或多个 runner 都会叠加。平台必须在入口和网络层限制最大流量,并有独立停止开关。
从轻量基线退出到更合适的工具
hey 可以做单接口、固定请求、简单速率约束和 HTTP/2 对照。需要登录流程、动态关联、业务断言、开放到达率、分布式发压、自动 threshold、趋势保留或复杂数据分片时,不应继续堆 shell;转向 k6、JMeter 或组织批准的性能平台。
每次运行都从显式 allowlist 选择目标,批准方法、worker、每 worker QPS、理论总上限、时长、写入副作用、停止条件和 owner。runner 网络只允许隔离压测网段,入口按来源与测试标记限制;任意用户 URL 不能直接拼进命令。
结束后确认 hey 进程退出、临时代理或入口规则关闭、凭证撤销、测试数据清零、服务端指标恢复。证据保存二进制哈希、帮助文本、命令、发压机规格、协议、连接策略、原始输出或 CSV、入口实际速率、业务成功和资源饱和。条件变化后建立新基线,不覆盖旧结果。
版本升级不只比较总吞吐。固定小样本应覆盖 -z 覆盖 -n、q × c、超时、Keep-Alive 开关、重定向、压缩、代理、HTTP/2、状态码分布、错误分布和 CSV 列。默认值或字段变化会影响脚本和下游报表,即使二进制仍能启动也不能直接替换。实验镜像退出时同步撤销 runner 权限与旧下载入口,避免团队在不同机器上继续产生无法对齐的数据。
官方安装、参数和 HTTP/2 能力继续以 hey 仓库为准。需要高连接 HTTP/1.x 基线时进入 wrk 手册,需要低并发请求总量与 Keep-Alive 历史对照时进入 ApacheBench 手册。
