wrk —— 高并发 HTTP 压测
wrk —— 高并发 HTTP 压测
分类:10 压测与基准 适用:Linux / macOS(wrk 4.1 / 4.2,需编译或包管理器) 一句话:多线程 + epoll,单机就能压出几十万 QPS,比 ab 强一个量级。
1. 它解决什么问题
- ab 在多核机器上压不上去(单线程瓶颈)。
- 需要精确的延迟分布(P50/P90/P99)。
- 要模拟长连接、自定义请求、动态参数(Lua 脚本)。
- 做容量评估与调优前后对比。
2. 安装
# Debian/Ubuntu
apt install -y wrk
# RHEL 系通常没有,需编译
dnf install -y git gcc make openssl-devel
git clone https://github.com/wg/wrk.git
cd wrk && make -j"$(nproc)"
cp wrk /usr/local/bin/
# macOS
brew install wrk
3. 基本用法
wrk -t4 -c100 -d30s http://127.0.0.1:8080/health
| 参数 | 含义 | 建议 |
|---|---|---|
-t |
线程数 | 等于 CPU 核数 |
-c |
并发连接数 | 从 100 起,逐步加 |
-d |
持续时间 | 至少 30s,稳定后才准 |
-s |
Lua 脚本 | 自定义请求 |
-H |
添加请求头 | 可多次 |
--latency |
打印完整延迟分布 | 必加 |
--timeout |
请求超时 | 默认 2s |
--script |
同 -s |
wrk -t8 -c1000 -d60s --latency --timeout 5s http://target/api
4. 结果怎么读
Running 30s test @ http://127.0.0.1:8080/health
4 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 12.34ms 5.67ms 120.00ms 85.23%
Req/Sec 2.05k 234.56 3.12k 72.11%
Latency Distribution
50% 11.00ms
75% 14.00ms
90% 18.00ms
99% 35.00ms
245678 requests in 30.10s, 45.67MB read
Requests/sec: 8162.34
Transfer/sec: 1.52MB
| 指标 | 含义 |
|---|---|
Latency |
单请求延迟(含排队,并发越高越大) |
Req/Sec |
每个线程每秒完成请求数 |
Latency Distribution |
百分位延迟,--latency 才有 |
Requests/sec |
总吞吐(= 各线程 Req/Sec 之和) |
Stdev |
标准差,越大越不稳定 |
+/- Stdev |
落在均值 ±1 标准差内的比例,越高越稳定 |
判读要点:
- QPS 上不去且
Latency稳定 → 服务端处理能力到顶。 - QPS 上不去且
Latency抖动大 → 连接数过多、GC、锁竞争、后端依赖慢。 Req/Sec各线程严重不均 → 负载不均或某核被抢占。- 出现
Socket errors→ 连接被拒/超时,先解决再谈性能。
5. Lua 脚本(wrk 的核心优势)
POST JSON
-- post.lua
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"userId":123,"action":"query"}'
wrk -t4 -c100 -d30s -s post.lua --latency http://target/api
自定义请求与动态参数
-- dynamic.lua
request = function()
local id = math.random(1, 100000)
local path = "/api/user/" .. id
return wrk.format("GET", path)
end
带 Token 与随机 body
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Authorization"] = "Bearer eyJhbGciOi..."
local bodies = {}
for i = 1, 100 do
bodies[i] = string.format('{"orderId":%d,"amount":%d}', i, i * 10)
end
request = function()
local idx = math.random(#bodies)
return wrk.format(nil, nil, nil, bodies[idx])
end
阶段式加压
-- ramp.lua
local counter = 1
function setup(thread) end
function init(args) end
function done(summary, latency, requests)
io.write("------------------------------\n")
for _, p in pairs({ 50, 90, 99, 99.9 }) do
local n = latency:percentile(p)
io.write(string.format("%g%% %dms\n", p, n / 1000))
end
end
6. 压测流程(找容量拐点)
URL=http://target/api/health
for c in 100 200 500 1000 2000 4000; do
echo "===== concurrency=$c ====="
wrk -t8 -c$c -d30s --latency "$URL" | grep -E 'Requests/sec|Latency|req'
done
判断标准:
| 现象 | 结论 |
|---|---|
| QPS 随并发线性上涨,P99 平稳 | 还有余量 |
| QPS 不再涨,P99 开始抬升 | 接近拐点 |
| QPS 下降,P99 暴涨,出现超时 | 已过载 |
出现 Socket errors: connect |
连接队列/文件描述符到顶 |
取"拐点前 70%"作为安全容量。
7. 实战场景
7.1 与 ab 对比,验证 ab 是否是瓶颈
ab -n 20000 -c 200 -k http://target/api | grep 'Requests per second'
wrk -t8 -c200 -d30s --latency http://target/api | grep 'Requests/sec'
wrk 的结果明显更高,说明之前测的是 ab 的上限。
7.2 压测时同步看服务端
# 服务端
watch -n1 "ss -s; echo; top -b -n1 | head -12"
# 关键指标
ss -lnt | grep 8080 # Recv-Q 是否堆积
cat /proc/net/sockstat # sockets: used / TCP: inuse
nstat -az | grep -i -E 'ListenOverflows|ListenDrops'
ListenOverflows/ListenDrops 增长 → somaxconn 或 backlog 不足。
7.3 长连接压测
wrk 默认使用 HTTP/1.1 Keep-Alive。验证服务端是否真的复用连接:
ss -tn state established '( dport = :8080 )' | wc -l
# 应接近 -c 的值,而不是远大于
7.4 限速模拟真实用户
wrk 本身不支持限速,可配合 tc 或改用 k6:
# 用 tc 在压测机上限制出口带宽
tc qdisc add dev eth0 root tbf rate 100mbit burst 32kbit latency 400ms
8. 常见坑
- 线程数超过核数:
-t大于 CPU 核数反而降性能,-t= 核数。 -c远大于-t:单线程要处理大量连接,调度开销大;一般c = t × 100~500。- 不加
--latency:看不到 P99,只有平均值,容易误判。 - 压测时间太短:
-d10s包含 JIT 预热与缓存冷启动,至少 30s,正式测 60~300s。 - 压测机自身瓶颈:用
top看 wrk 进程 CPU,接近 100% 说明客户端到顶。 - 文件描述符不足:
ulimit -n至少-c的 2 倍,报too many open files。 - 端口耗尽:短连接场景大量
TIME_WAIT,net.ipv4.ip_local_port_range与tcp_tw_reuse要调。 - DNS 解析:用域名时每轮可能解析,用 IP +
-H 'Host: ...'。 - HTTPS 开销:wrk 支持 TLS 但握手成本高,测业务能力时优先内网 HTTP。
- Lua 脚本错误:
-s报错时 wrk 直接退出,先用最小脚本验证。 --timeout太短:高并发下正常请求被判定超时,统计失真。- 只看 QPS 不看错误率:
Socket errors与Non-2xx必须为 0 才有意义。 - 压生产:除非有明确预案,否则只压预发/影子环境。
9. 速查表
wrk -t4 -c100 -d30s --latency <url>
wrk -t8 -c1000 -d60s --latency --timeout 5s <url>
wrk -t4 -c100 -d30s -s post.lua --latency <url>
wrk -t4 -c100 -d30s -H 'Authorization: Bearer x' <url>
# 阶梯加压
for c in 100 500 1000 2000; do
wrk -t8 -c$c -d30s --latency <url> | grep -E 'Requests/sec|90%|99%'
done
# 关注
Requests/sec # 吞吐
Latency 50/90/99% # 延迟分布
Socket errors # 必须 0
ulimit -n # 文件描述符