☰
wrk压测工具:从安装到实战,轻松掌握HTTP并发性能测试
2026/10/10 9:41:13 网站建设 项目流程

你是不是也想快速知道自己的服务器到底能扛多少并发?我在实际工作里最常用的一套HTTP压测工具,不是什么重量级的商业压测平台,而是一个单文件、无依赖、用法非常简单的小工具——wrk。它是一个基于事件驱动的开源HTTP基准测试工具,用C语言写的,安装包只有几十KB,核心线程模型非常轻量,能在一个进程里轻松打出几万甚至十几万的QPS。这篇文章就是围绕wrk写的一份实操入门总结,从安装到跑通、从参数含义到Lua脚本玩法、从常见坑到排查思路,全都给你捋清楚。适合刚接触HTTP压测的后端开发、运维同学,也适合想评估自建服务性能的独立开发者。

wrk用了大约五年,踩过不少坑,也拿它给一些真实业务做过压测与容量评估。相比AB、JMeter这类工具,wrk最打动我的一点是“上手零成本”:下载源码,make一下就能用,什么都不用配置。配合Lua脚本之后,它又能覆盖POST请求、带Token鉴权、并发模拟等真实场景,完全满足日常开发阶段的性能摸底需求。下面我从头讲,保证你照着操作就能跑出第一个压测结果。

1. wrk到底是个什么东西、为什么值得入门

1.1 先认识一下wrk

wrk是一个开源的HTTP基准测试工具,项目托管在GitHub上,主要作者是某个知名技术社区的老牌开发者。它的核心卖点在于“高性能”和“极简”。高性能是因为wrk内部基于epoll(Linux)和kqueue(macOS)这类事件通知机制,同时配合多线程模型,能够用非常少的系统资源发起大量并发连接。极简体现在使用方式上,一条wrk命令就可以完成从建立连接到输出统计报告的全过程。

很多人第一次看到wrk,会拿它和ab(Apache Bench)对比。ab是Apache自带的老牌工具,因为很多系统默认装了Apache所以用起来很方便。但ab在并发模型上是同步阻塞的,默认情况下每个连接占一个进程或线程,连接数一高就直接被系统资源卡死了。wrk不同,它是异步事件驱动,几千个并发连接也只是在几个线程里轮询处理,性能完全不在一个量级。

我用一个生活化的类比来解释wrk的模型:ab像是开了一个大饭堂,每来一个客人就雇一个服务员单独招待;wrk则像几个手脚麻利的老服务员,一个人同时接待十来桌客人,谁举手就过去处理谁。同样是接待500个客人,前者需要500个服务员,后者只需要10个服务员就够了。这就是wrk在压测时连接数开得很大、但CPU和内存资源依然很紧张的原因。

1.2 wrk能解决什么问题

wrk主要解决的是“我的HTTP接口到底能承受多大的访问压力”这个问题。具体来说,它可以帮你回答这几个问题:

  • 某个接口在单机状态下每秒能处理多少请求(QPS)
  • 请求的平均延迟、P90、P99延迟分别是多少
  • 在某个并发连接数下,服务是否还能稳定返回200
  • 代码上线前后,性能有没有明显变化
  • 同一台服务器上,不同接口或不同业务链路的性能瓶颈在哪里

这套能力在开发阶段、上线前自测、容量评估、性能回归测试里都很有用。我之前在某公司负责一个交易中台系统,每次大促前都要对核心交易链路做一轮压测,用的就是wrk加上一些简单的脚本组合,几分钟就能出一份初步性能报告,先摸个底再决定要不要上重型的全链路压测工具。

1.3 简洁背后的高性能原理

wrk为什么能在如此轻量的体积下打出很高的压力?核心在于它的网络模型。wrk在启动时会创建一组线程(默认每个CPU核心一个),每个线程里维护一个事件循环,基于epoll监听所有socket的事件。当某个连接上有数据可读或可写时,事件循环会立即分发对应的回调函数处理。线程之间通过管道通信来协调工作任务,连接本身也是异步非阻塞的。

这种设计的直接好处是:不需要为每个连接创建线程或进程,线程数不会随着并发连接数的增加而增加;CPU忙在计算和解析上,而不是忙在线程切换。所以wrk适合在单机低配环境下就能模拟大量的并发客户端,这对我们日常做性能摸底来说非常方便。

2. 安装与第一个压测:从零到跑通

