
简介wrk是一款高性能HTTP基准测试工具可对Web服务器、API接口、负载均衡器进行压测帮助开发者定位性能瓶颈这套wrk.tar.gz已预编译好适合后端开发、运维和测试人员直接使用。压缩包采用gz格式大小约214.16MB内含wrk可执行文件及运行所需的LuaJIT环境解压后无需额外编译即可通过-c、-d、-t等参数快速发起压测。借助Lua脚本可模拟复杂请求模式、校验响应状态、设置延迟并输出每秒请求数、延迟分布、传输速率等核心指标也可用于对比不同配置、版本或技术栈下的性能差异。目前已有258人学习浏览适合需要快速搭建压测环境并希望深入理解wrk用法的用户。整体是一份开箱即用的性能评估工具资源能显著降低工具搭建成本便于快速获得关键性能数据。1. wrk.tar.gz一个开箱即用的 HTTP 压测工具到底解决什么问题压测工具这条路我见过太多“卡在安装”的版本要装各种运行时、要拉依赖、要 configure 之后 make 报错缺 openssl-devel。所以当看到“wrk.tar.gz 压测工具已经编译完”这个标题时最先值得高兴的点其实是——你不用在构筑环境这件事上交学费了。wrk 本身是一个基于 epoll 和线程池的 HTTP 基准测试工具能用很小的开销打满多核并发请求是后端、运维、测试同学在验证“服务能扛多少 QPS”时最顺手的工具之一。接下来就从 tar.gz 包的解压讲起把 wrk 的常用参数、结果解读和典型踩坑一次理清新手能跟着跑熟手能看到边界。2. 解开 wrk.tar.gz 并跑通第一次压测验证包可用与构建环境无关2.1 先看压缩包内容再决定解压到哪拿到 tar.gz 压缩包我建议第一步永远是tar -tzf wrk.tar.gz | head -30探一眼而不是直接解压。原因很简单打包方式多样有的包把 wrk 放在根目录有的会带一个项目名子目录还有的干脆把源码和二进制一起打进去。如果你解压后找不到 wrk只会浪费时间去猜。用 -t 参数只列出不解压能立刻看到顶层结构。tar -tzf wrk.tar.gz | head -30逻辑说明-t 是 list 模式z 表示 gzip 压缩f 是文件。输出里如果第一行是wrk或者./wrk说明二进制在包根如果第一行是wrk-4.2.0/wrk说明有顶层目录。后者解压时会创建一个wrk-4.2.0文件夹直接把 PATH 指向这个文件夹即可。如果你希望解压后直接落在目标目录可以用--strip-components1去掉顶层目录tar -xzvf wrk.tar.gz --strip-components1 -C /opt/wrk参数说明--strip-components1 表示解压时移除第一层目录适合那种“整个包在项目名目录下”的常见结构。如果旧版本 tar 不支持这个选项就手动 cd 进目录处理。下一步检查可执行性和动态库依赖mkdir -p /opt/wrk tar -xzvf wrk.tar.gz -C /opt/wrk ls -l /opt/wrk/wrk file /opt/wrk/wrk ldd /opt/wrk/wrk这里 file 的输出如果是ELF 64-bit LSB executable, x86-64说明架构匹配如果提示cannot execute binary file大概率是架构不对。ldd 则能列出 libssl、libc 等动态库依赖这是“已经编译完”的包最容易翻车的地方编译机器的 glibc 比运行机器新或者缺 libssl 兼容库运行时会报GLIBC_2.28 not found或libssl.so.1.1: cannot open shared object file。遇到这种问题优先找目标发行版对应版本的预编译包或者准备 Docker 环境而不是强行折腾。2.2 配置环境变量并验证版本二进制本身没问题后把它加入 PATH 是为了后续命令好写。常见做法是把解压目录 export 到当前 shell再写进 ~/.bashrc 持久化。export PATH/opt/wrk:$PATH wrk -v逻辑说明wrk -v 能输出类似wrk [epoll]的版本信息同时验证 PATH 是否生效。如果返回 command not found先检查解压目录是不是真的存在 wrk 可执行文件以及有没有 chmod x。某些压缩包在 Windows 下解压后再传到 Linux会丢失可执行权限用 ls -l 看 x 位没有就补上chmod x /opt/wrk/wrk这里有个技能细节如果 wrk 报 GLIBC 版本错误但你的机器上只有旧版系统库不要听网上的“软链 libc”。把 /usr/lib64/libc.so.6 软链到新版本会造成系统问题。正确做法是在新环境上重新构建或者用 Docker 镜像来跑预编译包。顺便说一句预编译包的“预”是相对源码构建而言它依然依赖系统 C 库和 OpenSSL并不是静态二进制。2.3 跑第一个最小压测本机静态服务冒烟把 wrk 放进 PATH 后我习惯先用本机静态服务确认每一条链路都通而不是直接压远端的同事服务。因为如果第一次就压远端出了网络错误你会分不清是包的问题还是目标服务的问题。这里用 Python 临时起服务最省事# 终端 A起一个静态服务 python3 -m http.server 8000 # 终端 B压测 wrk -t4 -c100 -d10s http://127.0.0.1:8000/逻辑说明-t4 表示用 4 个线程-c100 表示总共 100 个连接-d10s 表示持续 10 秒。wrk 在启动时会平均分配连接4 个线程每个约 25 个连接然后每个线程用 epoll 同时处理这些连接的收发。10 秒时间足够产生几万请求但又不至于把临时进程拖崩溃。输出里你会看到 Requests/sec、Latency 等字段这里先不深究先看它有没有正常跑完。如果命令直接报connect() to 127.0.0.1:8000 failed优先确认服务是否活着用curl -v http://127.0.0.1:8000/试试。如果 curl 正常但 wrk 失败检查有没有防火墙拦截 wrk 所在机器的源地址或者目标机是否限制并发连接数。无论如何第一跑的目标只是“wrk 能产生流量”而不是压出多大数字。2.4 读懂第一次压测的输出格式wrk 的输出样式大致如下注意数字会因机器和服务端不同而变Running 10s test http://127.0.0.1:8000/ 4 threads and 100 connections Thread Stats Avg Stdev Max /- Stdev Latency 2.42ms 1.05ms 25.12ms 91.23% Req/Sec 9.80k 1.20k 12.40k 87.50% Latency Distribution 50.000% 2.20ms 75.000% 2.80ms 90.000% 3.90ms 99.000% 7.10ms 39220 requests in 10.00s, 30.34MB read Requests/sec: 3922.12 Transfer/sec: 3.03MB这个输出可以拆成三段看第一段是线程统计Latency 行表示每个请求的响应耗时Req/Sec 行表示每个线程每秒构造的请求数第二段是延迟分布也就是加了 --latency 才有的百分位第三段是汇总Requests/sec 是最常被引用的核心指标。39220 requests in 10.00s是累计请求数Requests/sec是每秒速率两者不要搞混。如果这里看到Socket errors字段那是连接失败或超时的统计比如connect 0, read 5, write 0, timeout 10说明有超时。第一次冒烟出现少量超时可能是服务端慢如果大量超时就要回到服务端日志里排查了。这一轮先确认 wrk 可用细节后面章节继续。2.5 预编译包的完整性与分发别在传输环节丢权限tar.gz 包“已经编译完”不等于一定能跑你在拷贝它到目标机时还可能踩最后一个坑文件权限丢失。比如从 Windows 共享目录拷过来或者用某些传输工具解压会丢掉 x 位。应对办法是解压后统一补权限并核对文件校验值# 计算压缩包校验值防止传输损坏 md5sum wrk.tar.gz # 解压后做一次冒烟验证可执行 tar -xzvf wrk.tar.gz -C /opt/wrk /opt/wrk/wrk -v如果团队里多人共用一个包建议把 md5 值贴在 README 里避免某次传输坏了包还怪 wrk 本身。跨平台传输后如果发现 wrk 被识别为 shell 脚本或者“No such file”先 file 一下再决定不要盲目重传。我已踩过“用 ftp 二进制模式传 tar.gz 后 md5 都不一样”的血泪经验所以现在每次都会先做校验。3. wrk 核心参数详解线程、连接、时长决定流量模型3.1 必会参数清单-t、-c、-d、-s、-T、--latency先来一张参数速查表后面所有配置都是这张表的组合参数作用典型范围使用注意-twrk 线程数等于压测机物理核数超过两倍核数反而降性能-c并发连接总数100~10000会被 -t 平分需配合文件句柄限制-d压测时长5s~5m太短样本不足太长可能影响线上-sLua 脚本路径按需用于模拟 POST、随机参数、鉴权-T单请求超时秒默认 2s慢接口请调大避免误记超时-H附加请求头可写多个常用于 Authorization、Content-Type--latency打印延迟分位建议总是加没有它只能看平均值无法看长尾wrk 的并发模型是“多线程 epoll”-t 决定线程数每个线程创建属于自己的事件循环-c 决定连接总数启动时平分到每个线程。举一个直观例子-t4 -c100线程 0 负责 1~25 号连接线程 1 负责 26~50 号依此类推。每个线程在自己的 epoll 里同时等待这些连接的可读可写事件所以连接数才是并发上限线程数决定的是压测机并行度。新手最容易犯的错是以为 -t 就是并发结果把 -t 开满但 -c 只有 10压制力完全不够。3.2 把业务场景翻译成 wrk 参数面对不同场景我是这样起步的# 场景 1接口冒烟判断能不能连通以及基本状态码 wrk -t2 -c20 -d5s --latency http://target/api/health # 场景 2常规容量评估压 30 秒看稳定吞吐 wrk -t4 -c200 -d30s --latency http://target/api/items # 场景 3高并发探拐点逐步加压直到延迟上涨 wrk -t8 -c2000 -d60s --latency http://target/api/items # 场景 4长连接保持观察连接耗尽和内存增长 wrk -t4 -c100 -d300s --latency http://target/api/event逻辑说明场景 1 的 -c20 是为了不产生压力只验证“能正常返回”场景 2 的 -c200 是一个常见起点大多数 HTTP 接口在这个负担下能看到有意义的吞吐场景 3 的 -c2000 适合压力测试要看服务端在大量并发下是否依然稳定场景 4 的 -d300s 是持续 5 分钟看长连接下服务端会不会把连接池打满。所有这些场景里-t 都保持在压测机核数附近不追求高线程数。这些参数怎么确定我一般会先看业务值班监控里的在线连接数峰值。如果线上网关的并发连接数平均是 5000那你压测起步就至少 -c2000不能只压到 50 个连接否则根本没有代表性。另一种方法是二分法先 -c100如果服务端延迟很低、QPS 增长很快就加 -c500、-c1000 逐档上调直到 p99 出现明显拐点。这种方法比一次直接压 5000 更容易定位容量曲线。3.3 线程与连接的比值经验以及 -T 超时的陷阱线程数和连接数的搭配我总结一个粗略公式让每个线程管理 100 到 500 个连接也就是 -c 除以 -t 的商在这个范围。如果小于 100连接太少压测机大量线程空转如果大于 500单线程要处理的事件太多延迟和调度开销会上升。举例压测机 8 核、目标希望并发 800那么 -t8 -c800 或 -t8 -c1600 都合理如果目标希望并发 5000那 -t16 -c5000 比较合理前提是压测机确实有 16 核以上。-T 参数最容易被忽略。wrk 默认每请求超时 2 秒2 秒内没收到完整响应就计入超时失败。很多内部接口因为要做二次查询、调用下游 RPC单请求可能就要 800ms偶发 2 秒超时并不代表系统故障。如果把 -T 从默认 2s 调成 5s 或 10s你会发现 Requests/s 和 error 率都有变化。所以压测前先想一句话这个接口在正常情况下的 p99 是多少然后让 -T 至少是它的 2 倍。3.4 附加请求头与延迟分位的使用习惯压测真实接口时几乎都要加鉴权头。wrk 的 -H 可以写多个比如同时带 Authorization 和 Content-Typewrk -t4 -c200 -d30s --latency \ -H Authorization: Bearer YOUR_TOKEN \ -H Content-Type: application/json \ http://target/api/items如果不带 Authorization很多接口直接返回 401Requests/s 再高也不是业务吞吐而是网关拒绝速率。所以要习惯在每次压测开始前用 curl 先试一次真实请求确认鉴权和 body 都被服务端接受再上 wrk。这个“先 curl 后 wrk”的顺序能省掉大量的无效压测。--latency 参数我基本每次都会加。原因是默认输出只有平均值和 Stdev平均值最容易骗人可能平均值 5ms 但 p99 是 500ms你只看平均值会误判系统良好。加上 --latency 后p50/p75/p90/p99 四档分位直接打在结果里配合第 4 章的读法才能下结论。4. 压测结果解读从 Requests/s 和 Latency 推演服务容量4.1 Requests/s 是结果不是目标面对一个 wrk 输出第一步是问自己这个 Requests/s 是服务端的能力还是压测机的上限如果 wrk 进程已经打满了压测机的所有 CPU 核那 Requests/s 只能证明“压测机极限在这”不能证明服务端扛不住更大流量。确认方法很简单压测的同时另开终端跑top -bn1 | head -20看 CPU 占用。wrk 是单进程多线程它会吃 CPU理论上一个核能构造几万请求所以压测机核数不足时瓶颈往往不在服务端。还有一个指标可以辅助判断Thread Stats 里的 Req/Sec 标准差。如果每个线程的 Req/Sec 差异很大比如一个线程 50k、另一个 12k说明连接分配不均衡或线程被调度打断了。如果所有线程的 Req/Sec 都贴近 Max且压测机 CPU 已满那就要先升级压测机再谈业务容量。4.2 Latency 的分位p50、p90、p99 各有各的用法wrk 输出里的 Latency Distribution正是你判断体验的关键。我的习惯是p50 看整体速度p90 看一般峰值p99 看最差体验。核心接口的 p99 超过 200ms 就是危险信号如果 p99 超过 500ms说明存在严重长尾真实用户会频繁抱怨。以下是一个对比示例场景p50p99判断A2ms10ms健康B10ms200ms偶发长尾C20ms800ms明显有问题注意这里的 ms 只是示意具体数值取决于业务。关键是看 p99 与 p50 的放大倍数超过 5 倍就要排查服务端的内部抖动比如定时任务、GC、缓存过期。wrk 输出的 Latency 是整个请求从开始到响应的耗时包含网络往返所以局域网压测与公网压测的 p99 不能直接对比公网压测的 p99 通常比局域网大一个量级。4.3 用多档压力做容量曲线而不是赌一次压测一次压测得到 5000 QPS 只是“在某个并发下的一个点”不是容量曲线。我常用的做法是连续压多档并发记录每一档的 Requests/s 和 p99for c in 100 300 600 1000; do echo connections: $c wrk -t4 -c$c -d20s --latency http://target/api/items done这段循环会依次以 100、300、600、1000 个连接各压 20 秒。输出很多建议把每次的 Requests/sec 和 p99 手动记到结构化表格里。通常你会看到两种形态形态一是 QPS 随连接数线性上涨p99 平稳说明服务还有余量形态二是 QPS 到某一档后不再上涨甚至回落p99 快速抬升这就是容量拐点。拐点处的 QPS 是你能承诺的业务吞吐上限拐点之前的斜率则是服务弹性。注意循环里的 -d20s 是最短建议时长。5 秒或 10 秒的样本太少服务端缓存和线程池刚热起来就结束了得到的数字波动大。20 秒到 30 秒比较稳每档之间停 5 秒让连接和内核表项释放一下避免上一档的 TIME_WAIT 影响下一档。如果你要求更严谨每档跑三次取中位数能过滤掉偶发抖动。4.4 一个容易误判的细节累计请求数、吞吐速率和并发上限wrk 输出里的39220 requests in 10.00s和Requests/sec: 3922.12常常让刚上手的人混淆。累计请求数是整个测试窗口完成的请求总量每秒速率是平均吞吐。而“并发”是 -c 参数设定的连接数。这三个数字的定义完全不同-c 是同一时刻最多维持的连接数Requests/sec 是每秒钟完成请求的速率累计请求数是速率乘以时间。如果有人说“我要压 1 万并发”他真正需要的可能是 -c 10000而如果他对某接口预期每秒 1 万次调用那就是 Requests/sec 的目标两者不是一回事。把这三个量分开后你才能设定合理的压测目标。容量评估的目标通常有两个盯着一个是吞吐上限一个是延迟拐点。典型结论是“在 -c 1000 下吞吐 8200 QPSp9945ms-c 2000 时吞吐 8200 QPSp99280ms可以认为容量拐点出现在并发 1000~2000 之间”。这样数字化之后产品和研发拿到结论也好做决策。5. wrk 压测避坑指南5 个最容易翻车的细节写这一章是因为我在实际压测中几乎每个坑都踩过下面的现象和解决方式可以直接照搬排查。5.1 现象报错 “connect() failed: Cannot assign requested address”压测刚启动几秒wrk 就打印大量 connect 失败服务端日志没有异常。问题几乎出在压测机自身的内核网络参数短时间创建大量连接本地端口耗尽。Linux 默认临时端口范围大约是 32768~60999约 2 万个端口一旦积压大量 TIME_WAIT新连接就分不到端口。解决方式是在压测机上调整三个参数ulimit -n 65535 sysctl -w net.ipv4.ip_local_port_range1024 65535 sysctl -w net.ipv4.tcp_tw_reuse1 sysctl -w net.ipv4.tcp_timestamps1逻辑说明ulimit -n 提高文件描述符上限否则连接数大了直接 “too many open files”ip_local_port_range 扩大可用源端口tcp_tw_reuse 让内核在安全条件下复用 TIME_WAIT 连接减少等待。这个组合只建议在压测机和临时环境用生产服务器的网络参数要按公司规范评估后再改。改完可以再用ss -s看 TIME_WAIT 数量是否下降。5.2 现象加大 -t 后 QPS 反而下降压测机是 8 核第一次 -t4 跑了 4.2 万 QPS把 -t 提高到 16 后反而掉到 3.5 万。原因是线程数超过物理核数后线程切换开销占了上风加上 -c 没变每个线程只分到不到 100 个连接线程之间彼此抢 CPU整体吞吐不升反降。解决方法是先用nproc看核数让 -t 不超过物理核数的两倍。更推荐的配置是让 -t 等于核数然后通过加 -c 来提升压力。如果确实需要更大连接数比如 -c5000优先在另一台机器上再起一个 wrk 实例把两个实例汇总成总吞吐也比在一个实例里塞 32 个线程更可控。压测机本身也要预留 20% CPU 余量否则 wrk 线程被调度延迟影响结果会抖动。5.3 现象同一接口两次压测QPS 波动超过 20%同一命令跑两遍结果差 20% 以上甚至第一遍 5 万第二遍 3.9 万。常见原因有三类服务端缓存状态变化第一遍把热数据缓存命中第二遍冷启动未命中压测机或服务端有其他任务抢占 CPU网络设备或中间层做了限流。排查时先做三遍如果三遍都稳就取中位数如果三遍里有离群值单独看离群时段的监控。另外要确认 wrk 默认的 keep-alive 是否真的在工作。wrk 默认每个连接复用但如果接口返回Connection: close或者中间层强制关闭那每次请求都要重新建连QPS 会明显偏低且波动大。检查方法很简单压测的时候另开一个终端看ss -tn | grep 8000连接数应该是稳定在 -c 附近如果连接数频繁抖动说明 keep-alive 没维持住。这时候要么让服务端开启 keep-alive要么在 Lua 脚本里关闭连接但要注意后者压出的数字是本机建连能力不是业务吞吐。5.4 现象压 POST 接口全是 401/400GET 却正常wrk 默认发 GET 请求。当你去压 POST 接口时如果不做任何设置服务端会直接 405 或 400。即使设置了 method缺少鉴权头或者 body 格式不对也会得到 401。这个坑不属于 wrk 故障而是压测脚本没跟上。先在最简单的层面修复在启动命令里加 -H 和 -s。比如用一个只设置 method 和 body 的 Lua 脚本下一章有完整版配合 -H Content-Type: application/json 使用。如果接口要求动态 token那就不能用一个写死的 header要在 Lua 的 request 函数里注册一个获取 token 的闭包每次更新 token 再构造请求。总之压测前先用 curl 完整复现一次成功请求把 curl 的参数一一翻译成 wrk 的 -H 或 Lua 脚本不要凭空猜测。5.5 现象wrk 打出来的结果业务方完全不认这是最伤士气的一种“坑”。你用自己的 wrk 命令压出 1 万 QPS业务方说线上高峰 2000 就报警。问题通常不在 wrk而在压测模型和真实模型差异太大wrk 发了极简 GET真实业务带几十 KB 的请求体、鉴权、下游依赖、读写数据库、缓存冷热交错。所以 wrk 结果只能叫“接口上限”不叫“系统容量”。缓解方式是把压测模型做真用 Lua 脚本模拟真实的请求体大小和 header压测的请求路径带上网关、数据库等完整链路压测时跑多档并发记录拐点而不是报一个单点峰值。交付结论时一定要附上当时使用的参数、脚本、服务端监控截图并写清楚“在 XX 条件下接口达到 X QPSp99 为 Y”。这样业务方至少知道这个数字的边界是什么不会拿它直接当线上极限。6. 进阶用 Lua 脚本压测 POST 接口并验证压测数据可复现wrk 的 -s 参数是它区别于很多压测工具的关键你可以把真实业务请求“翻译”成 Lua 代码让压测流量尽量接近线上。一个最小 POST 脚本如下-- post.lua wrk.method POST wrk.path /v1/order wrk.headers[Content-Type] application/json function request() local body {uid:12345,sku:A-100} return wrk.format(POST, wrk.path, wrk.headers, body) end启动命令wrk -t4 -c100 -d30s --latency -s post.lua http://target-api.example.local逻辑说明wrk 会在每次需要发送请求时调用 request 函数返回值作为本次请求的内容。wrk.format 是官方内置方法参数分别是 method、path、headers、body。如果业务需要每个请求用不同 uid可以在 request() 里拼接 body比如{uid: .. uid_counter .. }但要注意递增计数器时控制并发安全否则可能重复或错乱。如果你想模拟登录态可以在进来时先请求一次 login 接口把返回的 token 存到全局变量再在后续 request() 里带上。验证压测数据是否可信我的习惯是同一个参数跑两次间隔 1 分钟对比 Requests/s 和 p99。两次相对偏差在 3% 以内说明环境稳定超过 10% 就按第 5 章的波动排查不急着下结论。每次压测的命令、脚本、结果我都按日期存在同一目录改动前跑一次、改动后跑一次对照差值和 log比记忆可靠得多。这套方法不一定最优但帮我在多次压测里少走了很多弯路希望也能帮到你。本文还有配套的精品资源点击获取