wrk 实战:从安装到结果解读

说实话,压测工具我用了不少。ab、siege、JMeter 都折腾过。但要说轻量、高效、适合快速验证吞吐性能的,wrk 绝对排前三。它不像 JMeter 那么重,也不像 ab 那样功能单一。你想想看,一个单机工具能轻松压出几十万 QPS,这本身就说明它的设计很硬核。

这一章,我们就来把 wrk 彻底吃透。从安装开始,到参数调优,再到 Lua 脚本扩展,最后把那一堆输出结果看懂。嗯,都是实战干货。

1. 安装 wrk:别踩编译的坑

wrk 的安装其实很简单。但我在项目中遇到过几次编译失败的情况,基本都是因为缺少依赖。

Mac 用户:

brew install wrk

Linux 用户:

git clone https://github.com/wg/wrk.git
cd wrk
make
注意: Linux 编译需要 libssl-dev 和 libz-dev。我曾经在一台 CentOS 7 上直接 make,结果报错找不到 openssl。后来装了 yum install openssl-devel 才搞定。Ubuntu 的话是 apt install libssl-dev。

编译完直接就能用。我把 ./wrk 扔到 /usr/local/bin 里,全局访问。

2. 基础命令:先跑起来再说

先看一个最简单的例子:

wrk -t12 -c400 -d30s http://localhost:8080/api/test

这条命令的意思是:用 12 个线程,保持 400 个并发连接,持续压 30 秒。我刚开始用 wrk 时,习惯先跑个短时间的看看大概情况,比如 -d10s。等确认服务没问题,再上长时间的压力测试。

常用参数就这几个:

参数 含义 我的建议
-t 线程数 一般设为 CPU 核心数的 2-4 倍
-c 并发连接数 从 100 开始,逐步往上加
-d 压测时长 至少 30 秒,太短数据不稳定
--latency 输出延迟分布 我每次都加,方便看 P99
小技巧: 如果你压的是 HTTPS,记得加上 --timeout 5s。我遇到过 TLS 握手超时导致结果偏低的情况,加了超时参数后数据就正常了。

3. 线程与连接数调优:找到那个平衡点

很多人一上来就问:「线程设多少合适?」其实没有标准答案。它取决于你的机器、网络、以及目标服务的处理能力。

我个人习惯这样调:

  1. 先固定线程数,比如 -t4。
  2. 逐步增加并发连接数:-c100 → -c200 → -c500。
  3. 观察 QPS 和延迟。当 QPS 不再明显增长,而延迟开始飙升时,就是瓶颈了。

举个例子。我之前压一个 Java 服务,4 核 8G 的机器:

线程数 并发连接 QPS 平均延迟 P99 延迟
4 100 12,500 8ms 15ms
4 300 18,200 16ms 32ms
4 500 19,100 26ms 58ms
8 500 19,300 25ms 55ms

看到没?从 300 到 500 并发,QPS 几乎没涨,延迟却翻倍了。这时候再增加线程也没用,说明服务端已经到极限了。

核心原则: 线程数不是越多越好。线程多了,上下文切换的开销反而会吃掉性能。我一般控制在 CPU 核心数的 2 倍左右,再高收益就很低了。

4. Lua 脚本扩展:让压测更灵活

wrk 默认只做简单的 GET 请求。但实际场景哪有这么简单?登录、POST 数据、带 Token……这时候就需要 Lua 脚本了。

先看一个最常用的例子——POST 请求:

wrk.method = "POST"
wrk.body   = '{"username":"test","password":"123456"}'
wrk.headers["Content-Type"] = "application/json"

保存为 post.lua,然后执行:

wrk -t4 -c200 -d30s -s post.lua http://localhost:8080/login

更复杂一点的场景,比如每次请求都带不同的 Token。我遇到过需要压测带 JWT 的接口,Token 过期时间又短。这时候可以用 setup 函数提前生成一批 Token:

token_pool = {}

function setup(thread)
  -- 每个线程初始化时,从池子里拿一个 Token
  thread:set("token", token_pool[thread.id])
end

function request()
  wrk.headers["Authorization"] = "Bearer " .. token
  return wrk.format("GET", "/api/user/info")
end
避坑指南: 我曾经在 Lua 脚本里写了个死循环,结果 wrk 直接卡死。后来发现是 response() 回调里处理逻辑太复杂。记住,Lua 脚本里尽量只做简单的数据拼接和格式化,复杂的逻辑放到外面去。

wrk 的 Lua API 其实就几个:

  • wrk.format(method, path, headers, body) — 构造请求
  • wrk.lookup(host, port) — DNS 解析
  • wrk.connect(addr) — 建立连接
  • response(status, headers, body) — 处理响应

说实话,这些 API 足够覆盖 90% 的压测场景了。

5. 结果解读:别只看 QPS

跑完 wrk,你会看到类似这样的输出:

Running 30s test @ http://localhost:8080/api/test
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.34ms    8.56ms 123.45ms   75.23%
    Req/Sec     3,456.78   567.89   5,678.90   68.90%
  Latency Distribution
     50%   10.23ms
     75%   15.67ms
     90%   22.34ms
     99%   45.67ms
  1234567 requests in 30.00s, 1.23GB read
Requests/sec:  41152.23
Transfer/sec:   41.23MB

很多人只看最后一行 Requests/sec。但说实话,这远远不够。我一般会看这几个指标:

指标 关注点 我的经验
Avg Latency 平均延迟 如果超过 100ms,用户体验就很差了
P99 Latency 99% 请求的延迟 这个比平均值重要得多。P99 高说明有长尾请求
Req/Sec 的 Stdev 每秒请求数的标准差 如果很大,说明系统不稳定,有抖动
Transfer/sec 吞吐带宽 如果接近网卡上限,说明瓶颈在网络
注意: 如果看到 Socket errors: connect 0, read 5, write 0, timeout 12,说明服务端已经扛不住了。这时候的 QPS 数据其实是「假象」,因为很多请求根本没被处理。我曾经被这个坑过,以为系统能扛 5 万 QPS,结果一看错误率 20%……

还有一个容易被忽略的点:+/- Stdev 后面的百分比。比如 75.23%,意思是 75.23% 的请求延迟在平均值附近。如果这个值很低,说明延迟分布很散,系统不够稳定。

知识体系总览

下面这张图,把 wrk 的核心知识点串起来了。你可以把它当作一个检查清单,看看自己掌握了哪些。

wrk 实战知识体系 wrk 压测工具 安装与编译 基础命令参数 线程与连接调优 Lua 脚本扩展 结果解读与分析 Mac/Linux 依赖问题 -t -c -d --latency 逐步加压 观察拐点 POST 请求 Token 管理 QPS 与延迟 P99 与 Stdev 核心目标:找到系统吞吐瓶颈,优化性能