news 2026/10/13 13:43:05

wrk-4.1.0 Linux服务器压测实战:编译、参数调优与Lua扩展

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
wrk-4.1.0 Linux服务器压测实战:编译、参数调优与Lua扩展

简介:wrk 4.1.0版Linux性能测试资源包,是一款面向Web服务的高性能HTTP基准测试工具,适合后端开发、测试及运维人员对服务器进行高并发压测与瓶颈诊断。该工具采用C语言实现,支持多线程、多连接并发请求,并可利用Lua脚本定制复杂测试场景,在短连接高并发场景中比JMeter和ab更显轻量高效。资源包为7z压缩格式,体积约168.72MB,内含源码及配套说明,便于在Linux环境中编译部署。包内系统梳理了编译安装要点、常用参数(如并发连接数、线程数、测试时长)、典型压测命令、结果指标解读以及主流压测工具选型对比,可帮助读者理解wrk运行机制,并直接用于接口压测、容量评估与服务调优。当前已有586人学习浏览,适合希望快速掌握轻量级压测工具的开发、测试与运维人员,也适合作为团队性能评估的入门参考。

1. wrk-4.1.0 在 Linux 上的定位:服务器压测为什么首选它

给一台刚交付的 Linux 服务器做性能摸底,我先跑的工具不是 ab,也不是 JMeter,而是性能测试工具 wrk-4.1.0。wrk 用 C 写、基于 epoll 事件驱动,单机就能在普通服务器上压出几十万级 QPS,产物只有一个可执行文件,配置成本比 JMeter 低一个数量级。它解决的核心问题是「这台服务器能扛多少并发、延迟长什么样」,适合接口验收、容量评估和上线前后的回归对比。下面按 Linux 实际操作路径,把编译、参数、Lua 扩展和常见坑过一遍,给刚开始接触 wrk 的人一条能复现的路线。

2. Linux 上编译 wrk-4.1.0:从 git clone 到第一次压测

2.1 为什么不用系统包管理器直接装:版本与 Lua 支持的差别

很多 Linux 发行版的仓库里要么没有 wrk,要么版本停在 3.x 甚至更早。版本差异平时不显眼,一旦要写 Lua 脚本就露馅:老版本对 response 阶段和 done 阶段支持不完整,个别发行版还把 LuaJIT 换成了纯 Lua 解释器,压测时线程一多,性能完全对不上号。wrk-4.1.0 是稳定线里功能最全的一个版本,自带 LuaJIT,支持自定义请求函数、逐请求响应处理和压测结束时的统计汇总,这些能力不是每个打包版都会带上的。

另一层原因更实际:wrk 本身就是一个 C 项目,依赖只有 gcc、make 和 openssl 开发头文件,源码编译通常不到一分钟。与其跟包管理器较劲装个旧版,不如直接拉官方仓库编一次,拷到 /usr/local/bin 里,之后所有机器都用同一个固定版本。下面的命令按 Ubuntu/Debian 写,CentOS/RHEL 系把 apt 换成 yum install -y gcc make openssl-devel 就能对齐。

2.2 源码编译全流程:依赖、make 与可执行文件落地

先装编译依赖,三个包缺一不可:

sudo apt update sudo apt install -y build-essential libssl-dev git

build-essential 提供 gcc 和 make;libssl-dev 提供 HTTPS 压测需要的 openssl/ssl.h 头文件,wrk 的 Makefile 默认开 SSL 支持,缺它会在编译时报 fatal error;git 用来拉源码。如果公司内网隔离不能直接 clone,可以在一台能联网的机器上先把 wrk 源码 tar 打包传过去,make 过程本身不依赖网络。

拉取官方仓库并编译:

git clone https://github.com/wg/wrk.git cd wrk make

make 完成后,当前目录下会出现一个名为 wrk 的可执行文件。wrk 的 Makefile 没有 install 目标,所以习惯上手动拷贝到 PATH 里:

sudo cp wrk /usr/local/bin/ which wrk wrk --version

wrk --version 的输出里会带 4.1.0 和 epoll 字样。看到 epoll 说明编译时正确用上了内核的 I/O 多路复用机制,而不是 select 兜底。这一步值得专门确认,因为后面所有高并发表现都建立在这个基础上。如果输出里不是 4.1.0,先检查 PATH 里是不是还有另一个旧版 wrk 在干扰,常见位置是 /usr/bin/wrk。

2.3 第一次压测就跑通:最小命令与逐行输出解读

编译完成后先拿本机服务试手。假设 8080 端口有个 HTTP 服务在跑:

wrk -t 4 -c 100 -d 10s http://127.0.0.1:8080/