2.1 编译安装wrk

wrk的安装方式非常简单,但有一点要注意:官方仓库并没有直接提供编译好的二进制包,所以通常我们采用源码编译的方式。好在它依赖极少,只需要系统里有gcc和make即可。Linux环境下操作步骤如下。

# 第一步:安装编译工具(如果系统里已经有了可以跳过) # Debian/Ubuntu sudo apt-get update sudo apt-get install -y build-essential # CentOS/RHEL sudo yum groupinstall -y "Development Tools" # 第二步:克隆源码并编译 git clone https://github.com/wg/wrk.git wrk cd wrk make # 第三步:将编译好的二进制放到PATH目录下,方便全局调用 sudo cp wrk /usr/local/bin/

编译过程通常十几秒就能完成,因为wrk源码本身非常小。如果你在macOS上操作,也可以直接用Homebrew安装:brew install wrk,安装完直接使用。Windows环境稍微麻烦一点,官方不直接支持Windows,需要用WSL跑Linux子系统,或者在Windows上装一个Linux虚拟机,不建议折腾原生Windows版本,因为wrk的网络模型强依赖Linux系统的epoll机制,在Windows上的表现和功能完整性都不理想,除非你有很强的兼容需求。

2.2 跑出你的第一个压测数据

安装完成后,先跑一个最简单的命令试试,压测一下本地Nginx首页:

wrk -t4 -c100 -d10s http://localhost/

这个命令的意思我拆开解释一下:-t4表示 wrk启动4个线程,-c100表示维持100个HTTP连接,-d10s表示压测持续10秒。最后的URL是压测目标地址。跑完后终端上会输出一份完整的压测报告,包括延迟分布和请求量统计。

如果你这时看到终端报错“wrk: command not found”,大概率是编译后的二进制没有放到PATH里,检查一下/usr/local/bin是否在环境变量中,或者直接使用编译目录下的./wrk命令来执行。

2.3 看懂压测输出报告

第一次跑wrk的人,看到输出结果会有点懵,那一堆数字到底什么意思?我以一次真实的本地压测输出为例逐行解读:

Running 10s test @ http://localhost/ 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 5.42ms 3.18ms 87.34ms 86.23% Req/Sec 4.80k 318.65 5.61k 80.72% 192427 requests in 10.02s, 41.82MB read Requests/sec: 19203.12 Transfer/sec: 4.17MB

第一行说明了测试持续时间和目标地址。第二行告诉我们用了4个线程、100个连接。接下来是线程统计信息:Latency行显示的是请求延迟的统计,平均5.42毫秒,标准差3.18毫秒,最大延迟87.34毫秒,86.23%的请求延迟在平均值加减一个标准差的范围内;Req/Sec行表示每个线程每秒能处理的请求数量,这里平均每个线程每秒处理4800个请求,最大达到5610个,这个数据可以帮助我们判断线程压力是否均匀。

再往下是最核心的信息:192427 requests in 10.02s, 41.82MB read表示整个压测期间总共发送了192427个请求,读取了41.82MB的响应数据。Requests/sec: 19203.12就是本次压测最重要的指标——QPS(Queries Per Second),也就是每秒可以处理19203个请求。Transfer/sec: 4.17MB表示每秒吞吐带宽。

在实际工作中,我通常首先看QPS,其次看Latency里P99值的变化。QPS高但延迟波动大,说明服务可能处于不稳定状态,接口抖动会比较明显。

3. 核心参数怎么用、参数之间怎么搭配

3.1 wrk常用参数速查表

wrk的命令参数不复杂,常用的更少,我整理了一份速查表方便你对照使用:

参数含义示例
-t线程数-t4 表示使用4个压测线程
-c并发连接数-c100 表示建立100个并发连接
-d压测持续时间-d10s 表示持续10秒
-s指定Lua脚本-s post.lua 表示使用post.lua脚本
-H添加请求头-H "Authorization: Bearer xxxxx"
--latency输出延迟百分位明细--latency 会打印P50/P75/P90/P99数据
-T连接超时时间(默认2秒)-T5s 表示连接超时设为5秒

--latency这个参数非常实用,强烈建议每次压测都加上,它会额外输出一组延迟百分位数据,能更清楚地看出长尾请求的表现。

3.2 线程数和连接数到底怎么搭配

