首页 / 资讯中心 / 文章详情

Linux后端压测首选wrk:安装、参数调优与Lua扩展实战

Linux后端压测首选wrk:安装、参数调优与Lua扩展实战 ★ FEATURED ARTICLE
简介这是一份面向Linux平台的wrk 4.1.0高性能HTTP基准测试工具资源包主要服务于Web后端开发、运维以及性能测试人员用于评估高并发和短连接场景下服务器的吞吐量、响应时间与成功率。wrk本身用C语言编写具备轻量、高效、易扩展等特点与JMeter和ab相比更适合快速完成压测与瓶颈定位。资源包采用7z格式压缩大小约168.72MB内部文件明细暂未列出内容以工具详解为主线涵盖编译部署方法、常用命令行参数、自定义请求头、Lua脚本扩展测试场景、结果关键指标解读以及与其他主流压测工具的选型对比能够帮助读者从环境准备到实际测试逐步掌握。目前已有586人学习下载适合希望深入理解并发连接数、线程数、持续时间等参数对压测结果影响并借助Lua脚本定制请求的开发与运维人员。1. wrk 是什么为什么我在 Linux 后端压测里首选它我在 Linux 上做后端接口压测时选型清单里的首选性能测试工具是 wrk。之前用 JMeter装插件、配线程组、看聚合报告光准备环境就得半小时用 ab 又太粗只有总请求数和平均响应时间连接复用、延迟分布全看不出来。wrk 靠 epoll 和异步 I/O用几个线程就能管理几千个连接单机压到几万 QPS 很常见所以不管是上线前做容量摸底还是压一个接口看优化有没有效果我都会先把 wrk 拉起来跑一轮。这份 wrk-4.1.0-linux 源码包编译安装后用一条命令就能跑。正文把依赖、参数、Lua 扩展和常见坑都拆开讲适合刚接触压测的后端开发也适合要把压测固化成回归脚本的运维。2. 安装与编译老版本陷阱、三个依赖报错和第一次跑通2.1 先确认系统里是不是已经装了一个老版本 wrk很多发行版直接封装了 wrk。Ubuntu/Debian 系列执行 apt install wrk 就能装上但仓库里的版本往往落后几个大版本4.1.0 里修复的 TLS 握手问题、新增的 response 回调在老版本里全都没有。拿到这份源码后第一步不是急着编译而是先看本机已装的版本。提示执行 wrk -v如果输出类似 Version 0.7.0 这种旧版本建议直接用源码编译 4.1.0。两个版本的输出格式和 Lua 脚本行为差异很明显回归脚本混着用会得到错误结论。我在 Kali Linux 上就吃过这个亏apt 装的 wrk 跑 Lua 脚本时 response 函数完全不执行查了源码才发现是老版本行为。算上这个编译阶段真正要防的是四类问题老版本覆盖、缺 gcc、缺 make、缺 OpenSSL 头文件下面逐个说。2.2 编译依赖缺哪个库错误信息长什么样wrk 的编译依赖其实只有三样gcc、make、libssl-dev。wrk 会把 Lua 解释器编进二进制里所以不需要单独装 Lua唯一的外部库是 OpenSSL用来支持压测 HTTPS 接口时做 TLS 握手。Debian/Ubuntu/Kali 系sudo apt-get install -y gcc make libssl-devCentOS/Rocky 系sudo yum install -y gcc make openssl-devel依赖没装齐时编译错误比配置错误好认得多我遇到过的三类报错报 gcc: command not found缺 gcc装上再编译。报 make: command not found缺 make同样先装。报 fatal error: openssl/ssl.h: No such file or directory缺 libssl-dev 或 openssl-devel。最常踩的是这个别怀疑代码问题。wrk 没有 configure 脚本官方包直接就是 Makefile 组织。看到压缩包里没有 configure 文件别慌不需要执行 ./configure。2.3 编译、安装和版本验证tar zxf wrk-4.1.0-linux.tar.gz cd wrk-4.1.0 make -j$(nproc) sudo make install wrk -v逻辑说明tar 解包后直接 make-j$(nproc) 让编译任务跑满所有逻辑核编译会生成 src/wrk 可执行文件make install 默认放进 /usr/local/bin这样 PATH 里直接能调用。如果不想动系统目录也可以只执行 make然后把 src/wrk 手动拷贝到任意目录并加入 PATH。wrk -v 输出 Version 4.1.0 [wrk] 就说明编译成功。整个二进制是静态带上 Lua 解释器的复制到另一台同架构 Linux 上也能跑不用带一堆依赖。2.4 第一次压测把输出每一行读明白装好之后对本地 Web 服务跑一条最小命令wrk -t4 -c100 -d20s --latency http://127.0.0.1:8080/参数含义-t4 表示开 4 个线程-c100 表示维持 100 个并发连接-d20s 表示持续 20 秒--latency 会让 wrk 在结束时打印延迟百分位表。压测结束后输出类似这样Running 20s test http://127.0.0.1:8080/ 4 threads and 100 connections Thread Stats Avg Stdev Max /- Stdev Latency 9.80ms 4.20ms 96.00ms 86.94% Req/Sec 6.40k 480.00 7.80k 79.40% Latency Distribution 50% 8.50ms 75% 11.60ms 90% 15.20ms 99% 32.10ms 512640 requests in 20.00s, 76.45MB read Requests/sec: 25632.00 Transfer/sec: 3.82MB逐行说明Thread Stats 是每个线程内部统计求和后的分布Latency 的 Avg/Stdev/Max 反映响应时间Req/Sec 是每线程每秒处理请求数Latency Distribution 在加 --latency 后才出现50% 对应 p5099% 对应 p99比只看平均值可靠得多。Requests/sec 是最终吞吐Transfer/sec 是响应带宽。如果中途出现 connect/read/write/timeout 错误这里会多一行 Socket errors: connect 0, read 0, write 0, timeout 0。第一次跑只要能同时看到 4 threads、请求数和 Requests/sec 三行工具环境就算通了。wrk 的适用边界也要说清楚。它只支持 HTTP/1.1压 HTTP/2 接口会直接报错或退化到 HTTP/1.1它不会保存响应体只统计响应数据长度所以别指望用 wrk 做响应内容校验。这两种场景我分别会用 h2load 和带断言的脚本去补。wrk 的定位就是在 Linux 上快速、高并发地压 HTTP/1.1 接口选型选对工具后面才不翻车。3. 参数调优-t/-c/-d 的配合逻辑和延迟分布读法3.1 三个核心参数的配合逻辑wrk 的命令行参数不多真正决定压测形态的是 -t、-c、-d 三个。很多新手把 -t 当作“并发数”直接 wrk -t10000 -c10000然后看 wrk 自己先挂掉这就是把线程和连接搞混了。-t 是线程数-c 是连接总数wrk 会把连接平均分配到每个线程。设计压测参数时我一般按“核数”或“核数 × 2”来定线程数比如 8 核机器用 -t8 或 -t16再把连接数设为线程数的整数倍比如 -t8 -c400这样每个线程管 50 个连接。线程数开得比 CPU 核数多太多wrk 自身会花大量时间在上下文切换上测出来的不是服务上限是 wrk 的上限。常用参数表参数作用我的默认习惯-t/--threads压测线程数逻辑核数或核数 × 2不超过核数 × 4-c/--connections维持的连接总数线程数的 1050 倍按目标并发量调整-d/--duration压测时长30s 起步慢接口 60s-T/--timeout单个请求超时默认 10s慢接口适当调大防止误计超时-H/--header附加请求头需要自定义头时用可重复传-s/--scriptLua 脚本需要 POST、鉴权、随机场景时用-d 决定一次压测跑多久太短结果不稳定。我一般先跑 10s 当预热再正式跑 30s如果接口偶发慢请求比如最慢响应要 5s 才能回来时长至少覆盖 60s否则 p99 永远是截断的。3.2 延迟分布平均值会骗人百分位才是真话wrk 默认输出里的 Latency Avg/Stdev 很容易误导人。平均值会被大量低延迟请求拉低一个尾巴上的 1.5s 慢请求在 100ms 的平均值上几乎看不出来所以压测时一定要加 --latency让 wrk 打印 Latency Distribution 那张百分位表。读法上我习惯重点看 99% 和 50% 的差距比如 p50 8msp99 32ms说明绝大多数请求响应健康如果 p50 10msp99 900ms说明长尾严重接口或连接池可能在某些情况下退化了。只看 Requests/sec 高不高是不够的吞吐高但 p99 烂线上照样用户投诉。还有一类情况要警惕如果 Latency Distribution 里的 99% 数值接近 -d 设定的时长说明有一部分请求在压测结束前还没跑完p99 是被截断的要把时长拉长再跑。wrk 4.1.0 的百分位表只打印到 99%连 99.9% 都没有想要更精确的长尾数据可以在 Lua 脚本里拿延迟统计对象自己算这块在下一章讲。3.3 压测机系统参数ulimit、端口范围和 TIME_WAIT 复用wrk 自身能管理的连接再多也会被 Linux 的系统参数卡住。常见现象是压测跑到一半wrk 报错误或连接失败这不是被压服务挂了而是客户端这边先撑不住了。# 临时提高当前 shell 的文件描述符上限注意要在 wrk 同一个终端里执行 ulimit -n 1048576 # 扩大可用客户端端口范围短连接压测时非常关键 sudo sysctl -w net.ipv4.ip_local_port_range1024 65535 # 开启 TIME_WAIT 复用缩短端口回收时间NAT 环境慎用 sudo sysctl -w net.ipv4.tcp_tw_reuse1每条的作用ulimit -n 决定进程能打开多少文件描述符一个连接在 Linux 上至少要占一个 fd默认 1024 上限意味着 -c 超过 1000 就会碰到瓶颈ip_local_port_range 决定本地发起连接时可用的源端口范围连接数多时不改会报 Cannot assign requested addresstcp_tw_reuse 让处于 TIME_WAIT 的端口更快被复用但 NAT 后面开这个可能把旧连接的包复用到新连接上业务安全优先的话不建议在生产压测机默认开启。这些配置只在当前机器当前启动的压测任务里有效正式做压测时建议写进 /etc/sysctl.conf 再 sysctl -p 永久生效。4. Lua 扩展POST 请求、鉴权头和随机用户的四个模板wrk 内置的参数只适合 GET 压测。实际压测基本都要登录态、POST 体、随机参数这时候就要用 -s 指定 Lua 脚本。wrk 的脚本不是每个请求起一个虚拟机每个线程共享一个 Lua state函数是事件风格request 在每次发请求前被调用response 在每次收到响应后被调用done 在压测结束时被调用一次。4.1 最小脚本骨架method、body、headers 三个全局量-- post.lua固定 POST 请求 wrk.method POST wrk.headers[Content-Type] application/json wrk.body {page:1,size:20}逻辑说明wrk.method、wrk.headers、wrk.body 都是 wrk 暴露的全局变量脚本加载时设置一次之后每个请求都会带上。运行方式是 wrk -t4 -c100 -d30s -s post.lua http://127.0.0.1:8080/api/list。注意 wrk.headers 是 Lua table和普通 Lua 一样重复设置同一个 key 会覆盖。wrk 默认会带 User-Agent: wrk/4.1.0想伪装客户端的话在脚本里覆写 wrk.headers[User-Agent] 即可这个动作在 header 表里是一行字符串别在里面塞 \r\n否则可能被对端解析成额外请求头。4.2 带随机用户的压测预读账号文件别在 request 里读文件压测带登录态的接口时常见错误想法是在 request 函数里打开文件、按行读用户再关闭文件。request 每次请求都执行压几百并发时每个线程都在做磁盘 IOwrk 自己先变成了瓶颈测出来的数字全废。正确做法是在脚本加载阶段一次性读入内存request 里只做内存操作-- random_user.lua从账号文件里随机取一组带自定义 token 头 local users {} local f io.open(users.txt, r) if f then for line in f:lines() do local uid, token line:match(([^,]),([^,])) if uid and token then users[#users 1] { uid uid, token token } end end f:close() end math.randomseed(os.time()) function request() local u users[math.random(#users)] wrk.method GET wrk.headers[X-Uid] u.uid wrk.headers[X-Auth-Token] u.token return wrk.format() end逻辑说明users.txt 每行格式是 uid,token脚本加载时用 io.open 读取按逗号切分后放进 users 表request 里 math.random(#users) 取一个随机用户然后设置请求头和 methodwrk.format() 生成实际发送的请求。参数说明math.randomseed(os.time()) 要放在脚本顶层因为 wrk 每个线程都会执行一次脚本初始化不设置随机种子的话所有线程可能拿到同一串伪随机序列压测看起来像同一个用户在重复刷。wrk.format() 可以传参覆盖 method、path、body、headers在这里无参调用就沿用全局设置。4.3 用 response 回调统计非 200 和精确长尾延迟wrk 自带的输出不带 99.9% 百分位也不统计非 200 请求。response 回调补上这两块-- track.lua统计非 200 响应并打印 p999 延迟 local bad 0 local total 0 function response(status, headers, body) total total 1 if status ~ 200 then bad bad 1 end end function done(summary, latency, requests) io.write(string.format(非200数量: %d / %d\n, bad, total)) io.write(string.format(p99.9: %.2fms\n, latency:percentile(99.9))) end逻辑说明response 回调接收状态码、响应头和响应体这里只对状态码做计数done 回调在压测结束时拿到 summary 统计对象和 latency 统计对象。latency:percentile(99.9) 是 wrk 统计对象内置的百分位方法可以算出输出里没有的 p99.9这是把 wrk 结果接到质量门槛上的常用做法。注意 response 里的 body 在 wrk 4.1.0 中是字符串响应体很大时不建议在这里做字符串拼接会拖慢 wrk 线程需要校验响应内容时只取 body:sub(1, 128) 截取头部就好。4.4 每次请求动态改变 body 的写法压测上传或需要时间戳参数的接口时body 不能固定。wrk 允许每个请求重新生成 body-- dynamic_body.lua每次请求生成带时间戳的 JSON local users { u1, u2, u3 } function request() local body string.format({user:%s,ts:%d}, users[math.random(3)], os.time()) return wrk.format(POST, /api/report, nil, body) end逻辑说明request 函数内用 string.format 生成新的 body再通过 wrk.format 的第四个参数直接传给本次请求不修改全局的 wrk.body避免影响同一线程内其他请求。参数说明wrk.format 的完整签名是 wrk.format(method, path, headers, body)headers 传入 nil 表示沿用全局 headerbody 传入字符串就覆盖默认 body。注意 os.time 单位是秒同一秒内高并发场景多次请求的时间戳可能相同需要毫秒级唯一值可以配合计数器或 math.random 拼一个递增位进去。5. 避坑与排查五个翻车现场和对应修正5.1 连接与端口层面的两个高频坑坑一压到一半报 Cannot assign requested address。现象-c 开到几千后wrk 跑几秒就中断屏幕上出现大量 connect 错误Requests/sec 行要么缺失要么低得离谱。原因客户端端口耗尽了。Linux 默认 ip_local_port_range 通常只有两万多个端口每个连接都要占一个源端口短连接压测下几秒钟就能把端口用完。解决压测前先执行 ulimit -n 1048576再扩大端口范围 sysctl -w net.ipv4.ip_local_port_range1024 65535同时确认 wrk 所在机器和被测服务之间不是 NAT 环境。如果压测的是开启 keep-alive 的连接连接建立一次后复用这个问题基本不会出现。坑二压测 HTTPS 接口直接握手失败错误全记在 connect 里。现象wrk 压 http 一切正常改成 https:// 后大量 connect 错误服务端访问日志里看不到几个请求。原因wrk 编译链接的 OpenSSL 版本太旧或者跑在容器里缺证书导致 TLS 握手被拒。解决先单独验证 openssl s_client -connect 目标地址:端口 -servername 域名 /dev/null能正常输出证书链说明目标服务没问题那就是 wrk 编译环境的 OpenSSL 太老重装 libssl-dev 后重新编译 wrk-4.1.0别用 apt 装的旧版。5.2 Lua 脚本与结果可信度的三个坑坑三request 函数里做文件 IO压测吞吐上不去。现象加了 Lua 随机用户脚本后QPS 从 2 万掉到 2000CPU 使用率很高但都在 iowait。原因request 每次请求都触发磁盘读wrk 的核心优势是异步事件循环任何阻塞调用都会让整个线程停下来等 IO。解决按 4.2 的做法把用户数据在脚本加载阶段预读进内存。判断一只 Lua 脚本是否拖累 wrk 的方式很简单跑 -c1 单连接看 QPS再跑 -c100 看 QPS 是否线性上涨不上涨就检查脚本里有没有阻塞调用。坑四加了 --latency 还是觉得延迟分布不可信。现象延迟表里 p50 和 p99 几乎一样或者所有延迟低于 1ms和一个 RT 几十毫秒的接口明显矛盾。原因两个常见误用叠加一是压测和服务在同一台机器网络栈和调度相互干扰二是 wrk 默认 keep-alive连接已建立的情况下测到的是连接复用时的响应时间和用户侧每次新建连接的体验不是一回事。解决压测机和服务分开部署再用 Lua 脚本里 wrk.headers[Connection]close 压一次做对比两个结果差得多就说明连接建立开销占大头这时要看的是整体吞吐而不是单次延迟。坑五同一条命令两次压测结果差 20% 以上。现象连续执行两次相同命令Requests/sec 前者 18000 后者 22000连 p50 都变了。原因压测机上 CPU 频率波动、后台定时任务、wrk 线程被调度到不同核上都会造成误差。压测是最容易被“玄学”数字骗的活没有基线只有单次结果等于没测。解决同一组参数至少跑三次取中位数正式压测前先跑 10s 预热追求稳定的话用 taskset 把 wrk 绑定到固定 CPU 核上。下面这个命令循环三次输出三次 QPS 取中位数for i in 1 2 3; do wrk -t4 -c100 -d30s http://127.0.0.1:8080/api/ping 2/dev/null | grep Requests/sec | awk {print $2} done三次输出里的中间值就是这次压测的结论单次数字不作为上线依据。6. 把压测接进日常结果提取、阈值卡点与回归门槛wrk 的输出是给人看的不是给脚本看的。我一般把压测接到发布流程里先提取数字再卡阈值。6.1 用 shell 提取 Requests/sec#!/usr/bin/env bash wrk -t4 -c100 -d10s --latency http://127.0.0.1:8080/api/ping /tmp/wrk.log 21 qps$(grep Requests/sec /tmp/wrk.log | awk {print $2}) echo 当前 QPS: ${qps}grep 匹配输出里的 Requests/sec 行awk 取第二列就是数值。wrk 的错误信息会输出到 stderr21 是为了把错误也留档方便排查。6.2 阈值卡点低于基线就失败#!/usr/bin/env bash threshold8000 qps$(wrk -t4 -c100 -d10s http://127.0.0.1:8080/api/ping 2/dev/null | grep Requests/sec | awk {print $2}) # bc 做浮点数比较qps 为空时直接判失败 if [ -n $qps ] [ $(echo $qps $threshold | bc) -eq 1 ]; then echo 压测通过QPS${qps}未低于阈值 ${threshold} else echo 压测未通过QPS${qps:-0}低于阈值 ${threshold} exit 1 fi这里的 threshold 不要拍脑袋定我一般拿历史三次正常压测结果的中位数打八折作为门槛。改完接口、调完 JVM 参数、动过内核参数都先用同一份 wrk 命令压一遍数字低于门槛直接拦住别再往上线走。从那以后我每回动完服务端配置都会强制走一遍这个压测脚本把结果追加到历史文件里看趋势翻车概率低了很多希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站