-t 4 表示开 4 个线程,-c 100 表示同时保持 100 个并发连接,-d 10s 表示持续压 10 秒。wrk 会把 100 个连接平分给 4 个线程,每个线程独立跑一个 epoll 事件循环。第一次跑主要是验证工具本身没问题,具体数值不重要。输出长这样:

Running 10s test @ http://127.0.0.1:8080/ 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 45.30ms 22.10ms 120.00ms 70.12% Req/Sec 2.60k 350.00 3.20k 81.00% Latency Distribution 50% 38.00ms 75% 52.00ms 90% 68.00ms 99% 110.00ms 83331 requests in 10.00s, 78.42MB read Requests/sec: 8333.10 Transfer/sec: 7.84MB

第一段 Thread Stats 的 Latency 行:Avg 是所有请求的平均响应时间,Stdev 是标准差,+/- Stdev 列表示落在平均值±标准差范围内的请求比例。Stdev 接近 Avg 一半以上,说明延迟抖动明显,不能只拿平均值下结论。第二段 Latency Distribution 是延迟分位数:99% 这一行表示 99% 的请求在 110ms 内完成,剩下 1% 的慢请求才是线上用户能感知到的尾巴。第三段 Requests/sec 是整个压测期间的总平均每秒请求数,也就是平时说的 QPS;Transfer/sec 是网络吞吐量。

这里有个新手常犯的误读:Latency Distribution 里的 99% 110ms 是延迟,不是 QPS;Requests/sec 才是 QPS。压测报告里如果只写「QPS 99% 是多少」,基本是拿错了字段。另外,-d 计时从连接建立开始算,前几秒连接和数据都在热身,所以正式压测建议至少 30 秒起步,不然冷启动的波动会混进结果里。

3. wrk 压测参数详解:-t、-c、-d 怎么配才不翻车

3.1 三个参数与并发模型:线程、连接、时长的关系

wrk 的并发模型跟 ab 不一样,它不 fork 子进程,而是起 t 个线程,每个线程持有 c/t 个连接,跑独立的非阻塞事件循环。所以 -t 不是越大越好:线程数超过物理核数之后,上下文切换成本反而吃掉一部分能力。常见做法是先看 nproc 拿到核数,取物理核数或 2 倍核数作为 -t 的起点。

参数配合上,连接数要按被测服务的吞吐和延迟来估,不能随手填。三者关系可以用下表归纳:

参数含义设置依据
-t线程数物理核数或 2 倍物理核数
-c并发连接数目标 QPS × 平均响应时间
-d压测时长至少 30s,包含热身期

-c 不能整除 -t 时,wrk 会尽量平均分配,但线程间的连接数差异会导致负载不均,最终输出里能看到某个线程的 Req/Sec 明显偏高、另一个偏低。所以一般把连接数设成线程数的整数倍。时长 -d 也常被忽略:10 秒压测对 Java 应用来说可能 JIT 还没热完,GC 抖动直接拉高延迟;做上线前评估,我一般至少跑 60 秒,用长时间数据覆盖 GC 和连接池重建等周期性抖动。

3.2 压测案例:一个普通 Web 服务的参数取值过程

假设要评估一个用户中心接口,已知线上平均响应时间约 50ms,希望它扛住 8000 QPS。先用经验公式粗估并发连接数:并发连接数约等于目标 QPS 乘以平均响应时间,也就是 8000 × 0.05 = 400 个连接。

# 目标 QPS 8000,平均响应时间 50ms,并发连接数约 400 wrk -t 8 -c 400 -d 60s http://127.0.0.1:8080/api/user/info

400 个连接分到 8 个线程,每线程 50 个。这里要注意 keep-alive 的影响:wrk 默认请求头里带 Connection: keep-alive,所以 -c 对应的是「同时保持的连接数」,不是「每秒新建连接数」。如果服务端没开 keep-alive,响应里返回 Connection: close,那实际压的就是每秒 TCP 建连能力,TLS 场景下这个开销还会成倍放大,连接数公式就不成立了。

更稳妥的调参方式是从小往上加:先 -c 50 跑一遍看延迟,再把 -c 翻倍。如果延迟线性涨、QPS 几乎不动,说明已到服务端并发上限;如果延迟没变、QPS 跟着翻倍,说明离瓶颈还很远。这样加两三轮,就能在几分钟内摸到服务的真实承载边界,而不是一次跑个超大参数把服务直接打挂。

3.3 只看平均数是玄学:延迟分位与每秒请求数的真实读法

