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 脚本 | 后面重点讲 |
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
另一个常用场景:自定义请求体
-- 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 再大也没用。
常见问题排查
| 现象 | 可能原因 | 我的经验 |
|---|---|---|
| 延迟突然飙升 | 服务端线程池打满、GC 停顿 | 加个 -H "Connection: keep-alive" 试试 |
| 错误数很多 | 连接数超限、超时 | 降低并发数,或者检查服务端配置 |
| QPS 上不去 | CPU 瓶颈、网络带宽不够 | 用 top 看 CPU,用 iftop 看带宽 |
3.5 知识体系总览
下面这张图,是我自己总结的 wrk 压测知识结构。你照着这个思路走,基本不会跑偏。
这张图把 wrk 的四个核心模块串起来了。你从安装开始,一步步走到结果解读,中间用 Lua 脚本解决复杂场景。说白了,压测不是为了跑个数字好看,而是为了发现系统的瓶颈在哪。