ab、wrk、Locust 压测工具实战对比:用法、实测与选型建议

      发布在:技术      评论:0 条评论

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. 常见误区和校验清单

  1. 只看平均响应时间。至少报告 P50、P95、P99、最大值和错误率。
  2. Keep-Alive 条件不一致。ab 默认不开 Keep-Alive,而 wrk 和 Locust 常规 HTTP 会话会复用连接。对比时要统一。
  3. 压测机先到瓶颈。监控压测端 CPU、内存、网卡、文件描述符和临时端口;客户端满载后,增加并发只会制造假结论。
  4. 没有预热和重复运行。连接建立、JIT、缓存、连接池和数据库页缓存都会影响前几秒。先预热,再至少跑三轮并说明汇总方法。
  5. 用静态接口外推整套系统容量。静态文件测试只能说明 HTTP 栈和压测端效率,不能代表数据库、缓存和下游调用。
  6. 用闭环 RPS 当作固定到达率。服务器越慢,闭环客户端发得越慢。评估排队和突发流量时要明确负载模型。
  7. 直接压生产。只能测试明确授权的目标。先确认限流、告警、回滚和数据清理方案,写接口还要准备隔离数据。
  8. 没有服务端证据。客户端只能告诉你“慢了”或“错了”,根因要靠服务端的 CPU、队列、GC、数据库慢查询和依赖指标。

10. 结论

ab、wrk、Locust 没有统一的优胜者。ab 的价值是简单,wrk 的价值是单机高吞吐,Locust 的价值是场景建模和扩展。先写清楚测试目标、负载模型、成功标准和监控指标,再选工具;如果这四项没写清楚,换一个更强的压测工具也只会得到更漂亮但不可靠的数字。

实测日期:2026-07-22。本文结果只对文中硬件、软件版本和 97 字节静态响应成立,不能直接作为生产容量承诺。

参考资料

Responses

评论已关闭。