只看平均值是压测报告里最常见的坑。wrk 输出的 +/- Stdev 列是落在平均值±标准差范围内的请求比例,70% 这个值意味着三成请求的延迟明显偏离均值,服务的真实体验并不稳定。Latency Distribution 要一行行看:50% 和 99% 之间差多少倍,是判断稳定性的直接指标。两者差 3 倍以上,说明存在明显的慢请求拖尾,这种尾巴通常来自 GC、锁竞争或者连接池排队。

还有一层容易踩的坑是前后对比时参数不一致。前一次 -t 4 -c 100,后一次 -t 8 -c 400,QPS 涨了也分不清是服务变强了还是并发加满了。要对比就得锁死参数,并且记录负载机配置、被压服务版本和网络环境,缺一项数据都没法复现。压测结果是个范围不是个点,对比时看趋势,不要纠结单次几 percent 的浮动。

两个容易混的指标再单独拎一下:Requests/sec 是压测机视角的总吞吐,只要压测机不成为瓶颈,它可以近似看作服务端承载上限;Transfer/sec 是网络吞吐,如果这个值很高但 Requests/sec 偏低,问题大概率出在响应体太大或带宽受限上。

4. 用 Lua 脚本扩展 wrk-4.1.0:POST 请求、鉴权头与参数化

4.1 为什么必须用 Lua:wrk 默认没有 -P 参数和请求体文件

wrk 的命令行参数只有 -t、-c、-d 和几个辅助项,没有 -P 传 POST 参数、没有 -F 传请求体文件,这是它刻意保持的简洁。要压不同类型的接口,就得靠 --script 指定的 Lua 脚本扩展。wrk 内置 LuaJIT,每个线程启动时各持一个独立的 Lua VM,脚本在启动阶段执行一次,之后每个请求触发一次 request 函数。

这个模型决定了两个事实:第一,脚本里定义的变量只在线程内部共享,跨线程的全局计数器、累计值都不能依赖它;第二,脚本顶层的变量赋值只执行一次,适合放固定 header 和静态 body。常见误用是拿全局变量做总请求计数,最后结果会比真实请求数小 t 倍。这类血泪案例不止一次出现,写脚本时先记住线程边界。

4.2 POST 压测脚本完整示例:method、headers、body 的顺序

-- post.lua: 对登录接口做 POST 压测 wrk.method = "POST" wrk.headers["Content-Type"] = "application/json" wrk.headers["User-Agent"] = "wrk-load-test" wrk.body = '{"username":"admin","password":"123456"}'

这些赋值发生在脚本顶层,wrk 启动时读取一次。wrk.headers 是个 table,多次赋值用方括号加字符串键;wrk.body 直接放 JSON 字符串,不需要额外转义。然后运行:

wrk -t 4 -c 200 -d 60s --script post.lua http://192.168.1.50:8080/api/login

脚本文件路径放在 --script 后面、URL 前面,顺序不能反。如果脚本语法有错,wrk 启动时会直接报 load error,不会进入压测循环,这一点比运行到一半再失败要好排错得多。很多教程把 wrk.method 写在 request 函数里,新手照着抄就会出现每个请求临时改 header 的性能损耗。我的习惯是顶层赋值一次,request 函数里只覆盖真正变化的字段,比如动态 body。

4.3 模拟真实用户:动态参数、Token 注入与多阶段切换

