4. wrk 压测实战:安装、参数、Lua 扩展与结果解读

wrk 是一款轻量级、高性能的 HTTP 压测工具,基于 C 语言编写,利用多线程和事件驱动(如 epoll、kqueue)实现极低的资源占用与高并发连接。本章将围绕 wrk 的安装、核心参数、Lua 脚本扩展以及结果解读四个维度展开,帮助你在 Go 后端压测场景中快速上手并准确分析。

4.1 wrk 安装

wrk 支持主流操作系统,推荐从源码编译安装以获得最佳性能。

操作系统 安装方式 命令
macOS Homebrew brew install wrk
Ubuntu/Debian 源码编译
sudo apt-get install build-essential libssl-dev git
git clone https://github.com/wg/wrk.git
cd wrk
make
sudo cp wrk /usr/local/bin/
CentOS/RHEL 源码编译
sudo yum groupinstall 'Development Tools'
sudo yum install openssl-devel git
git clone https://github.com/wg/wrk.git
cd wrk
make
sudo cp wrk /usr/local/bin/
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 示意

延迟分布示意图(单位:ms) 42.1 P50 51.8 P75 63.4 P90 112.0 P99 0 80 160 240 P99 显著高于 P50 时,需排查慢查询、GC 停顿或资源争抢

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 起步,逐步增加并发直到延迟拐点出现。