很多人一开始不知道-t和-c到底怎么设置。我在这几年的使用中总结了一套经验:-t一般设置为服务器CPU核心数量,或者稍大于核心数,最高不建议超过CPU核心数的2倍,因为线程设得再高,在CPU已饱和的情况下也不会带来额外收益,反而增加上下文切换开销。-c是根据压测场景来定的,单接口基准测试一般先从-c50开始,逐步增加到-c200、-c500,观察QPS和延迟的变化趋势。

这里举一个实际例子。我在压测某个模拟项目X的订单查询接口时,服务器是4核8G,开始配置为-t2 -c50,QPS约6000,延迟P99为18ms。逐步增大到-t4 -c200,QPS涨到15000,P99延迟升到22ms。继续加到-t8 -c500,QPS反而下滑到13000,P99飙到55ms,说明这个接口的服务端线程池已经扛不住这么高的并发压力了。这个趋势本身就是非常有价值的压测结论——它的上限就在-t4 -c200附近,再往上压服务就要出问题了。

顺带提醒一下:-t设置过大时,wrk客户端自身也可能成为性能瓶颈,因为它需要把大量事件分发给多个线程处理。所以如果一个服务的性能特别强,需要更大压力才能压满,与其无限调大-t,不如从多台机器同时发起压测,这样数据才更接近真实情况。

3.3 耗时参数怎么选

-d参数决定压测时长。太短会导致数据不稳定,太长则浪费时间。常规建议是:做基准测试,10到15秒就够了;观察长尾延迟,建议跑30秒;如果需要模拟持续压力对服务的稳定性影响,可以跑1到5分钟。

时长太短的问题很容易被忽视。比如你只压测3秒,服务端线程池刚刚开始被塞满,连接也还在逐步建立,统计区间还没进入稳态就结束了,出来的QPS和延迟数据会偏低且不稳定。我的一般习惯是先花2到3秒做预热(wrk在统计上不是严格区分预热期的,但服务端在开始的几秒内往往还没进入稳定状态),再用10秒以上的正式窗口取平均数据。这个注意事项在实际压测中非常重要。

3.4 带Header请求和自定义超时

实际接口很少是完全无鉴权的,压测时需要带上Header来模拟真实调用。wrk支持用多次-H参数添加多个请求头:

wrk -t4 -c100 -d10s -H "Authorization: Bearer eyJhbGciOi..." -H "Content-Type: application/json" --latency http://api.example.com/v1/query

如果你想让某个压测请求的超时时间更长一点,比如对接第三方接口时经常出现超过默认2秒的请求,可以这样配置:

wrk -t2 -c50 -d30s -T10s --latency http://api.example.com/slow-endpoint

这里的-T10s表示单个连接超过10秒未响应才判定为超时。注意,wrk的超时和TCP连接超时不是同一个概念,wrk的超时是等待HTTP响应的最长间隔。

4. 用Lua脚本玩出更真实的压测场景

4.1 为什么需要Lua脚本

光用命令行参数,wrk只能发固定路径的GET请求,这对于真实业务的模拟是远远不够的。真实场景里的接口请求往往包括POST请求体、动态参数、带签名的Header、登录态Cookie等,这些需求都需要通过wrk的Lua脚本机制来实现。

wrk对Lua脚本提供了三种钩子函数的支持,分别是setup(线程创建时执行)、init(每个请求发送前执行)、request(每次发送请求时构造HTTP请求)、response(每个HTTP响应返回后执行)、done(整个压测结束后汇总数据)。最常用的两个钩子是request和response,前者用来动态构造请求,后者用来统计自定义数据。

4.2 压测POST接口的完整脚本

下面这份脚本是我压测交易链路上的下单接口时实际用过的例子,你完全可以照着改。它做的事情是:每次请求生成一个随机的订单号,拼成一个JSON请求体,然后以POST方式发送。

-- post.lua wrk.method = "POST" wrk.headers["Content-Type"] = "application/json" local counter = 0 function request() counter = counter + 1 local body = string.format('{"order_id":"order_%04d","amount":99.9,"channel":"android"}', counter) return wrk.format(nil, nil, nil, body) end

使用方式:

wrk -t4 -c100 -d20s -s post.lua --latency http://api.example.com/v1/order

这里的wrk.format函数可以接受请求方法、路径、请求头、请求体四个参数,如果只想改请求体,其余位置传nil即可。要注意的是,wrk.method是全局变量,在整个压测过程中设置一次就行,不需要在每次request时重复设置。

