4. wrk 压测实战:安装、参数、Lua 扩展与结果解读
wrk 是一款轻量级、高性能的 HTTP 压测工具,基于 C 语言编写,利用多线程和事件驱动(如 epoll、kqueue)实现极低的资源占用与高并发连接。本章将围绕 wrk 的安装、核心参数、Lua 脚本扩展以及结果解读四个维度展开,帮助你在 Go 后端压测场景中快速上手并准确分析。
4.1 wrk 安装
wrk 支持主流操作系统,推荐从源码编译安装以获得最佳性能。
| 操作系统 | 安装方式 | 命令 |
|---|---|---|
| macOS | Homebrew | brew install wrk |
| Ubuntu/Debian | 源码编译 | |
| CentOS/RHEL | 源码编译 | |
| Windows | WSL 或 Docker | 推荐在 WSL2 中按 Ubuntu 方式编译,或使用 docker run --rm williamyeh/wrk |
验证安装: 执行
wrk --version,若输出类似 wrk 4.2.0 [epoll] 则安装成功。注意方括号内为当前系统使用的 I/O 模型(epoll / kqueue / select)。
4.2 常用参数详解
wrk 的核心参数控制着并发连接数、持续时间、线程数等关键压测维度。以下为最常用的参数及其说明:
| 参数 | 含义 | 示例 | 说明 |
|---|---|---|---|
-c |
并发连接数 | -c 100 |
保持打开的 HTTP 连接总数,wrk 会尽量维持此数量 |
-t |
线程数 | -t 4 |
启动的压测线程数,通常设置为 CPU 核心数的 2~4 倍 |
-d |
压测持续时间 | -d 30s |
支持 s(秒)、m(分)、h(时) |
--latency |
输出延迟分布 | --latency |
打印延迟百分位(P50、P75、P90、P99) |
-s |
指定 Lua 脚本 | -s script.lua |
用于自定义请求、鉴权、参数化等 |
-H |
自定义请求头 | -H "Authorization: Bearer xxx" |
可重复使用,添加多个请求头 |
--timeout |
请求超时时间 | --timeout 5s |
超过此时间未收到响应视为超时 |
典型压测命令:
wrk -t 4 -c 200 -d 60s --latency http://localhost:8080/api/v1/users
该命令使用 4 个线程、200 个并发连接,持续压测 60 秒,并输出延迟分布。
4.3 Lua 脚本扩展
wrk 内置 LuaJIT 引擎,允许通过 Lua 脚本实现复杂的压测逻辑,如动态参数、请求签名、条件判断等。Lua 脚本通过 -s 参数加载,wrk 会在不同阶段调用预定义的钩子函数。
4.3.1 核心钩子函数
| 钩子函数 | 调用时机 | 典型用途 |
|---|---|---|
setup(thread) |
每个线程启动时调用一次 | 初始化线程级数据,如随机种子、连接池 |
init(args) |
每个请求发起前调用 | 解析命令行参数,生成请求上下文 |
delay() |
每次请求发送前 | 返回延迟毫秒数,控制请求发送间隔 |
request() |
每次请求发送时 | 构造并返回 wrk 的请求对象(method、path、headers、body) |
response(status, headers, body) |
每次收到响应后 | 解析响应内容,记录自定义指标,判断业务成功/失败 |
done(summary, latency, requests) |
压测结束后调用一次 | 输出自定义统计结果,如错误率、业务耗时 |
4.3.2 实战脚本示例:带 Token 的动态请求
-- token_demo.lua
local token = "Bearer eyJhbGciOiJIUzI1NiIs..."
local counter = 0
function init(args)
-- 从命令行参数读取用户ID范围
id_range = tonumber(args[1]) or 1000
end
function request()
counter = counter + 1
local user_id = (counter % id_range) + 1
local path = "/api/v1/user/" .. user_id
return wrk.format("GET", path, {["Authorization"] = token})
end
function response(status, headers, body)
if status ~= 200 then
-- 记录非 200 响应,可在 done 中统计
wrk.status = (wrk.status or 0) + 1
end
end
function done(summary, latency, requests)
local error_rate = (wrk.status or 0) / summary.requests * 100
io.write(string.format("非200响应率: %.2f%%\n", error_rate))
end
执行命令:
wrk -t 2 -c 50 -d 30s -s token_demo.lua -- 5000 http://localhost:8080
其中 -- 后的 5000 会作为 args[1] 传入 init() 函数。
4.4 压测结果解读
wrk 的默认输出包含吞吐量、延迟、错误统计等关键指标。以下为一个典型输出示例:
Running 30s test @ http://localhost:8080/api/v1/users
2 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 45.23ms 12.87ms 287.00ms 78.45%
Req/Sec 1.12k 156.32 1.89k 72.10%
Latency Distribution
50% 42.10ms
75% 51.80ms
90% 63.40ms
99% 112.00ms
67234 requests in 30.01s, 89.45MB read
Socket errors: connect 0, read 3, write 0, timeout 1
Requests/sec: 2240.12
Transfer/sec: 2.98MB
4.4.1 核心指标解读
| 指标 | 含义 | 诊断方向 |
|---|---|---|
| Latency Avg | 平均延迟 | 若 > 200ms 需关注网络或服务端处理能力 |
| Req/Sec Avg | 每线程每秒请求数 | 乘以线程数即总吞吐,用于评估单机极限 |
| 50% / 90% / 99% | 延迟百分位 | P99 若远高于 Avg,说明存在长尾延迟 |
| Requests/sec | 全局吞吐量(RPS) | 核心性能指标,与预期目标对比 |
| Socket errors | 连接/读/写/超时错误数 | 大量错误表明服务端连接池或网络瓶颈 |
| Transfer/sec | 网络吞吐量 | 结合响应体大小判断带宽是否成为瓶颈 |
4.4.2 延迟分布 SVG 示意
4.5 常见问题与诊断建议
- 连接错误过多: 检查服务端最大连接数(
net.core.somaxconn、ulimit -n),以及 Go 中http.Server的MaxHeaderBytes和ReadTimeout设置。 - 延迟突增(P99 >> P50): 可能由 Go 的 GC STW、锁竞争、数据库连接池耗尽或 CPU 限流引起。建议配合
pprof和trace定位。 - 吞吐量上不去: 检查是否达到 CPU 瓶颈(单核 100%)、网络带宽上限,或 Go 中
GOMAXPROCS设置是否合理。 - Lua 脚本性能影响: 避免在
request()中执行耗时操作(如文件读写、复杂正则),否则会降低 wrk 本身的发包速率。
注意: wrk 的线程数不宜超过 CPU 核心数的 4 倍,否则线程切换开销会抵消并发收益。对于 Go 后端,建议先使用
-t 2 -c 50 起步,逐步增加并发直到延迟拐点出现。