-- dynamic.lua: 每个请求携带不同的 username local users = {"user_a", "user_b", "user_c", "user_d"} local idx = 0 function request() idx = (idx % #users) + 1 local body = '{"username":"' .. users[idx] .. '","password":"123456"}' wrk.body = body return wrk.format() end

request 函数每个请求都会被调用,这里用取模循环让 4 个 username 轮换,模拟不同账号并发,而不是所有请求共用一个账号。wrk.format() 会按当前的 wrk.method、wrk.headers 和 wrk.body 拼出完整 HTTP 请求文本并返回。如果对性能有极致要求,可以先拼好请求字符串缓存起来,但动态参数场景下每次重新拼更合理。

带鉴权的接口可以用 init 函数从命令行接收 token:

-- authed.lua: 从命令行参数读 token local token function init(args) token = args[1] end function request() wrk.headers["Authorization"] = "Bearer " .. token return wrk.format() end
TOKEN=$(curl -s -X POST http://192.168.1.50/api/login -d '{"user":"admin"}' | jq -r .token) wrk -t 4 -c 200 -d 60s --script authed.lua http://192.168.1.50/api/orders -- "$TOKEN"

init(args) 是 wrk 保留的初始化函数,不能改名字。-- 后面的内容会作为 args 数组传进 Lua 脚本,这样 token 不写死在脚本文件里,也不会把鉴权信息提交到代码仓库。多阶段压测同样可以用脚本控制:脚本里记录启动时间,用 os.time() 判断当前处于哪个阶段,前 30 秒压登录接口、后 30 秒压业务接口。不过 os.time() 精度只到秒,粗粒度切换够用;要求精确的阶段划分,还是拆成两个脚本各跑一次更稳。

5. wrk-4.1.0 常见问题与排查:编译、连接、负载机的 5 个坑

wrk 本身不是黑匣子,多数问题出在编译环境、连接参数和 Lua 脚本三个地方。下面 5 条是接口压测和容量评估里反复出现的真实案例,按现象、原因、解决的顺序写,照着自己排查基本都能对上号。

5.1 编译报错找不到 openssl/ssl.h

现象:执行 make 时终端刷出一大片 fatal error: openssl/ssl.h: No such file or directory,编译中断。

原因:wrk 的 Makefile 默认开启 SSL 支持,编译时需要 libssl-dev 头文件,而最小化安装的 Linux 往往没装这个开发包。装运行时库(openssl)不会自动带上头文件,必须显式安装 -dev 版本。

解决:装依赖后重编。Ubuntu/Debian 执行 sudo apt install -y libssl-dev,CentOS 执行 yum install -y openssl-devel。装完不用清理缓存,直接在 wrk 目录下重新 make 即可。

5.2 Cannot assign requested address:客户端端口耗尽

现象:-c 开到很大时,wrk 报 Cannot assign requested address,或者某个线程的输出里出现大量 Connection reset。

原因:客户端端口范围不够。Linux 默认 ip_local_port_range 是 32768-60999,最多撑约 2.8 万个四元组;短连接建立和关闭都很快时,TIME_WAIT 状态占满整个端口池,新连接就建不起来了。

解决:压测前放宽端口范围并确认复用配置:

sysctl -w net.ipv4.ip_local_port_range="1024 65535" sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_fin_timeout=15

更根治的方案是让被压服务支持 keep-alive。wrk 默认请求头带 Connection: keep-alive,但如果服务端响应带回 Connection: close,连接还是会用完即关,客户端继续积累 TIME_WAIT。压测前用 curl -v 确认服务端的连接行为,再决定是否要在连接复用上做文章。

5.3 压测机 CPU 被打满,QPS 结果失真

现象:被压服务 CPU 只有 30%,wrk 所在机器的 CPU 已经满核,QPS 数字却纹丝不动。

原因:负载机自身成了瓶颈。wrk 的线程忙在内核协议栈和事件循环里,测出来的根本不是服务端能力。单机 wrk 能压多高有个天花板,跟机器网卡、CPU 主频和内核配置都有关。

解决:先测一下本机的上限——用 wrk 去压一个只返回固定字符串的极简 HTTP 服务,看能跑多少 QPS。如果正式压测结果与这个上限接近,就该换更强的负载机、适当降低 -t 减少线程切换,或者把被压服务迁到另一台机器上。内网压测时,负载机和服务端最好用千兆以上网卡直连,避开交换机限速这类隐藏变量。

5.4 HTTPS 自签证书压测失败:wrk 没有跳过校验的参数

现象:wrk 压 HTTPS 地址时握手阶段就失败,报错指向证书校验;服务端用的是自签证书或内网私有 CA。

原因:wrk-4.1.0 没有像 curl -k 那样提供跳过证书校验的开关。SSL 握手时证书校验失败直接断连,不是参数写错的问题。

解决:内网压测最常见的做法是绕开 TLS——用 Nginx 做 HTTPS 终结,把 443 转发到内网 80 端口,wrk 直接压 HTTP 地址;或者给被压服务临时配一张内网信任的证书,把证书链装进 wrk 所在机器的 ca-certificates 目录。压 TLS 本身不是 wrk 的强项,如果业务压测必须走加密链路,建议换 Gatling 或 JMeter,别在这里耗时间。

5.5 Lua 脚本报 attempt to concatenate a nil value

现象:Lua 脚本执行时报 attempt to concatenate a nil value (field 'body'),而且只在特定请求出现。

原因:request 函数里做字符串拼接时,某个变量是 nil。常见于分支条件漏掉默认值,或者 init 里没初始化变量就直接进了 request。wrk 的报错会指向 request 函数的拼接行,往上游找第一个 nil 变量就是根因。

解决:拼接前做兜底赋值:

local payload = wrk.body or "" local body = '{"key":"' .. payload .. '"}'

如果变量来自命令行参数,先在 init(args) 里检查 args[1] 是否为 nil 并给默认值。这类问题定位本身不难,难在压测脚本里这种隐藏分支往往不会被短时间压测触发,所以写脚本时就把默认值逻辑补全,别留给线上压测去暴露。

6. 压测数据可信度怎么验证:三次取中位数与瓶颈定位技巧

6.1 三次运行取中位数:Socket errors 不为 0 直接作废

压测数据不是跑一次就能当结论的。我现在的习惯是先跑三轮再决定信不信:

for i in 1 2 3; do wrk -t 4 -c 200 -d 30s http://127.0.0.1:8080/ | grep -E "Requests/sec|Latency Distribution|Socket errors" echo "--- run $i ---" done

三次结果取中位数,作为这次压测的代表值。Socket errors 只要不是 0,这批数据就得标注或作废:errors 来自 reset、timeout 和连接失败,混入失败请求会压低延迟、虚增 QPS 可信度。就算三轮全跑完,数据波动也很正常,性能评估是个范围不是个点,我一般用中位数 ±10% 作为报告的置信区间。每次压测都留原始输出和参数记录,文件名按服务名加日期,后续回溯性能问题时能省掉大量猜测。

6.2 QPS 上不去时反推:看延迟曲线、连接状态和错误日志

QPS 上不去时,反推顺序是固定的:先看压测机 CPU 和网络是否先满了,再看服务端 CPU、内存和 I/O,最后看连接状态和服务端日志。wrk 输出里 Latency 随 -c 增加线性上涨、QPS 不动,基本是服务端并发饱和;Transfer/sec 长期贴着网卡上限但 QPS 上不去,说明响应体太大、带宽成了瓶颈。用 ss -s 确认服务端连接队列里有没有大量 TIME_WAIT,再翻 nginx error.log 里的 499/502,能区分到底是连接层拒绝还是应用层超时。

这套排查顺序我踩过不少次反向弯路:先是盯着服务端日志看半天,最后发现是压测机网卡先满了。从那之后我养成的习惯是压测前先在本机跑一遍天花板测试,确认负载机余量足够,才开始正式压测。希望这几条 wrk-4.1.0 经验能帮你在下次压测时少踩两个坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 13:42:41

用户侧储能优化配置与经济分析:基于Matlab的辅助服务收益建模

这两年做用户侧储能项目的前期评估,我几乎每次都会被问到同一个问题:"这个项目配多大容量、多长时间能回本?"这个问题看着简单,实际上牵涉到储能系统的容量规划、运行策略、参与辅助服务的收益测算、电池寿命折算等一系…

作者头像 李华
网站建设 2026/10/13 13:42:34

从无标题到清晰定位:项目命名与方向梳理实战指南

“无标题”这个词,在大多数人眼里就是一个占位符,是文档没写完、文件没命名的临时状态。但我这几年做项目下来,越来越觉得“无标题”本身就是一个极其真实、极其普遍、甚至值得被认真对待的项目状态。它不是一个空白,而是一团还没…

作者头像 李华
网站建设 2026/10/13 13:41:39

MySQL性能优化实战:从慢查询定位到索引与参数调优

MySQL性能优化一直是后端开发和DBA绕不开的核心话题。很多项目初期跑得飞快,一旦数据量过了千万级,或者并发一上来,慢查询、锁等待、CPU飙升各种问题就全冒出来了。这篇内容我从实际运维和调优的角度,把常用的优化手段、排查思路和…

作者头像 李华
网站建设 2026/10/13 13:41:12

弹窗表单非空校验抢跑:Element Plus的成因与五种根治方案

Element UI / Element Plus 用过的人,十有八九都遇到过这个情况:点开“新增”按钮,弹窗刚弹出来,表单里所有必填项下面齐刷刷亮起一排红字——“请输入xxx”“请选择xxx”。你明明一个字符都没输入,校验却像抢跑一样提…

作者头像 李华
网站建设 2026/10/13 13:39:22

从存了到用得上:一套寄生在浏览器和编辑器上的知识工作插件集

最近我把收藏夹里攒了三年的网页全部翻出来,发现真正还会再打开的不超过5%。这个结果并不意外,因为在知识工作这件事上,大多数人缺的从来不是信息量,而是调用能力。为此我花了两个多月做了一个名为knowledge-work-plugins的插件集…

作者头像 李华
网站建设 2026/10/13 13:38:42

K8S多Master集群Dashboard可视化部署与安全实践

1. 项目概述:为什么K8S集群必须配一个看得见的Dashboard? 在某高校实验室搭建高可用K8S教学平台时,我带的三组学生反复卡在一个看似简单却极其真实的问题上:kubectl get nodes 返回正常,但没人能说清当前有多少Pod正在…

作者头像 李华