3. wrk实战

好,咱们进入实战环节。压测工具那么多,我为什么偏偏推荐 wrk?说白了,它够轻、够快、够直接。不像某些工具启动半天,wrk 一条命令就能把服务压出原型。今天我就带你把它玩透。

3.1 安装 wrk:三分钟上手

安装这事儿,不同系统路子不一样。我习惯在 Linux 上搞压测,因为 wrk 对 epoll 的利用最充分。

Linux(Ubuntu/Debian)

sudo apt-get install build-essential libssl-dev git -y
git clone https://github.com/wg/wrk.git
cd wrk
make
sudo cp wrk /usr/local/bin/

嗯,这里有个坑——必须装 libssl-dev,不然编译会报错。我当年第一次装时就卡在这儿了,折腾了十分钟才发现。

macOS

brew install wrk

macOS 用户就简单了,一行搞定。不过说实话,macOS 下压测的精度不如 Linux,因为系统调度策略不同。我建议最终压测数据以 Linux 为准。

验证安装

wrk --version

看到版本号,就算成了。

3.2 基本命令:从零开始压

wrk 的命令行参数不多,但个个有用。咱们先看最基础的用法。

wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html

这条命令的意思是:用 12 个线程、保持 400 个并发连接、持续压 30 秒。我刚开始用 wrk 时,总搞混「线程数」和「并发数」——线程是 wrk 自己用的 worker,并发是模拟的用户数。你想想看,12 个线程去维护 400 个连接,每个线程管三十多个,压力就上来了。

常用参数一览

参数 含义 我的建议
-t 线程数 一般设为 CPU 核心数的 2-4 倍
-c 并发连接数 从 100 开始,逐步往上加
-d 压测时长 至少 30 秒,太短数据不稳定
--latency 输出延迟分布 我每次都加,排查问题必备
-s 指定 Lua 脚本 后面重点讲
我的小技巧:压测时先跑个短时间(比如 10 秒)看看服务扛不扛得住,别一上来就 30 秒,万一把服务打挂了,恢复起来也麻烦。

3.3 Lua 脚本扩展:让 wrk 更聪明

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

我个人觉得,wrk 的 Lua 扩展是它最牛的地方。它提供了几个钩子函数,你可以在请求的不同阶段插入逻辑。

核心钩子函数

  • setup():初始化阶段,只执行一次。我一般在这里解析命令行参数。
  • init():每个线程开始时执行一次。适合初始化线程级别的数据。
  • request():每次发送请求前调用。返回一个 HTTP 请求对象。
  • response():每次收到响应后调用。可以检查状态码、提取数据。
  • done():压测结束后执行。汇总结果用。

实战:模拟登录后压测

我曾经遇到一个场景——接口需要先登录拿到 ,然后带着 去压业务接口。用 Lua 脚本完美解决。

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

response = function(status, headers, body)
    if status == 200 then
        -- 从响应中提取 
        local  = headers["Authorization"]
        if  then
            -- 把  存到全局变量,供后续请求使用
            wrk.headers["Authorization"] = 
        end
    end
end

request = function()
    -- 后续请求都带上 
    return wrk.format("GET", "/api/data")
end

运行方式:

wrk -t4 -c100 -d30s -s login.lua http://127.0.0.1:8080
注意:Lua 脚本里不要做耗时操作,比如写文件、网络请求。wrk 是事件驱动的,阻塞操作会拖垮整个压测。我曾经犯过这个错,在 response() 里写日志,结果压测结果直接废了。

另一个常用场景:自定义请求体

-- custom_body.lua
request = function()
    local body = '{"user_id": ' .. math.random(1, 10000) .. '}'
    return wrk.format("POST", "/api/update", nil, body)
end

这样每次请求的 user_id 都不同,更贴近真实场景。

3.4 结果解读:别只看 QPS

压测跑完了,一堆数字摆在你面前。很多人只盯着 QPS 看,觉得越高越好。其实不然。

Running 30s test @ http://127.0.0.1:8080
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    45.62ms   12.34ms 198.00ms   72.45%
    Req/Sec    723.45    89.21     1.02k    68.90%
  Latency Distribution
     50%   42.00ms
     75%   51.00ms
     90%   62.00ms
     99%   98.00ms
  259830 requests in 30.00s, 38.45MB read
  Socket errors: connect 0, read 5, write 0, timeout 2
Requests/sec:   8661.00
Transfer/sec:      1.28MB

关键指标解读

  • Latency(延迟):看平均值和 P99。平均值 45ms 看起来还行,但 P99 到了 98ms,说明有 1% 的请求比较慢。我一般要求 P99 不超过平均值的 2 倍。
  • Req/Sec(每秒请求数):每个线程的吞吐。如果线程间差异很大(Stdev 高),说明负载不均衡。
  • Socket errors:这个要特别关注。read 错误、timeout 都说明服务端处理不过来。我曾经压一个没做连接池优化的服务,timeout 飙到几百,一看就是后端线程池满了。
  • Requests/sec:总 QPS。但别高兴太早——如果延迟很高,QPS 再大也没用。
我的判断标准:先看错误数,再看延迟分布,最后看 QPS。如果错误数不为 0,QPS 再高也是假的。

常见问题排查

现象 可能原因 我的经验
延迟突然飙升 服务端线程池打满、GC 停顿 加个 -H "Connection: keep-alive" 试试
错误数很多 连接数超限、超时 降低并发数,或者检查服务端配置
QPS 上不去 CPU 瓶颈、网络带宽不够 用 top 看 CPU,用 iftop 看带宽

3.5 知识体系总览

下面这张图,是我自己总结的 wrk 压测知识结构。你照着这个思路走,基本不会跑偏。

wrk 压测实战 安装 Linux / macOS 基本命令 -t / -c / -d / --latency Lua 脚本扩展 钩子函数 / 自定义请求 结果解读 延迟 / QPS / 错误数 编译安装 brew 安装 线程与并发 压测时长 钩子函数 自定义请求 延迟分布 错误排查 核心思路:先看错误 → 再看延迟 → 最后看 QPS 用 Lua 脚本模拟真实场景,用数据指导优化

这张图把 wrk 的四个核心模块串起来了。你从安装开始,一步步走到结果解读,中间用 Lua 脚本解决复杂场景。说白了,压测不是为了跑个数字好看,而是为了发现系统的瓶颈在哪。

最后说一句:wrk 是个好工具,但它不是万能的。如果你需要压测 WebSocket、gRPC 这类协议,还是得用其他工具。不过对于 90% 的 HTTP 接口压测场景,wrk 足够用了。