4.3 模拟多用户登录态

有些接口需要根据用户维度做数据隔离,压测时如果所有请求都用同一个Token,服务端可能会因为命中缓存导致数据偏乐观。比较好的方式是准备一批Token,在init函数里分发给各个线程,每个线程请求时随机从池子里取一个。

-- multi_token.lua local tokens = {} local thread_id function setup(thread) thread_id = 1 end function init() tokens = { "token_user_001", "token_user_002", "token_user_003", "token_user_004", "token_user_005" } end function request() local idx = math.random(1, #tokens) wrk.headers["Authorization"] = "Bearer " .. tokens[idx] return wrk.format() end

这个例子里我用一个简单的字符串数组存放Token池,如果你有真实的Token列表文件,可以在init里用io.open读取并解析。这种做法的好处是更贴近真实用户行为,服务端的缓存命中率和分布式锁竞争都会更接近生产环境。

4.4 自定义统计数据

wrk默认输出的统计指标是QPS和延迟分布,但有时候我们更关心响应状态码分布,比如想统计4xx请求占比。在response钩子里可以拿到HTTP响应状态码,配合一个全局计数变量就能实现:

local count_2xx = 0 local count_5xx = 0 function response(status, headers, body) if status >= 200 and status < 300 then count_2xx = count_2xx + 1 elseif status >= 500 then count_5xx = count_5xx + 1 end end function done(summary, latency, requests) io.write("2xx count: " .. count_2xx .. "\n") io.write("5xx count: " .. count_5xx .. "\n") end

注意,这里的done函数在整个压测进程退出前执行一次,可以用io.write直接输出自定义统计结果。这个能力在压测带有清理逻辑、依赖外部状态变化的接口时特别有用,并不局限于单纯的吞吐测量。

4.5 Lua脚本编写的常见误区

Lua脚本写错了通常不会直接报错中止压测,而是会表现为请求行为不符合预期,或者生成的文件为空,排查起来比较费劲。我在实际中使用Lua脚本时踩过的坑主要有三个:

第一,wrk.format的路径参数不会自动带上主机名,如果你在脚本里写了完整URL作为路径,wrk会把它当成路径拼接到目标host上,导致404。第二,math.random在缺少math.randomseed的情况下,每个线程产生的随机序列可能一样,导致所有线程发出的参数相同,达不到模拟真实流量的效果。第三,脚本里定义的变量是全局变量,多个线程之间可能存在竞争,如果只需要在当前线程内计数,可以用local修饰局部变量,避免多线程下的数据串扰。

5. 压测中的常见问题、误区和排查技巧

5.1 为什么压测结果每次都不一样

wrk压测结果存在波动是正常现象,但波动过大就需要排查了。常见原因有:服务端机器的负载不均匀,比如压测时正好有定时任务在跑;网络环境不稳定,远程压测时跨交换机容易出现抖动;压测目标服务依赖了外部服务或数据库,这些依赖自身的延迟抖动会直接反映到压测数据里。

为了尽量获得稳定可靠的数据,我在压测时通常会这样做:先把同一条命令连续跑3次,取其平均值;压测期间用top或htop观察服务端CPU和内存状态,排除外部干扰;如果条件允许,把wrk运行在和服务端同一内网的机器上,减少网络噪声。

5.2 连接数越高,压测就越厉害吗

这个认知是个典型误区。wrk的并发连接数只是客户端可以维持的TCP连接数量,实际服务端能处理的请求数是受自身线程池、数据库连接池、CPU核心数等限制的。无限调大连接数,并不会带来QPS的线性增长,反而可能导致连接数超过服务端的最大文件描述符限制,或者触发了服务端的连接保护策略,表现为大量请求排队超时,QPS反而下降。

正确的做法是阶梯式加压。比如第一次-c50,第二次-c100,第三次-c200,每次记录QPS和延迟,观察拐点在哪里。拐点附近的连接数就是服务的实际承载上限,拐点之后的性能数据可以为容量评估提供参考。

5.3 wrk报错“open too many files”怎么办

wrk在设置很高的并发连接数时(例如-c1000以上),可能会在客户端侧报出“open too many files”之类的错误。原因很简单,每个TCP连接在Linux下都对应一个文件描述符,而默认的ulimit值通常只有1024,不够用。这个并不是wrk本身的问题,而是操作系统限制。

解决方法是在运行wrk前临时修改当前Shell的文件描述符上限:

ulimit -n 65535 wrk -t4 -c2000 -d10s http://localhost/

注意,这个修改只在当前Shell会话里有效,新开一个终端窗口需要重新执行。如果想要永久修改,需要在/etc/security/limits.conf里为你的用户设置nofile值,但日常压测时临时改一下就够了。

5.4 wrk机器本身成了瓶颈怎么办

压测高吞吐服务时,客户端机器的性能可能先被跑满。判断依据是压测时观察wrk所在机器的CPU使用率,如果CPU已经达到100%,wrk的QPS数据就没办法再往上走,因为瓶颈在客户端而不是服务端。

遇到这种情况有几个处理办法:一是把-t调低一点,因为线程数过高时,线程切换也会吃掉不少CPU资源,过度配置反而可能降低压测性能;二是把并发连接数分散到多台wrk客户端上,同步压测同一个服务地址,最后把数据汇总;三是优化wrk本身的使用方式,尽可能避免在Lua脚本里做大量字符串拼接等耗时操作,因为这个操作发生在请求路径上,会直接影响wrk自身的发包效率。

5.5 压测本机服务靠谱吗

如果wrk和目标服务运行在同一台机器上,压测数据往往不具备参考价值。原因在于wrk也会占用CPU和内存资源,和服务端抢资源,导致服务端实际可用的CPU减少,测试出的QPS会比真实外部客户端压测时偏低。尤其是本机压测时,网络协议栈的收发也在同一条路径上,这个偏差会更明显。

如果实在只能用同一台机器,建议至少把-t设置为物理核心数的一半左右,留下足够的CPU给服务处理请求,同时观察压测曲线是否平稳。否则数据只能作为粗略参考,不能作为容量规划的基准。

5.6 常见问题速查表

症状可能原因解决方法
wrk command not found二进制未放入PATH用编译目录下./wrk或检查PATH
连接数开不了很大文件描述符受限ulimit -n 65535
压测QPS极低目标服务需要鉴权/参数错误用-H或Lua脚本补全请求条件
压测结果每次波动大服务端负载/网络扰动多次压测取均值、内网压测
客户端CPU占满wrk自身达到瓶颈降低线程数或多机并发
延迟P99异常高服务端线程池排队看服务端GC/线程池监控定位
请求一直超时服务端设置有连接保护降低连接数阶梯式加压

6. wrk在真实工作流里怎么用

6.1 代码上线前的性能回归

我平时最常用的一个场景是做性能回归测试。代码改动可能影响性能时,在上线前先压测一下改动前和改动后的接口,对比QPS和P99延迟数据,能快速判断这次变更是否存在性能退化。具体做法是在CI流程里加一段自动化脚本,部署完毕之后执行同一个wrk命令,把结果输出到文件,供人工对比。

6.2 容量评估和限流阈值设定

通过对核心接口做不同并发下的压测,可以画出一条QPS和延迟的曲线,这个曲线对设置限流阈值非常有参考价值。比如订单查询接口在-c200时P99已经接近1秒,那么限流阈值就可以设置在接近这个连接数对应的QPS附近,提前保护下游数据库不被拖垮。

6.3 结合监控系统做深度定位

wrk只能告诉我们“服务不行了”,具体哪里不行还需要和监控数据结合起来看。压测过程中同时关注服务端的CPU使用率、内存、GC次数、线程池活跃线程数、数据库慢查询数。之前在定位某个模拟项目X的接口性能瓶颈时,就是通过wrk压测发现问题,然后结合监控发现是数据库连接池泄漏导致的连接被耗尽,光看wrk输出是看不出这个原因来的。

我个人在实际操作中的体会是,wrk的最大价值不在于提供一个高精度的压测结论,而在于用最低的上手成本帮助开发者建立性能直觉。它就像一把快刀,足够锋利,但不适合用来雕花。如果需要做全链路压测、逐步加压控制、复杂场景编排、分布式施压这些重活儿,还是该上专业的压测平台。但日常开发、性能摸底、上线前自测、定位性能拐点这些场景,用wrk已经非常够用了。

最后再分享一个小技巧:压测时建议每次都在命令里加--latency参数,然后保存原始输出。等到积累了几轮压测记录之后,你会慢慢形成对自己服务性能数据的敏感度,看到QPS波动就能大致判断问题方向。这个习惯帮我省下了很多排查时间,值得养成。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询