wrk 是一款基于 epoll 和 kqueue 事件模型的 HTTP 基准测试工具,它通过多线程和异步 I/O 在单台机器上产生大量并发连接,因此特别适合用来评估云服务器上 Web 应用、API 网关或负载均衡节点的吞吐能力。与传统的 ab 工具相比,wrk 在相同硬件条件下通常能打出更高的请求速率,并且支持通过 Lua 脚本灵活构造请求头、请求体和动态 URL。开始压测之前,需要明确一个基本原则:压测端和被压测端最好部署在不同的云主机或本地机器上,避免 wrk 自身消耗 CPU、内存和网络资源,导致最终数据无法真实反映服务端能力。

安装 wrk 并调整云服务器参数
在 Ubuntu 或 Debian 云主机上,可以通过源码编译的方式安装 wrk。先安装编译依赖,然后克隆仓库并执行 make。不同发行版对构建工具名称略有差异,CentOS 或 Rocky Linux 通常需要安装 gcc、make 和 openssl-devel。编译完成后,wrk 可执行文件会出现在当前目录下,把它复制到 /usr/local/bin 即可全局使用。
sudo apt update sudo apt install -y build-essential libssl-dev git git clone https://github.com/wg/wrk.git cd wrk && make sudo cp wrk /usr/local/bin/
压测开始前还要检查云服务器的文件描述符上限和本地端口范围。默认的 1024 个文件描述符在几万并发连接下很快就会被耗尽,导致压测端出现大量 socket 创建失败。可以使用 ulimit 临时调高限制,同时调整内核参数加快 TIME_WAIT 状态端口回收。如果被测服务运行在同一台云服务器上,还需要在安全组中放通对应端口,否则连接会被云平台防火墙直接丢弃,压测结果会出现大量超时。
ulimit -n 1048576 sudo sysctl -w net.ipv4.ip_local_port_range="1024 65500" sudo sysctl -w net.ipv4.tcp_tw_reuse=1
核心参数与常用压测命令
wrk 最常用的参数是 -t 指定线程数、-c 指定总连接数、-d 指定持续时间。连接数表示 wrk 同时维持的 HTTP 连接数量,线程数决定这些连接由多少个工作线程管理。连接数并非越大越好,当连接数超过服务端能够快速处理的范围时,请求会堆积在队列中,响应时间骤增,但吞吐量并不会继续上升。一般建议先从较低连接数开始,比如 50、100、200 逐步加压,找到 QPS 到达瓶颈且错误率开始上升的拐点。
wrk -t12 -c400 -d30s --latency http://127.0.0.1:8080/api/health
上面这条命令表示开启 12 个线程、维持 400 个连接,对目标接口持续压测 30 秒,并在结果中打印延迟分布。加上 --latency 后,wrk 会输出 50%、75%、90%、99% 分位延迟以及最大延迟,这对于发现偶发的慢请求非常有用。如果只是查看平均延迟,可能会掩盖少量请求严重超时的问题。对于需要携带请求头的接口,可以使用 -H 参数,多次使用即可添加多个头部字段。
wrk -t8 -c200 -d20s -H "Authorization: Bearer test-token" -H "Content-Type: application/json" http://127.0.0.1:8080/api/order/list
当目标地址包含查询参数时,建议把 URL 用单引号或双引号包裹起来,避免 shell 对分隔符进行解释。若 URL 中存在多个参数,例如分页接口中的 page 和 size,可以直接写入完整地址。此时需要留意 shell 对 & 符号的处理,最好把整个 URL 放进引号内。对于 POST 请求,wrk 本身不直接提供 -d 参数来指定请求体,需要借助 Lua 脚本完成。
使用 Lua 脚本扩展压测能力
wrk 默认只会请求同一个 URL,实际压测中经常需要模拟登录、随机读取不同资源、提交 JSON 数据等场景。通过在命令中增加 -s 参数并指定 Lua 脚本,可以在 request 函数中动态生成每一次请求的方法、路径、请求头和请求体。脚本结构通常包含 init、request、response 和 done 四个函数。init 在压测开始时执行一次,response 在每次收到响应后调用,done 在压测结束后汇总结果。
request = function()
local headers = {}
headers["Content-Type"] = "application/json"
local body = '{"username":"loadtest","password":"123456"}'
return wrk.format("POST", "/api/login", headers, body)
end运行脚本时只需要在 wrk 命令中加上 -s 参数,例如 wrk -t8 -c100 -d30s -s post.lua http://127.0.0.1:8080。更为复杂的场景可以在 request 函数中随机选择不同路径,通过数学库生成随机数,再从预置的路径数组中取一个值。这样可以更贴近真实用户访问行为,而不是让缓存命中率变得异常高,导致压测数据过于乐观。
除了构造请求,response 函数还能做自定义统计。例如统计不同 HTTP 状态码的数量,或者记录响应体中的某个业务字段。下面这段脚本会分别统计 200 和非 200 响应数量,并在压测结束后打印出来。
local count_200 = 0
local count_non_200 = 0
response = function(status, headers, body)
if status == 200 then
count_200 = count_200 + 1
else
count_non_200 = count_non_200 + 1
end
end
done = function(summary, latency, requests)
print("200 responses: " .. count_200)
print("non-200 responses: " .. count_non_200)
end这种脚本化能力让 wrk 不再只是一个简单的 URL 压测工具,而可以承担接口冒烟、错误率统计、延迟分布分析等任务。不过需要注意,request 函数中的逻辑不能过于复杂,否则 wrk 自身会变成瓶颈。尤其是不要在 request 函数里做大量字符串拼接或高开销计算,这会影响最终测出的 QPS 上限。
解读压测结果并定位云服务器瓶颈
一次典型的 wrk 结果中,Requests/sec 表示每秒完成的请求数,这是最核心的吞吐指标;Transfer/sec 表示每秒传输的数据量,计算方式为响应头和响应体字节数之和。Latency 部分展示平均延迟、标准差、最大延迟以及分位延迟。标准差越大,说明响应时间波动越明显。如果 99% 分位延迟远高于平均延迟,通常说明服务端存在排队、锁竞争或者偶发的 GC 停顿。
Running 30s test @ http://127.0.0.1:8080/api/health
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 85.23ms 42.10ms 680.15ms 78.90%
Req/Sec 1.52k 210.35 2.31k 72.00%
545123 requests in 30.08s, 82.19MB read
Requests/sec: 18122.34
Transfer/sec: 2.73MB拿到数据后,要回到云服务器监控中寻找瓶颈。如果 CPU 使用率已经接近 100%,而 QPS 不再增长,说明应用层计算或框架开销过大。如果 CPU 不高但网络流量接近云主机带宽上限,则需要升级带宽或启用压缩。如果压测端 CPU 已经打满,而服务端资源还很空闲,就要考虑增加压测机器数量,或者降低 wrk 的线程和连接数,避免误判。使用 top、vmstat、ss -s 等命令可以快速查看 CPU、内存和连接状态。
云服务器环境下,TIME_WAIT 是另一个容易被忽视的问题。压测结束后,客户端或服务端可能堆积大量处于 TIME_WAIT 状态的连接,影响后续压测或正常业务。通过调整 tcp_tw_reuse 和 tcp_fin_timeout 可以加快回收,但更推荐在压测前合理设置连接数,避免在短时间内反复创建和销毁连接。应用侧可以启用 HTTP keep-alive,减少握手开销,提升单连接上的请求效率。
最后要区分清楚 QPS 与并发用户数。wrk 的 -c 参数表示并发连接数,并不等同于真实在线用户数。一个用户在浏览器中可能同时打开多个连接,而且用户操作之间会有思考时间。因此不要直接把 wrk 测出的最大 QPS 当作系统能承载的用户规模,还需要结合业务模型、平均会话时长和请求频率进行换算。只有把压测数据放在真实场景中理解,才能做出合理的容量规划和扩容决策。