wrk —— 高并发 HTTP 压测

运维工具手册 10-压测与基准 5.1 KB 原文

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. 常见坑

  1. 线程数超过核数:-t 大于 CPU 核数反而降性能,-t = 核数。
  2. -c 远大于 -t:单线程要处理大量连接,调度开销大;一般 c = t × 100~500。
  3. 不加 --latency:看不到 P99,只有平均值,容易误判。
  4. 压测时间太短:-d10s 包含 JIT 预热与缓存冷启动,至少 30s,正式测 60~300s。
  5. 压测机自身瓶颈:用 top 看 wrk 进程 CPU,接近 100% 说明客户端到顶。
  6. 文件描述符不足:ulimit -n 至少 -c 的 2 倍,报 too many open files。
  7. 端口耗尽:短连接场景大量 TIME_WAIT,net.ipv4.ip_local_port_range 与 tcp_tw_reuse 要调。
  8. DNS 解析:用域名时每轮可能解析,用 IP + -H 'Host: ...'。
  9. HTTPS 开销:wrk 支持 TLS 但握手成本高,测业务能力时优先内网 HTTP。
  10. Lua 脚本错误:-s 报错时 wrk 直接退出,先用最小脚本验证。
  11. --timeout 太短:高并发下正常请求被判定超时,统计失真。
  12. 只看 QPS 不看错误率:Socket errors 与 Non-2xx 必须为 0 才有意义。
  13. 压生产:除非有明确预案,否则只压预发/影子环境。

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           # 文件描述符
wrk —— 高并发 HTTP 压测 - 知识库 - 起航社区