Linux下wrk 4.1.0压测实战:参数、脚本与避坑指南

发布时间:2026/10/13 19:55:35
Linux下wrk 4.1.0压测实战:参数、脚本与避坑指南
简介wrk 4.1.0 Linux版性能测试工具包面向后端开发、运维及性能测试人员用于在高并发短连接场景下快速评估Web服务器的吞吐量、延迟与稳定性。工具本身基于C语言与线程模型轻量易用可独立压测也可配合JMeter、ab等工具形成分层测试方案。压缩包采用7z格式整体大小约168.72MB文件总数暂未标注内含wrk源码、Linux环境编译配置及基础使用说明文件类型以源码和文档为主目前暂无更细明细便于直接下载后编译运行。已有586人学习浏览具备一定实战参考价值。资料除工具本体外还围绕线程数、连接数、测试持续时间、自定义请求头及Lua脚本定制等核心参数展开说明并给出总请求数、吞吐量、响应时间等关键结果指标的解读方法能够帮助读者快速掌握从简单压测到复杂场景模拟的完整路径适用于服务端容量评估、性能对比与调优实践。1. wrk 4.1.0 on Linux为什么压测工具里我第一个推它做后端服务的人迟早要面对一个灵魂拷问你的接口到底能扛多少并发线上出问题之前有没有办法先摸到性能天花板我见过不少人拿着 JMeter 配半个小时的线程组、监听器结果压测脚本比业务代码还复杂。其实在 Linux 环境下有一个更轻、更直接、结果更干净的工具就是 wrk。这个标题里的 wrk 4.1.0是目前 GitHub 上最主流的一个版本一条命令就能对 HTTP 接口发起高并发压测能打出几万乃至几十万的 QPS 数据对于定位接口瓶颈、验证服务调优效果来说足够用。它不像 AB 那样只擅长简单 GET也不像 Locust 那样需要 Python 写场景wrk 的价值在于用极其简单的命令行参数配合一个可选的 LuaJIT 脚本就能模拟出接近真实的业务请求模型。适合谁用开发、测试、运维只要你在 Linux 上跑服务需要快速验证接口性能都值得花半小时把这套流程走一遍。注意wrk 是单机压测工具它用异步事件驱动模型能用一个进程打满几千连接但它不能像分布式压测平台那样模拟多地域的真实用户这是它的边界。下面我们直接在 Linux 上把它跑起来。2. 从源码编译 wrk 4.1.0一条命令装完但依赖得过关2.1 为什么不用包管理器直接安装常见做法是直接用 apt install wrk 或 yum install wrk但这样装的版本往往不是 4.1.0而且老发行版里的 wrk 可能停留在 4.0.x某些参数行为不一致。这个标题既然锁定了 wrk 4.1.0就按源码编译的方式来讲这也是社区里最常用的做法。4.1.0 相比 4.0.x 修正了一些 http parser 的边界问题在长 URL、特殊字符和 chunked 响应上表现更稳做压测的时候少一些“工具本身出错”的干扰。源码编译其实很快因为 wrk 是用 C 写的只依赖 OpenSSL 和 LuaJIT 的运行库。如果你的机器上没有装过编译工具链先执行下面这段# Ubuntu / Debian 系 sudo apt update sudo apt install -y build-essential libssl-dev git # 如果是 CentOS / Rocky Linux # sudo yum groupinstall Development Tools # sudo yum install -y openssl-devel git逻辑上这步是把 gcc、make、OpenSSL 头文件和 git 装好。wrk 的 Makefile 会在编译时自动找 OpenSSL 的 libssl.so所以 libssl-dev 或 openssl-devel 必须装否则你会在链接阶段遇到 libssl.so: cannot find 这类错误这是新手最容易卡住的第一道坎。参数说明build-essential 在 Ubuntu 里是一组包包含 gcc 和 makelibssl-dev 提供 sha256 等加密函数的头文件和符号链接。2.2 编译与验证4.1.0 这个目录名是怎么来的拿到源码的方式常见两种一种是从 GitHub 直接 git clone另一种是下载 tar 包后传到服务器上。以 git clone 方式举例git clone https://github.com/wg/wrk.git wrk-4.1.0 cd wrk-4.1.0 makegit clone 会拉取最新源码当前官方仓库的标签就在 4.1.0 上所以目录名里的 4.1.0 其实是对应仓库 tag。make 命令做的是编译加链接顺便把可执行文件放到这个目录下。如果你执行完没有任何 error 输出那二进制就已经在当前目录里了。可以验证一下版本./wrk --version看到 wrk 4.1.0 后再把二进制放到系统的 PATH 里这样任意目录下都能直接用 wrksudo cp wrk /usr/local/bin/这里的要诀是不要把整个仓库目录往 PATH 里塞而是只复制这一个可执行文件。wrk 是单二进制程序运行时不依赖源码目录里的任何东西这点比很多需要设置 LD_LIBRARY_PATH 的压测工具干净。如果你遇到了权限问题检查一下当前用户对 /usr/local/bin 的写权限或者直接改用 sudo install -m 755 wrk /usr/local/bin/wrk。编译过程在两核小机器上大约一分钟内完成。如果你用的是一台只有 512MB 内存的云主机make 的时候可能会因为内存不足被 OOM killer 干掉那就先加 swap 或换台规格高一点的机器编译完再拷二进制过去这个二进制放在别的同发行版 Linux 机器上也能直接跑。2.3 一条最小压测命令先确认工具真的能用装好之后拿一个你自己搭的 HTTP 服务试一下。比如本机有个 Nginx 或随便一个 Spring Boot 应用我们压一个最简单的 GET 接口wrk -t4 -c100 -d10s http://127.0.0.1:8080/api/ping这条命令的意思是用 4 个线程保持 100 个并发连接持续压 10 秒目标地址是本地服务的 ping 接口。参数说明-t 是线程数一般跟 CPU 核数持平或略多-c 是总的连接数不是每秒连接数wrk 会尽可能在这 4 个线程里均分这 100 个连接-d 是压测时长10s 算一个比较稳的起步值既能看到稳定 QPS又不会等太久。跑完你会看到一份报告报告里有 Latency 分布、每秒请求数Requests/sec和每秒传输字节数Transfer/sec。第一次跑的时候如果看到 Requests/sec 是几千甚至上万别急着高兴先看看 Latency 的 Avg 是不是已经高到了离谱的程度。如果 Avg 延迟只有几毫秒QPS 却有上万那你机器的网络协议栈和服务端都处于健康状态。新手容易踩的第一个坑在这里wrk 默认压测的是 HTTP/1.1并且会带上 Connection: keep-alive 头这意味着一个连接上会连续发多个请求。如果你想测的是短连接场景也就是每个请求都新建 TCP 连接那需要在请求头里显式改成 Connection: close。这个改动对 QPS 影响极大因为握手开销完全不一样我们在避坑章节再展开。到了这一步minimal 流程已经跑通了。但 wrk 比较出名的能力其实在参数组合和 LuaJIT 脚本上只靠刚才那条命令去压真实业务接口会得出偏理想化的数据。别急接下来我们把参数掰开揉碎讲一遍教你怎么针对不同场景调整出可信的测试方案。3. 压测参数怎么设从并发模型到结果解读一次讲透3.1 线程数与连接数不是越大越好背后是事件循环逻辑wrk 的性能模型基于 epoll 或 kqueue每个线程维护一个事件循环连接均匀分布在多个线程上。所以 -t 和 -c 的关系决定了每个线程要管多少个 socket。如果服务端配置了很高的 keep-alive 超时而你用的 -c 又特别大那么这些连接会一直占着服务端的连接池压测出来的 QPS 反而会下降。常见现象是把 -c 从 100 调到 1000QPS 先涨后跌看到一个明显的拐点。这个拐点不是服务端的极限而是你的压测线程因为维护太多空闲连接导致事件循环调度开销增大。我一般这样选先看机器核数-t 设成 CPU 物理核数最多乘 2。然后从 -c 小的开始试探比如 -c 50压 10 秒记录一次 QPS再 -c 100、-c 200、-c 500每次压完看 QPS 曲线。找到 QPS 开始不再上升的那个 -c就是这台压测机能打出的有效负载范围。不是说每次都要做这么精细的试探但对一个生产接口做正式压测时这组数据值得花两分钟跑一遍。这段“试探”过程 wrk 本身没有自动递增能力所以推荐的做法是写一个 for 循环脚本for c in 50 100 200 500 1000; do wrk -t4 -c$c -d10s http://127.0.0.1:8080/api/ping | grep Requests/sec donegrep 只提取每秒请求数方便对比输出。这里的观察点是QPS 随 -c 增长的趋势。如果你看到 -c 200 比 -c 100 反而下降说明服务端在 100 并发左右已经过载了继续加压只是把请求堆积在队列里响应时间变长QPS 上不去。3.2 压测时长和超时设置短压看峰值长压看稳定性-d 参数决定总压测时长。wrk 内部会在指定时间内尽可能多地打请求不适合用“总共发 N 个请求”这种思维来理解它。短压比如 10s看到的是峰值吞吐能力因为服务端刚开始接收请求各种缓存还没受太大压力。如果你想评估长时间运行下的内存泄漏或连接泄漏应该把 -d 拉到 300s 甚至 600s。还有一种情况是压测接口慢每次响应都要 3 秒以上这时候 10s 的压测可能只发出几十个请求统计意义不大。遇到这种接口先单独 curl 确认延迟再决定 -d 要不要加长。wrk 没有显式的“每个请求超时”参数它的超时控制靠的是 socket 层面的超时默认没有最顶层的截止时间所以慢接口会拉长整个压测的实际执行时间。比如 -d 10s 会被解释为“持续打 10 秒”但如果某些请求一直挂着wrk 会在 10 秒后停止新建请求再等已有请求返回完毕。这里推荐一个实战参数组合wrk -t8 -c400 -d60s --timeout 5s http://127.0.0.1:8080/api/query--timeout 5s 表示单个连接在 5 秒内没有响应就算超时。这个参数非常重要尤其是在压测数据库查询类接口时一旦服务端出现慢查询连接会越积越多最后压测机和目标机之间全是半开连接。设一个合理的超时能让你更早看到失败率而不是让压测卡在那里。还有一点容易看漏wrk 的 --timeout 是针对连接级别的不是请求级别的。在 keep-alive 模式下一个连接上连续发多个请求只要连接上还有数据流动它不会被判定超时。所以如果服务端每个请求都慢慢吞吞但一直没有完全断开wrk 会一直等在那里。这种情况压测报告里的超时数可能为 0但 Latency 的 Avg 已经高到几百毫秒得结合延迟分布一起判断。3.3 结果报告怎么读QPS 不是唯一标准延迟分位数才是wrk 默认给出的报告包含 Latency 的 Avg、Stdev、Max、/- Stdev以及 Requests/sec。但真正在做容量评估的时候延迟分布比平均值重要得多。平均值容易被少数慢请求拉高但你更想知道的是 p99 是多少也就是 99% 的请求都在多少毫秒内完成。wrk 默认不打印分位数但这不意味着没得看。使用 wrk 的 LuaJIT 脚本可以采集完整的延迟样本在压测结束后自行计算 p50、p95、p99。常见的做法是用一个名为 wrk2 的分支工具它自带 p99 输出但标题锁定了原版 wrk所以我推荐用 Lua 脚本解决。先看一个只改输出的简单 Lua 脚本把它存为 stats.lua-- stats.lua local stats require(stats) local function done(summary, latency, requests) print(p50: .. stats.percentile(latency, 50)) print(p90: .. stats.percentile(latency, 90)) print(p99: .. stats.percentile(latency, 99)) end wrk function() end注意以上写法有错误wrk 的 Lua 脚本里没有现成的 stats 模块不能 require 一个不存在的库。真实做法是在脚本里自己维护一个数组把每个请求的延迟记录下来压测结束回调里排序计算。-- delay_stats.lua local latencies {} function wrk.response(status, headers, body) local elapsed wrk.threads and yes or no end function done(summary, latency, requests) local arr {} for i 1, #latencies do arr[i] latencies[i] end table.sort(arr) local n #arr local function pct(p) if n 0 then return 0 end local idx math.floor(p / 100 * n 0.5) if idx 1 then idx 1 end if idx n then idx n end return arr[idx] end print(p50: .. pct(50) .. ms) print(p95: .. pct(95) .. ms) print(p99: .. pct(99) .. ms) end逻辑说明wrk 在每个请求完成时会回调 wrk.response需要你在脚本里记录耗时。但上面这段还没有记录耗时真正的耗时需要借助 wrk.thread 里的定时器或直接读取 wrk 内部延迟对象原版 wrk 的 Lua 扩展接口并不提供单请求回调里直接取毫秒数的方法。这个坑非常深也是很多人在网上搜“wrk p99 脚本”找不到可靠答案的原因。原版 wrk 的 Lua 上下文只暴露了 thread 的 get/stop 接口以及 setup、init、delay、request、response、done 几个回调但 response 回调里没有当前请求的耗时。那真实项目里怎么取分位数常见做法是放弃用原版 wrk 脚本取延迟分布直接看 wrk 输出的 Latency 分布直方图这个图在 -d 结束后会自动打印例如 Latency 的分布区间和累计百分比已经能看出大概。或者承接前面说的用 wrk2 分支来测。这篇文章明确锁原版 wrk所以这里我讲清楚边界原版 wrk 的分位数需要额外解析 stdout 的直方图不如直接用 wrk2。此部分不再深入。3.4 用 LuaJIT 脚本模拟真实请求POST、Header、Token一个脚本说清wrk 最大的爽点在于它能用 Lua 脚本定制请求体、请求头和请求频率这让它不再只是 GET 压测器。最常见的场景是压测一个需要登录态的 JSON 接口。假设你的接口要求 Header 里带 X-Token请求体是 JSON-- post_json.lua local headers {} headers[Content-Type] application/json headers[X-Token] a1b2c3d4e5f67890 local body {page:1,size:20} function wrk.request() wrk.headers headers wrk.body body wrk.method POST wrk.path /api/list return wrk.format(POST, wrk.path, headers, body) end逻辑说明wrk.request 函数会在每个请求发送前执行你可以动态修改 wrk 的 method、path、headers、body 字段而 wrk.format 用来把这些字段拼成完整的 HTTP 请求报文。这里参数说明wrk.headers 是一个表wrk.format 的第三个参数直接传入表即可不需要你自己拼字符串。但如果每个请求的 token 不一样比如要模拟不同用户并发访问上面这个脚本就失效了。解决办法是结合 thread:set 和线程内计数器做简单随机-- dynamic_token.lua local tokens {} function setup(thread) thread:set(token, tokens[1]) end function init(args) local f io.open(tokens.txt, r) for line in f:lines() do tokens[#tokens1] line end f:close() end function wrk.request() local idx math.random(1, #tokens) local h {} h[X-Token] tokens[idx] h[Content-Type] application/json return wrk.format(GET, /api/profile, h, nil) end逻辑说明init 函数在线程初始化时读一次 token 文件然后 wrk.request 每次随机取一个这样压测请求的 token 分布在 N 个用户上更接近真实场景。注意这里的 math.random 是 LuaJIT 自带的种子需要人工设置一下否则每次压测的随机序列完全相同。建议在 setup 里加上 math.randomseed(thread:get(id) os.time())。每条代码后的参数说明和这个随机逻辑都是正式压测里容易被忽略但影响结果的细节。写到这里wrk 的基本能力已经覆盖了接下来进入整个压测流程里最容易翻车的部分。4. 避坑与排查我在 wrk 压测中踩过的 5 个坑4.1 最大打开文件数限制莫名其妙报 too many open files现象wrk 命令看起来在跑但终端里刷出大量 unable to create socket 报错压测结果和预期相差十万八千里。原因Linux 默认进程可打开的文件描述符上限通常是 1024而一个连接至少占一个 fd。你指定 -c 2000但进程根本创建不了这么多 socket连接建不满QPS 自然上不去。解决执行 ulimit -n 查看当前限制。调大它的方式常见两种# 临时调整当前 shell 有效 ulimit -n 65535如果想永久生效修改 /etc/security/limits.conf在文件末尾加下面两行然后重启会话* soft nofile 65535 * hard nofile 65535还有一点要注意的是如果你是通过 systemd 运行的压测任务那么 service 文件里也要加 LimitNOFILE65535否则 ulimit 改了也没用。这是我在容器和 CI 平台里踩过的坑很隐蔽。4.2 压测机和目标机在同一台机器数据全是假的现象压测本机服务QPS 高得离谱但服务部署到测试环境后用同样参数压测QPS 掉了 80%。原因wrk 和目标服务共享同一个内核协议栈socket 通信不经过物理网卡TCP 握手开销大幅缩水。另外 CPU 资源也被 wrk 抢走一半服务端实际获得的 CPU 远远少于独立部署的情况。解决把 wrk 部署到独立的机器上至少与目标服务不要共享 CPU 核心。如果实在没有第二台机器用 taskset 给 wrk 和服务分别绑核例如# 绑 wrk 到 CPU0-1 taskset -c 0,1 wrk -t2 -c200 -d30s http://server-ip:8080/api/ping这样 wrk 不会去抢服务端所在的其他核。但要注意这个方法只能缓解不能完全模拟真实网络延迟。跨机房压测时不要忽略网络带宽和延迟wrk 的 Transfer/sec 会告诉你打满了多少带宽。4.3 压测结果里完全没有失败请求但接口明明报错现象wrk 的 Requests 数量在增长Latency 也正常但业务日志里看到大量 500 错误。原因wrk 默认只看 TCP 层的数据传输成功与否不校验 HTTP 响应状态码。一个 5xx 响应在 wrk 眼里就是一次正常请求完成。解决在 Lua 脚本里检查状态码并用 thread:stop 及时止损。示例如下function wrk.response(status, headers, body) if status ~ 200 then local ok, err pcall(function() wrk.threads:stop() end) end end如果错误只统计不停压可以用一个全局计数器local error_count 0 function wrk.response(status, headers, body) if status 500 then error_count error_count 1 end end function done(summary, latency, requests) print(5xx count: .. error_count) end这个脚本的价值在于压测结束后你能看到真实的业务失败率而不是只关注 QPS 一个数字。注意 wrk.threads:stop() 会立刻停止所有线程谨慎使用。4.4 压测 HTTPS 接口时 CPU 飙到 100%QPS 却不涨现象压测 HTTPS 接口wrk 所在机器 CPU 打满但 Requests/sec 远低于 HTTP 压测数据。原因TLS 握手和加密消耗了大量 CPU 资源。wrk 在压测 HTTPS 时默认使用单个 SSL 会话但每次新连接都要重新握手。如果并发很大TLS 握手开销就变成一个瓶颈。解决启用 wrk 的 TLS 会话复用参数。wrk 4.1.0 提供一个参数叫 -- 无其实 wrk 没有这个参数但你可以通过 Lua 脚本在 setup 阶段复用会话。不过社区更常用的方案是压测 HTTPS 时提升服务端的 TLS 会话缓存时间或者改用 HTTP/2? wrk 不支持 HTTP/2所以都解决不了 TLS 握手实质成本。正确思路是把 TLS 终结放到 Nginx/HAProxy 层wrk 压测时先压 HTTP 端口验证业务逻辑再单独压 TLS 终结层来评估证书和握手性能。如果你确实需要端到端压测 HTTPS那就接受 CPU 被加密消耗的现实并把 wrk 的 -t 数量提升到接近 CPU 核数。统计 CPU 使用率时区分 user 和 sys 两块若是高 sys 则多半是内核协议栈瓶颈可以考虑优化网络参数。4.5 压测结果不稳定每次跑的数字天差地别现象同一台机器同一条命令隔 5 分钟再压一遍Requests/sec 波动超过 30%。原因目标机上有类似的定时任务、日志清理、备份进程甚至别的同事也在压测同一台机器。wrk 的结果对系统扰动极其敏感。解决压测前用无人值守的方式清场。先看目标机负载top -b -n1 | head -20 ss -sss -s 能看到当前 socket 统计如果 TIME-WAIT 数量很大说明上一次压测的连接还没完全释放此时直接开始新一轮压测会受影响。建议每次压测间隔参数至少 30 秒。另外把压测命令和结果输出到文件方便对比多次执行的差异wrk -t4 -c200 -d30s --timeout 5s http://127.0.0.1:8080/api/ping result_$(date %s).txt加上时间戳方便溯源。如果连续三次压测数值都稳定在 -5% 范围内这组数据才值得写进报告。5. 压测数据的可信度验证与进阶技巧wrk 给出的 Requests/sec 到底可不可信我的习惯是不做单一工具依赖。用 wrk 压测完再用 curl 抽样验证单个请求的延迟或者换个并发模型交叉验证。交叉验证的常见做法是wrk 跑一组再用 curl 循环请求同一接口做对比测试time curl -s -o /dev/null -w %{http_code} %{time_total}\n \ http://127.0.0.1:8080/api/ping跑 10 次如果 curl 的 time_total 和 wrk 的 Latency Avg 相差不大数据的可信度就高。如果 wrk 显示的 Latency 是 5ms但 curl 是 200ms说明 wrk 的并发模式可能打到了不同服务节点或者服务端有连接相关的优化需要进一步排查。再看一个进阶技巧wrk 本身不直接支持压测 WebSocket但你可以用 LuaJIT 的请求回调模拟 WebSocket 的握手请求验证握手阶段的性能。真实 WebSocket 的消息收发压测需借助其他工具这一点要记住不要把 wrk 用在不合适的场景里。第四个进阶用法是压测结果自动出报告。我一般会把 wrk 的输出丢给后端分析用 Go 或 Python 解析 Requests/sec 和 Latency然后判定是否通过。这里分享一个极简的 Python 判断脚本思路import subprocess, sys output subprocess.check_output([ wrk, -t4, -c200, -d10s, http://127.0.0.1:8080/api/ping ], textTrue) for line in output.splitlines(): if Requests/sec in line: qps float(line.split(:)[1].strip().split()[0]) if qps 10000: print(PASS, qps) else: print(FAIL, qps)这个脚本的逻辑是压测结束后解析每秒请求数与阈值比较决定本次性能回归是否通过。你可以挂在 CI 流水线里每次代码上线前自动跑一遍基准接口的性能对比。我个人的习惯是压测报告只认两个指标组合——QPS p99 延迟 错误率缺一不可。QPS 再高p99 延迟超过 1 秒对用户体验来说依然是不可用服务错误率再低但 QPS 上不去说明系统在过度保护也需要调优缓存和服务端线程池配置。关于 wrk 4.1.0 这个版本我的整体评价是它是我见过的最快的轻量压测工具没有之一。编译一次到处可跑对比 JS 生态的压测工具和 AB它在并发模拟和请求自定义上的平衡做得最好。如果你的工作流里经常要验证 Linux 服务端的性能指标把 wrk 练熟省下的时间足够你去琢磨调优策略本身了。希望这篇文章能帮你在 Linux 压测这条路上少走几步弯路尤其是最后那节避坑清单每条都是我实际翻过车的教训值得收藏对照排查。下次压测前先看一眼 ulimit、再看一眼压测机和目标机是否同机、最后确认脚本里有没有对状态码做校验再跑压测不迟。本文还有配套的精品资源点击获取