ab、wrk 和 Locust 经常被放进同一张“压测工具对比表”,但它们解决的并不是同一个问题。ab 适合快速探测单个 HTTP 接口;wrk 擅长把一台压测机的网络和 CPU 利用起来;Locust 的重点是用 Python 描述用户行为、控制增压过程,并把压力扩展到多台 worker。
为了避免只谈功能不谈数据,我在一台内网 Linux 测试机上跑了可复现的对照实验。本文保留安装、常用参数和脚本示例,也给出实际命令、延迟分位数、资源占用和原始结果摘要。
1. 先分清:基准测试、负载测试和压力测试
“压测”通常混在一起说,实际至少有三类目标:
- 基准测试(benchmark):固定一个极简请求,比较吞吐、延迟或版本差异。wrk 和 ab 很适合。
- 负载测试(load test):按预期流量运行,检查 P95/P99、错误率和资源水位。Locust 更容易描述这类业务负载。
- 压力测试(stress test):持续加压直到系统超过拐点,观察限流、排队、超时、降级和恢复。
并发也不是一个统一单位。ab 的 -c 是同时未完成的请求数,wrk 的 -c 是保持打开的连接数,Locust 的 -u 是并发用户数。Locust 用户会执行任务并遵守 wait_time;当等待时间不为 0 时,200 个用户通常远远达不到 200 个同时在途请求。
闭环负载会“等服务器”
这三种工具的常见用法都接近闭环模型:收到响应后才继续发下一次请求。服务器变慢时,请求发送速率会自然下降,最拥堵时段可能被低估,这就是常说的 coordinated omission。需要固定到达率时,应使用支持 arrival-rate 场景的工具,或选择 wrk2 这类恒定吞吐分支。
2. 实测环境与方法
| 项目 | 配置 |
|---|---|
| 测试机 | CentOS Linux 7,Linux 3.10.0 |
| 处理器 | Intel Xeon E5-2430 0 @ 2.20 GHz,8 个逻辑 CPU |
| 内存 | 15,884 MiB |
| 目标服务 | Nginx 1.16.1,4 个 worker,127.0.0.1:18080 |
| 响应 | 97 字节静态 JSON,HTTP Keep-Alive |
| 工具版本 | ApacheBench 2.3;wrk 4.1.0;Locust 1.4.4 / Python 3.6.8 |
| 测试方式 | 并发 50 和 200;每轮 15 秒;每轮前预热 2 秒;各跑 3 次,取中位数 |
压测端和 Nginx 在同一台机器,通过 loopback 通信。这样能减少网络波动,适合比较三个客户端的发压开销,但不能代表线上容量。生产评估应把压测机和被测系统分开,并同时采集服务端 CPU、负载、连接数、队列、GC、数据库和下游依赖指标。
目标服务与基准命令
# 返回 97 字节 JSON
curl -i http://127.0.0.1:18080/payload.json
# ab:显式启用 Keep-Alive;把大 -n 放在 -t 后面
ab -k -t 15 -n 100000000 -c 200 \
http://127.0.0.1:18080/payload.json
# wrk:4 个线程、200 个连接
wrk -t4 -c200 -d15s --latency \
http://127.0.0.1:18080/payload.json
# Locust:200 用户,快速完成 ramp-up,无等待时间
locust -f locustfile.py --headless -u 200 -r 1000 \
-t 15s --host http://127.0.0.1:18080 --only-summary
3. ab:适合快速、可复制的单接口检查
ApacheBench 是 Apache HTTP Server 项目附带的命令行程序。它的优势不是“能压出最高 QPS”,而是部署简单、命令短,几乎每个运维人员都能读懂结果。它采用单进程、单线程的发压方式,高吞吐场景中压测端很容易先占满一个 CPU 核。
安装
# Ubuntu / Debian
sudo apt-get update
sudo apt-get install apache2-utils
# RHEL / CentOS / Rocky Linux
sudo dnf install httpd-tools # 老版本系统使用 yum
# macOS(ab 随 httpd 安装)
brew install httpd
ab -V
原文把 pre 标签当成普通文字存进了 WordPress,浏览器当然会原样显示。正确做法是让编辑器保存真正的“预格式文本 + code”代码块,而不是把标签本身做 HTML 转义。本文的命令区已经全部改为实际代码块。
常用参数
| 参数 | 作用 | 容易踩的坑 |
|---|---|---|
-n 100000 |
请求总数 | 只给很小的 -n,测试可能还没稳定就结束 |
-c 100 |
并发请求数 | 不是线程数,也不是每秒请求数 |
-t 30 |
按时间运行,单位秒 | 旧版 ab 会同时把请求上限设为 50000;长测试可在 -t 后再写一个很大的 -n |
-k |
启用 HTTP Keep-Alive | 默认不开;和默认 Keep-Alive 的 wrk/Locust 对比时必须显式加 |
-H |
追加请求头 | 可重复使用,例如鉴权头和 Host |
-p / -T |
从文件读取 POST body / 指定 Content-Type | 适合固定请求体,不适合复杂数据关联 |
# 固定次数的 GET
ab -n 10000 -c 50 -k https://example.com/health
# 带鉴权头
ab -n 10000 -c 50 -k \
-H 'Authorization: Bearer TOKEN' \
https://example.com/api/orders
# POST JSON
ab -n 5000 -c 20 -k -p body.json \
-T 'application/json' https://example.com/api/orders
怎样读 ab 输出
Requests per second 是整体吞吐。ab 会输出两行 Time per request:第一行是并发批次的平均完成时间;第二行除以并发数,适合估算吞吐关系,但不是单个用户真正感知到的平均延迟。延迟判断应看后面的 50%、95%、99% 分位数。Failed requests 必须和非 2xx/3xx 响应、超时及服务端日志一起检查。
4. wrk:单机高吞吐 HTTP 基准测试
wrk 使用多线程和事件驱动 I/O。-t 决定工作线程数,-c 决定总连接数,每个线程负责一部分连接。相比 ab,它通常能让多核压测机产生更高吞吐,并且 --latency 会直接打印延迟分位数。
安装
# macOS
brew install wrk
# Debian / Ubuntu(仓库提供时)
sudo apt-get install wrk
# 源码构建
git clone https://github.com/wg/wrk.git
cd wrk
make -j"$(getconf _NPROCESSORS_ONLN)"
sudo install wrk /usr/local/bin/wrk
wrk -v
常用命令
# 4 个线程、200 个连接、运行 30 秒
wrk -t4 -c200 -d30s --latency https://example.com/api/items
# 增加请求头并把单请求超时设为 2 秒
wrk -t4 -c200 -d30s --latency --timeout 2s \
-H 'Authorization: Bearer TOKEN' \
https://example.com/api/items
线程数并非越大越好。通常从压测机 CPU 核数的一半到相近数量开始,再观察压测端 CPU 和上下文切换。连接数必须大于等于线程数;同时检查 ulimit -n,否则客户端可能因为文件描述符不足产生连接错误。
Lua 脚本:固定 POST 请求
-- post.lua
wrk.method = "POST"
wrk.body = [[{"sku":"A-100","quantity":1}]]
wrk.headers["Content-Type"] = "application/json"
wrk.headers["Authorization"] = "Bearer TOKEN"
function response(status, headers, body)
if status >= 400 then
io.stderr:write("unexpected status: " .. status .. "\n")
end
end
wrk -t4 -c200 -d30s --latency \
-s post.lua https://example.com/api/orders
Lua 很适合改请求方法、请求头和请求体,也能生成动态值。但登录后取 token、跨请求传递 ID、按权重执行多条业务链路时,脚本会迅速变难维护。wrk 的常规模式也不是固定到达率模型;服务变慢后,它会跟着降速,因此压垮系统时的尾延迟可能比真实排队场景更好看。
5. Locust:用 Python 描述用户和业务流程
Locust 把负载单位定义为 User。每个用户有自己的 HTTP 会话,会复用连接并按任务权重执行代码。它基于 gevent 并发模型,单个 Python 进程的纯请求吞吐通常不如 wrk,但脚本表达力、统计界面、阶段控制和分布式能力强得多。
安装到虚拟环境
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install locust
locust --version
一个带权重、等待时间和校验的场景
from locust import HttpUser, task, between
class ShopUser(HttpUser):
wait_time = between(1, 3)
def on_start(self):
response = self.client.post(
"/api/login",
json={"username": "load-user", "password": "secret"},
name="POST /api/login",
)
self.token = response.json()["token"]
@task(4)
def list_products(self):
self.client.get("/api/products", name="GET /api/products")
@task(1)
def create_order(self):
with self.client.post(
"/api/orders",
json={"sku": "A-100", "quantity": 1},
headers={"Authorization": f"Bearer {self.token}"},
name="POST /api/orders",
catch_response=True,
) as response:
if response.status_code != 201:
response.failure(f"unexpected status {response.status_code}")
Web、无界面和分布式运行
# Web UI,默认访问 http://127.0.0.1:8089
locust -f locustfile.py --host https://example.com
# CI / 服务器无界面运行:500 用户,每秒启动 20 个,持续 10 分钟
locust -f locustfile.py --headless \
-u 500 -r 20 -t 10m --host https://example.com \
--csv results/run-001 --html results/run-001.html
# 分布式:一台 master,多台 worker
locust -f locustfile.py --master
locust -f locustfile.py --worker --master-host 10.0.0.10
-u 控制用户数,-r 控制每秒启动多少用户。wait_time 决定每个用户两次任务之间的停顿,这三个参数共同决定实际 RPS。排查 Locust 性能瓶颈时,要看 worker CPU;单 worker 占满一个核心后,应增加 worker 进程或分布到多台机器,而不是只继续提高 -u。
6. 实测结果:吞吐、尾延迟和客户端开销
下面是三轮测试的中位数。CPU 和最大常驻内存来自压测客户端进程,不包含 Nginx。三轮合计失败数均为 0。Locust 1.4.4 会把停止用户的耗时计入最终 CSV 的 Requests/s;并发 200 时,停止 200 个用户额外用了约 17 秒。为了和 ab、wrk 的固定 15 秒窗口对齐,表中 Locust RPS 按“完成请求数 ÷ 15 秒”重新计算,原始 CSV 仍保留在测试档案中。另一个值得注意的信号是:虽然设置了每秒启动 1000 个用户,但单 worker 已经占满一个核心,200 个用户实际用了约 8 秒才全部启动;这组 Locust 数据反映的正是单 worker 饱和,而不是服务端容量上限。
并发 50
| 工具 | RPS | P50 | P90 | P99 | 客户端 CPU | 最大 RSS | 失败 |
|---|---|---|---|---|---|---|---|
ab |
30,492 | 1.00 ms | 3.00 ms | 4.00 ms | 100% | 31.6 MiB | 0 |
wrk |
45,134 | 1.00 ms | 1.33 ms | 2.84 ms | 219% | 5.1 MiB | 0 |
locust |
327 | 110.0 ms | 150.0 ms | 190.0 ms | 100% | 43.0 MiB | 0 |
并发 200
| 工具 | RPS | P50 | P90 | P99 | 客户端 CPU | 最大 RSS | 失败 |
|---|---|---|---|---|---|---|---|
ab |
30,348 | 6.00 ms | 11.0 ms | 19.0 ms | 100% | 32.4 MiB | 0 |
wrk |
45,809 | 4.15 ms | 5.43 ms | 8.17 ms | 217% | 5.1 MiB | 0 |
locust |
659 | 350.0 ms | 460.0 ms | 1100.0 ms | 100% | 52.4 MiB | 0 |
在并发 200 的这组静态小响应测试中,wrk 的中位吞吐是 ab 的 1.51 倍、Locust 的 69.5 倍;ab 约为 Locust 的 46.0 倍。这个差距主要反映发压引擎和脚本层开销,不代表 wrk 对真实业务一定“快这么多”。一旦接口本身需要 50 ms、数据库成为瓶颈,三个客户端的吞吐差距会明显缩小。
代表性运行的关键字段
# ab,c=200,一轮代表性结果
Complete requests: 455319
Failed requests: 0
Requests per second: 30347.98 [#/sec]
50% response time: 6 ms
90% response time: 11 ms
99% response time: 20 ms
# wrk,c=200,一轮代表性结果
Requests: 688759
Socket errors: 0
Requests/sec: 45808.56
Latency 50%: 4.150 ms
Latency 90%: 5.250 ms
Latency 99%: 8.010 ms
# Locust,u=200,一轮代表性结果
Requests: 9892
Failures: 0
Requests/s (15s window): 659.47
CSV reported Requests/s: 309.79
Response time 50%: 350 ms
Response time 90%: 460 ms
Response time 99%: 1100 ms
结果应该怎样解释
- wrk 的 4 个线程能使用多个 CPU 核,吞吐最高符合它的设计目标。
- ab 的客户端 CPU 接近一个核心上限,单线程发压端已经是明显约束;继续加并发不会线性提高吞吐。
- Locust 需要执行 Python 用户代码、事件统计和响应校验。单 worker 的 RPS 低,但它提供的是业务建模能力。需要更大流量时,应多开 worker。
- P99 比平均值更能暴露排队和偶发抖动。任何吞吐对比都应同时报告分位数和失败率。
7. 三种工具的专业对比
| 维度 | ab | wrk | Locust |
|---|---|---|---|
| 核心抽象 | 并发请求 | 线程 + 连接 | 用户 + 任务 + 等待时间 |
| 实现与并发 | C,单线程 | C,多线程事件驱动 | Python,gevent greenlet |
| 单机发压能力 | 中等,易受单核限制 | 高 | 取决于脚本;可增加 worker |
| 场景脚本 | 固定 GET/HEAD/POST 等 | Lua,可改请求和处理响应 | 完整 Python,适合状态、关联和条件分支 |
| 多步骤业务流 | 不适合 | 能做但维护成本高 | 适合 |
| 等待时间/任务权重 | 无 | 需自己写 Lua | 原生支持 |
| 实时 Web 界面 | 无 | 无 | 有 |
| 分布式执行 | 无内建支持 | 无内建支持 | master/worker 原生支持 |
| 无界面/CI | 适合 | 适合 | 支持 headless、CSV、HTML |
| 典型用途 | 冒烟、回归、快速基线 | 接口/网关吞吐与延迟基准 | 业务负载、长稳、容量和分布式压测 |
“wrk 支持 Lua,所以等同于 Locust”是一个常见误解。Lua 能把单请求变复杂,但 Locust 的用户生命周期、任务调度、等待时间、事件钩子、分阶段增压和多 worker 管理属于另一层能力。反过来,用 Locust 测一个 97 字节静态文件的单机极限也不划算。
8. 怎么选:按问题选工具
选 ab 的情况
- 发布后快速确认接口没有明显性能回退。
- 在最小环境里做可复制的 HTTP 基线。
- 测试只需要固定 URL、请求头或请求体。
选 wrk 的情况
- 比较 Nginx、网关、框架或序列化方案的吞吐和尾延迟。
- 压测机单核已经限制 ab,需要利用多核。
- 请求逻辑不复杂,Lua 足够表达。
选 Locust 的情况
- 用户要先登录,再浏览、查询、下单,数据需要在请求间传递。
- 不同任务有权重和思考时间,需要更接近用户行为。
- 需要分阶段增压、长时间运行、Web 观察或多机发压。
实际项目中可以组合使用:ab 做部署后的 30 秒冒烟;wrk 找单接口或网关的吞吐拐点;Locust 用同一版系统跑业务链路和长稳测试。三份结果回答不同问题,不必强行合成一个“总分”。
9. 常见误区和校验清单
- 只看平均响应时间。至少报告 P50、P95、P99、最大值和错误率。
- Keep-Alive 条件不一致。ab 默认不开 Keep-Alive,而 wrk 和 Locust 常规 HTTP 会话会复用连接。对比时要统一。
- 压测机先到瓶颈。监控压测端 CPU、内存、网卡、文件描述符和临时端口;客户端满载后,增加并发只会制造假结论。
- 没有预热和重复运行。连接建立、JIT、缓存、连接池和数据库页缓存都会影响前几秒。先预热,再至少跑三轮并说明汇总方法。
- 用静态接口外推整套系统容量。静态文件测试只能说明 HTTP 栈和压测端效率,不能代表数据库、缓存和下游调用。
- 用闭环 RPS 当作固定到达率。服务器越慢,闭环客户端发得越慢。评估排队和突发流量时要明确负载模型。
- 直接压生产。只能测试明确授权的目标。先确认限流、告警、回滚和数据清理方案,写接口还要准备隔离数据。
- 没有服务端证据。客户端只能告诉你“慢了”或“错了”,根因要靠服务端的 CPU、队列、GC、数据库慢查询和依赖指标。
10. 结论
ab、wrk、Locust 没有统一的优胜者。ab 的价值是简单,wrk 的价值是单机高吞吐,Locust 的价值是场景建模和扩展。先写清楚测试目标、负载模型、成功标准和监控指标,再选工具;如果这四项没写清楚,换一个更强的压测工具也只会得到更漂亮但不可靠的数字。
实测日期:2026-07-22。本文结果只对文中硬件、软件版本和 97 字节静态响应成立,不能直接作为生产容量承诺。
参考资料
本文由
coffee 创作,除注明转载/出处外,均为本站原创,转载前请务必署名
最后编辑时间为: 2026-07-22 16:52 星期三