☰
Locust压力测试实战:QPS、分布式压测与CI门禁
2026/10/1 5:00:27 网站建设 项目流程

1. QPS不是"每秒请求数"这么简单:一次全链路超时的排查复盘

很多同学第一次接触压力测试(QPS)这个词,是在需求文档里看到一句"接口需支持 1000 QPS"。然后大家默认的理解就是:每秒能打进去一千个请求不报错,就算达标。我最早也是这么想的,直到有一次我们一个内部订单接口,单机压测跑出了 1200 QPS、失败率 0%、平均响应时间 38ms,上线之后却在大促预热当天全线超时——接口本身没有挂,数据库也没死,但平均响应时间从 38ms 涨到了 4 秒多,前端大面积转圈。后来复盘才发现,我们压测的时候压的是"空账户查询",命中缓存几乎百分之百,而真实流量里有大量跨店铺的联合查询,缓存命中率不到四成。那次之后我对"压力测试"这四个字的理解就变了:它压的从来不是接口,而是整条链路在特定流量模型下的表现。

压力测试(Load Testing)这件事,本质上是在回答三个问题:系统在预期流量下稳不稳、系统的极限在哪里、系统逼近极限时会以什么方式退化。QPS 只是这三个问题里最容易量化、但也最容易被误读的一个指标。它衡量的是一段时间内系统成功处理的请求数量除以时间窗口,单位是"次/秒"。注意这里有两个隐含前提——"成功处理"和"时间窗口"。如果把失败请求也算进 QPS,或者用峰值瞬时窗口去代表整体,得到的数字就是自欺欺人。我在实际项目里见过最离谱的一份压测报告,写着"峰值 QPS 8500",追问之后才知道那是 200ms 内的瞬时采样值,持续一分钟的平均值只有 600 出头。

这篇内容我打算围绕 Locust 这个压测工具,把一套完整的压力测试流程拆开讲:从 QPS 的正确理解,到 Locust 的选型理由、脚本编写、指标解读、分布式压测,再到把压测固化成 CI 里的常态化门禁。适合已经写过接口、想系统掌握性能测试的后端和测试同学;如果你只想知道 QPS 怎么算,第一节看完就够了;如果想自己搭一套能出报告、能卡发布的压测体系,那后面的工具配置、脚本细节和踩坑记录应该对你有用。

2. 选Locust的四个理由,以及它不适合的场景

2.1 为什么在JMeter和Locust之间我选了后者

压测工具这个领域,JMeter 的生态和成熟度确实是老大哥,图形界面点一点就能出一个测试计划,对测试岗位的同学非常友好。我早期也用 JMeter 压过不少接口。但近几年我越来越多地把 Locust 作为主力工具,理由有这么几条,都是实际用过之后才体会到的。

第一是脚本即代码。Locust 的压测场景就是一个普通的 Python 文件(习惯上叫locustfile.py),这意味着我可以直接 import 项目里的签名工具、加解密库、数据构造模块,甚至复用业务代码里的常量定义。JMeter 在这块要靠 JSR223 脚本或者插件去绕,写复杂逻辑的时候体验差一大截。第二是并发模型。Locust 基于 gevent 协程做并发,单个进程拖着几千个虚拟用户是常规操作,资源占用比多线程模型低得多;而 JMeter 的线程模型下,几千并发往往就要考虑分布式部署了。第三是能压任何协议。HTTP 只是默认提供的客户端,只要你能用 Python 写出来,MQTT、WebSocket、gRPC、数据库连接、自定义 TCP 协议都能塞进去当压测目标,写一个继承User的类、挂上client属性就行。第四是分布式和 Web UI 的平衡做得比较舒服,主从模式下汇总统计是实时的,不用等跑完再看报告。

也有需要认怂的场景。如果团队里完全没有 Python 基础,测试同学又需要快速上手,那 JMeter 的图形界面就是更现实的选择,强行推 Locust 只会让压测这件事没人愿意做。如果需要录制回放浏览器行为(比如带复杂前端交互的场景),JMeter 加录制代理、或者直接上 Playwright、k6 这类工具会更省事。Locust 里想做浏览器级别的压测,得靠locust-plugins里的 PlaywrightUser,但那已经偏离了它最擅长的领域。

维度LocustJMeter
场景定义Python 代码,可复用业务逻辑图形界面配置,复杂逻辑需脚本
并发模型gevent 协程,单机并发高线程模型,高并发需分布式
协议支持任意可用 Python 实现的协议HTTP/JDBC/MQ 等,需插件扩展
报告能力Web UI 实时 + CSV/HTML 导出聚合报告 + HTML Dashboard
上手门槛需要 Python 基础界面友好,无编程基础可上手
结果可复现性高,配置文件纳入版本管理中,测试计划文件较难维护

2.2 一个容易忽略的前提:压测工具不能成为瓶颈

选工具的时候,我见过太多人只关心"能不能压出高 QPS",却忘了问一句:这个数字是服务端的能力,还是压测机的能力?压测机 CPU 打满、网卡跑满、本地端口耗尽,都会让 QPS 数字提前见顶,你会误以为被压系统到极限了,实际上是施压方先趴下了。Locust 因为是单进程协程模型,这个问题尤其需要注意——一个 Locust 进程实际只能吃满一个 CPU 核,想跑满多核就得开多个 worker 进程,或者干脆上分布式。这一点我在第六节会展开讲怎么处理,包括压测机内核参数的调整。

所以我的建议是,选工具之前先算一笔账:目标 QPS 是多少、单次请求的响应体积大概多大、压测机的网络带宽和 CPU 撑不撑得住。1000 QPS、响应体 10KB 的场景,出口带宽大概需要 80Mbps 左右,一台普通云主机勉强够;如果响应体变成 1MB,那就是 8Gbps,必须多机分布式,而且瓶颈大概率在网卡上。

提示:压测前先单独做一次"空转测试",即让压测机直接访问一个固定的静态小文件,看看这台机器自己能跑出多少 QPS。这个数字就是你这套压测环境的天花板,服务端跑出来的结果如果接近它,就要怀疑是施压端的问题了。

3. 从零写出第一个可用的Locust脚本

3.1 环境准备:Python版本和依赖

Locust 对 Python 版本有要求,2.x 版本建议 Python 3.8 以上,Python 3.10 到 3.12 是比较舒服的区间。安装本身很简单:

python3 -m venv venv source venv/bin/activate pip install locust locust --version

生产环境不建议全局 pip 装到系统 Python 里,用虚拟环境隔离,避免和业务依赖打架。如果你的压测脚本要复用业务代码,那更应该单独建一个压测专用的虚拟环境,把需要的依赖列进requirements.txt,这样换一台压测机也能一条命令复现。

被压服务这边有几个提前要做的事,很多坑都是这里埋下的。一是准备一份独立的测试数据,别直接在线上数据上压;二是确认服务端的限流、熔断、防重放策略,压测流量大概率会触发它们,要么临时放行压测机 IP,要么把这个当成测试目标之一;三是把压测机的 IP 加到服务端的白名单和监控大盘里,方便压测过程中实时看服务端指标。

3.2 locustfile.py的结构与最小可用范例

Locust 的脚本结构其实就几个概念:User代表一类虚拟用户,@task装饰的方法代表这个用户会做的事,wait_time代表两次任务之间的间隔,host是被压服务地址。下面这个例子覆盖了登录拿 token、后续带 token 访问两个典型动作:

import random from locust import HttpUser, task, between class OrderUser(HttpUser): wait_time = between(1, 3) host = "http://127.0.0.1:8000" def on_start(self): resp = self.client.post( "/api/login", json={"username": "perf_user", "password": "perf_pass"}, name="/api/login", ) self.token = resp.json().get("token", "") self.headers = {"X-Token": self.token} @task(5) def list_orders(self): page = random.randint(1, 20) self.client.get( f"/api/order/list?page={page}&size=20", headers=self.headers, name="/api/order/list", ) @task(1) def create_order(self): self.client.post( "/api/order", json={"sku": random.randint(1000, 9999), "count": 1}, headers=self.headers, name="/api/order", )

这段代码里有三个细节值得单独拎出来说。on_start只在虚拟用户启动时执行一次,适合做登录、建立长连接这类的初始化动作。@task(5)里的数字是权重,意味着这个任务被选中的概率是create_order的五倍,用来模拟线上读写比例。最关键的是name=参数——如果不写它,带随机 page 参数的 URL 在统计报表里会散成几十条独立记录,你想看这个接口整体的 QPS 就得自己加总,非常痛苦。用了name之后,所有变体都会归并到同一条统计里。

3.3 启动方式与常用参数

写完之后,最直观的启动方式是带 Web UI 的模式:

locust -f locustfile.py # 浏览器打开 http://localhost:8089

在界面上填并发用户数(Number of users)、每秒启动用户数(Spawn rate)、运行时长,点 Start 就跑起来了。这种方式适合调试脚本和演示,能看到实时的 RPS 曲线和响应时间分布。

真正做正式压测的时候我更推荐无界面模式,因为参数固定、结果可复现、方便脚本化:

locust -f locustfile.py --headless \ -u 500 -r 20 -t 10m \ --csv=result --html=report.html \ --only-summary

这里几个参数的含义必须记牢:-u是并发用户总数,-r是每秒启动多少个用户(爬坡速率),-t是总运行时长,--csv导出原始统计数据,--html生成可视化报告,--only-summary让 CSV 只输出汇总行而不是每次都追加。-r这个参数特别容易被忽略——如果 500 个用户用-r 500一次性全部启动,系统会瞬间承受满负荷冲击,看到的失败率往往是被瞬时压力打出来的,不代表稳态能力。我通常设置-r让爬坡过程占据总时长的一到两成,给系统一个适应过程。

常用参数作用建议取值思路
-u并发虚拟用户数按目标 QPS × 平均响应时间去反推
-r每秒启动用户数总用户数的 5% 到 20%,避免瞬时冲击
-t运行时长稳态压测不少于 10 分钟
--headless无界面模式正式压测和 CI 中必开
--csv/--html导出结果归档和写报告时必开
--tags按标签筛选任务分批压测不同业务模块时用

4. 用权重和思考时间把脚本写"像真人"

4.1 wait_time决定了你的QPS天花板

这是我最想强调的一点,也是新手最容易翻车的地方。假设你设置了 500 个虚拟用户,每个用户的wait_time是between(1, 3)(平均 2 秒),那单个用户在任务执行完之后的循环周期大约是"响应时间 + 2 秒"。如果响应时间是 100ms,那么每个用户每秒产生的请求数约为 1 ÷ 2.1 ≈ 0.48,500 个用户加起来只有约 240 QPS。很多人跑到这里发现"怎么加用户 QPS 都不涨",其实是wait_time把自己锁死了。

QPS 和并发数之间的关系可以用一个简单公式描述:QPS = 并发数 ÷ 平均响应时间(秒)。这就是性能测试里常说的 Little's Law 的直观形式。注意这里的并发数指的是"同一时刻正在处理请求的用户数",不是"在线虚拟用户数"。如果一个用户做完请求要思考 2 秒,那 500 个用户里真正同时在发请求的可能只有二三十个。所以当你需要压出 1000 QPS,而实测平均响应时间是 200ms,那理论需要的同时并发是 200;再考虑思考时间,虚拟用户总数要放大好几倍。

调wait_time有几种常见写法,用途不同:

  • between(1, 3):模拟有思考时间的真实用户,最贴近线上流量。
  • constant(0)或constant_throughput(10):不留间隔,用于压榨接口极限。constant_throughput(10)表示每个用户每秒最多发 10 次请求,比constant_pacing更直观。
  • constant_pacing(0.5):每个用户每 0.5 秒发一次,不管你前面执行了多久,节奏更稳定。

我一般会准备两份脚本配置:一份"业务仿真模式",wait_time按真实用户行为设置,用来估算容量;一份"极限模式",wait_time = constant(0),用来找系统的崩溃拐点。两者结论差异很大,不能混用。

4.2 任务权重按照线上真实流量比例来定

压测场景里读多写少是常态,但具体比例是多少,不能拍脑袋。我的做法是去拉一份线上近一周的接口调用量统计,把 Top 20 接口的调用次数占比算出来,然后按这个比例设置@task的权重。比如查询类占 70%、下单类占 15%、其他杂项占 15%,那权重就设成 70、15、15,简单直接。

这里有个容易被忽略的细节:不同接口的响应时间和资源消耗完全不一样。一个全表扫描的报表接口,可能响应时间是普通查询的 50 倍,如果它的流量占 5%,那么它对整体资源池的占用可能接近一半。所以权重只按调用次数来分配是不够的,最好再乘上一个"资源权重"。实操上我会分两轮压:第一轮纯按调用比例跑,看整体;第二轮把重接口单独拎出来放大压,看它对资源的挤压效应。脚本里可以用--tags做任务打标,然后通过命令行筛选:

@task(70) @tag("read") def query(self): ... @task(5) @tag("heavy") def report(self): ...

压测时用locust -f locustfile.py --tags heavy单独跑重接口的任务集,这样既能复用同一份脚本,又不用维护两套代码。

4.3 参数化和数据隔离,别让压测数据污染业务

压测写出来的数据是会真实落库的。我踩过最尴尬的一次坑,是压测的下单接口用的商品 SKU 和线上正式商品撞了,压测过程中把库存字段刷成了负数,运营那边直接炸锅。从那之后我给团队定了两条硬规矩:一是压测脚本只能操作测试环境的资源,跨环境压测必须走审批;二是所有压测数据必须带可识别的标记前缀,比如用户名统一以perf_开头、订单备注统一写入固定标识,跑完之后能一条 SQL 精准清理。

参数化的方式在 Locust 里有好几种,各有用处。用random.randint、random.choice生成随机数据适合读操作;用预置的数据文件按行读取适合需要保证唯一性的写操作;更讲究一点的做法是用locust-plugins里的@datasets或者自定义一个数据池,保证同一个虚拟用户在整个生命周期内使用同一份身份数据,这样 token、用户上下文、购物车状态才能串起来。

import csv from locust import HttpUser, task, between class DataDrivenUser(HttpUser): wait_time = between(1, 3) def on_start(self): with open("perf_users.csv", encoding="utf-8") as f: self.rows = list(csv.DictReader(f)) import random self.user = random.choice(self.rows)

数据文件按需在on_start里读取,注意不要在任务方法里每次都读文件,IO 开销会成为压测机的额外负担。

5. 看懂压测报告:Little's Law与分位数指标

5.1 三个指标之间的换算关系,比单个数字重要得多

Locust 的 Web UI 和 CSV 报告里,最核心的指标就三类:请求数/失败数(Requests、Failures)、每秒请求数(RPS)、响应时间分布(Response Times)。很多人只盯着 RPS 看,其实这三个数字必须放在一起解读才有意义。

前面提过QPS = 并发数 ÷ 平均响应时间。这个等式在压力测试里可以变形使用:当你观察到 QPS 不再随并发数上升而上升,同时响应时间开始线性增长,说明系统进入了饱和区,此时 QPS 就是当前的容量上限。如果继续加并发,QPS 甚至会下降——因为排队、重试、锁竞争会消耗额外资源。我在一次数据库连接池只有 20 个的压测里就见过这个现象:并发从 100 加到 300,QPS 从 900 掉到了 700,响应时间从 110ms 涨到 400ms 以上,典型的资源争抢导致吞吐倒退。

具体算一下更清楚。假设目标 QPS 是 800,实测单请求平均响应时间是 150ms,带入公式,需要的稳态并发数 = 800 × 0.15 = 120。再考虑 Locust 的wait_time设置,如果每个用户思考 1 秒,那么单个用户的循环周期 = 0.15 + 1 = 1.15 秒,每秒贡献约 0.87 个请求,120 ÷ 0.87 ≈ 138 个虚拟用户就够。这个数字和"直接设 800 个用户"差了将近六倍,如果不算这一步,压出来的失败率完全没法解释。

5.2 P95和P99才是真正体验到的响应时间

平均响应时间这个指标有很强的欺骗性。100 个请求里有 1 个耗时 5 秒,另外 99 个都是 50ms,平均下来是 99.5ms,看起来一切正常,但那个 5 秒的请求对应的用户已经在骂人了。所以压测报告里必须看分位数:P50(中位数)代表典型体验,P95 代表较差的体验,P99 代表最差的那批用户。

指标含义为什么重要
P50一半请求快于这个值反映大多数用户的典型感受
P9595% 的请求快于这个值常被用作 SLA 门槛
P9999% 的请求快于这个值长尾问题、慢查询、GC 停顿会在这里暴露
Avg算术平均容易被极端值带偏,参考价值最低
Max最大值用来定位是否存在超长请求

我的经验是,读多写少的接口,P99 通常控制在 P50 的 5 到 8 倍以内算健康;如果 P99 是 P50 的 20 倍以上,说明存在明显的长尾,通常是缓存击穿、慢 SQL、锁等待或者垃圾回收导致的。压测的时候我会专门开一个终端用watch盯着 P99 曲线,它在什么时候开始翘头,往往就是系统开始吃力的时间点。

5.3 从Statistics页找瓶颈的四个信号

Locust 的统计表按接口分组,非常直观。我通常按下面这个顺序扫一遍,五分钟之内基本能定位到大方向:

  • 失败率突然抬升:先看失败原因是 5xx 还是超时。如果是 5xx,去看服务端日志;如果是超时,说明响应时间已经超过了客户端设置的超时阈值,去看服务端是否有慢查询或者线程池打满。
  • 某个接口的 RPS 明显低于其他接口:在同样并发下,它的响应时间更长,是整条链路的短板,优先优化它。
  • RPS 曲线呈锯齿状:忽高忽低,通常是同步锁、定时任务或者连接池回收造成的周期性问题。
  • 响应时间随运行时间缓慢上升:典型的内存泄漏或连接未释放特征,压测持续时间越长越明显。这也是为什么我坚持稳态压测至少要跑十分钟以上,短时间的压测根本发现不了这类问题。

注意:Locust 默认的客户端超时时间是 30 秒,如果被压接口响应时间接近这个值,统计里会大量出现失败,容易被误判成服务不可用。可以按需调整self.client.get(url, timeout=10),让它更快暴露问题而不是拖到超时。

6. 分布式压测与压测机自身的瓶颈

6.1 Master-Worker模式怎么搭

单台压测机能提供的并发是有限的,通常一台 4 核 8G 的机器,跑两三千虚拟用户就开始吃力了。这时候就上分布式,Locust 的主从模式配置非常简单。

先在 master 节点启动:

locust -f locustfile.py --master --expect-workers 4 --headless -u 4000 -r 50 -t 15m

然后在四台 worker 节点上分别启动,指向 master 的地址:

locust -f locustfile.py --worker --master-host=10.0.0.10

master 只负责分发任务和汇总统计,实际施压由 worker 承担,所以 master 这台机器的配置可以低一些,但要保证它和所有 worker 之间的网络延迟足够低,否则统计数据同步会成为瓶颈。--expect-workers这个参数建议写上,它会等所有 worker 都连上再正式开始,避免某个 worker 晚到导致部分请求没有施加上去,造成结果偏低。

Locust 2.x 还提供了--processes参数,可以在单机上启动多进程模式,本质上是本机版的分布式。比如locust -f locustfile.py --processes 4,会拉起一个 master 加四个 worker,充分利用多核 CPU。这个用法在单机压测时特别实用,我现在的默认配置就是这一条。

6.2 压测机不是万能的:端口、文件描述符和内核参数

压测机自己会先撞到几面墙,这几面墙不处理掉,你永远不可能知道被压系统的真实上限。

第一面墙是本地端口耗尽。每个 HTTP 请求都会占用一个本地临时端口,Linux 默认的端口范围大概是 32768 到 60999,可用端口只有两万八千个左右,而且 TIME_WAIT 状态的端口要等 60 秒才会释放。如果你的压测跑了十万次请求,端口会迅速耗尽,报出 "Cannot assign requested address" 之类的错误。解决方法是调整内核参数,扩大端口范围并允许复用:

# 查看当前配置 sysctl net.ipv4.ip_local_port_range sysctl net.ipv4.tcp_tw_reuse # 临时调整(写入 /etc/sysctl.conf 可持久化) sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" sudo sysctl -w net.ipv4.tcp_tw_reuse=1 sudo sysctl -w net.core.somaxconn=65535

第二面墙是文件描述符限制。每个 TCP 连接都是一个文件描述符,默认上限往往是 1024 或者 4096,高并发时会被打满,表现为随机性的连接失败。用ulimit -n 65535临时提升,或者改/etc/security/limits.conf永久生效。注意这个限制要同时作用于启动 Locust 的那个 shell 会话。

第三面墙是 CPU 和网卡。Locust 用 gevent 协程,单进程只能吃满一个核,所以一定要用--processes或者分布式把负载摊开。另外要看sar -n DEV 1观察网卡流量,如果接近千兆网卡的理论上限(约 940Mbps),那就只能换万兆网卡或者增加压测机数量。

现象可能原因处理方式
Cannot assign requested address本地端口耗尽扩大端口范围、开启 tcp_tw_reuse
Too many open files文件描述符不够提高 ulimit -n
压测机 CPU 单核跑满Locust 单进程瓶颈使用 --processes 或分布式
压测机网卡流量接近上限带宽瓶颈增加压测机或压缩响应体
worker 之间结果差异大网络延迟或配置不一致检查网络、统一环境配置

6.3 FastHttpUser:把单机吞吐再抬一截

Locust 默认的HttpUser底层用的是 requests 库,功能全但开销大。如果压测场景对吞吐要求高、又不需要 requests 那些高级特性,可以换成FastHttpUser,它基于 geventhttpclient,单机吞吐通常能提升三到五倍,CPU 和内存占用也更低。

from locust import task, between from locust.contrib.fasthttp import FastHttpUser class MyUser(FastHttpUser): wait_time = between(0.1, 0.5) @task def query(self): self.client.get("/api/health", name="/api/health")

需要注意的差异是,FastHttpUser 的响应对象是FastResponse,resp.json()的用法和 requests 不完全一致,返回的是解析好的字典而不是方法调用结果;另外它默认开启了连接池复用,这对压测结果是有利的,但和某些服务端的短连接场景不完全一致。所以如果被压系统对短连接有特殊处理,还是得用标准 HttpUser 才准确。

7. 把压测变成常态:CI门禁与容量基线

7.1 每次发版前自动跑一遍基线压测

压测最大的价值不在于某一次跑出漂亮数字,而在于发现"这一次比上一次慢了多少"。人肉压测的问题是,节假日前后、版本节奏紧张的时候,压测这件事最先被砍掉,然后性能问题就在线上爆了。我的做法是把压测脚本和参数配置一起放进代码仓库,用 CI 在每次合入主分支或者每日定时跑一遍。

脚本里保存一份基线结果作为对照,新结果和基线对比,超出阈值就报警甚至卡住发布。这个阈值不用定得太死,我一般用"P95 响应时间劣化超过 20%"或者"相同并发下 QPS 下降超过 15%"作为红线。真正执行起来,噪声肯定有,所以我会要求连续两次都超标才判定为回归,避免偶发抖动误伤。

7.2 用退出码做自动化门禁

Locust 默认跑完就返回 0,不论失败率多高。想在 CI 里做门禁,得自己挂事件钩子。Locust 提供了quitting事件,在压测结束前触发,可以在里面判断指标并设置退出码:

from locust import events @events.quitting.add_listener def _(environment, **kwargs): stats = environment.stats.total if stats.fail_ratio > 0.01: print(f"失败率超标: {stats.fail_ratio:.2%}") environment.process_exit_code = 1 if stats.get_response_time_percentile(0.95) > 800: print(f"P95 响应时间超标: {stats.get_response_time_percentile(0.95)}ms") environment.process_exit_code = 1

配合命令行的--headless --only-summary --csv baseline,CI 流水线就能通过退出码判断是否通过,失败了还能把 CSV 和 HTML 报告作为构建产物归档,方便回溯。

7.3 从压测数据反推容量规划

压测结果最终要转化成运维和产品能用的语言。我通常会给出一张三档容量表,用实际压测数据反推:

档位判定标准建议用途
舒适区P99 低于 300ms,失败率 0%日常流量,留足余量
预警区P99 在 300ms 到 800ms 之间大促预估峰值,需提前扩容
危险区P99 超过 800ms 或失败率超 1%必须限流降级,禁止长期运行

这张表比一句"系统支持 2000 QPS"有用得多,因为它同时告诉了业务方"到什么程度开始变差",以及"变差之后该怎么应对"。做容量规划的时候,我会用线上近三个月峰值的 1.5 倍作为目标,验证系统落在这张表的哪一档,然后决定是扩容、加缓存还是做业务侧的限流。

8. 几个反复踩到的坑,写在这里省得你再踩一遍

第一个坑是拿压测环境和线上环境的数据量做对比。测试库只有一万条数据,全表扫描当然快;线上有两千万条,同一个 SQL 慢一百倍都正常。所以压测数据量至少要达到线上的一个数量级,实在造不出全量数据,也要把核心表的索引和分布模拟出来,否则压测结论完全没有参考价值。

第二个坑是把压测日志忘了关。有一次我们的压测脚本在每次请求后都打了 INFO 级别的日志,压到 2000 QPS 的时候,压测机的磁盘 IO 直接跑满,QPS 卡在 1800 上不去,排查了半天才发现是日志把自己拖死了。压测前记得把 Locust 的日志级别调到 WARNING,业务代码里的调试日志也一并关掉。

第三个坑是预热不足。JIT 编译、连接池初始化、缓存加载这些动作在压测刚开始的几十秒里都会造成性能波动。我现在固定会在正式统计之前跑一个 30 秒的预热阶段,on_start里发几个请求把链路先走通,然后再看稳态数据。Locust 本身没有内置预热开关,用-r控制爬坡速率加上一个短时预热脚本基本能覆盖。

第四个坑是只压应用层不压依赖。数据库、缓存、消息队列这些下游组件的连接池上限,经常是真正的瓶颈。压测的时候一定要同时盯着服务端监控,看连接池的使用率、慢查询数量、缓存命中率这些指标,光看 Locust 的报告永远只能看到表象。我自己最常用的一个判断方法是:如果应用层 CPU 不高、但 QPS 上不去、响应时间又很平,那八成是被下游的连接池或者限流卡住了。

最后分享一个我自己一直在用的小习惯:每次压测结束后,把压测脚本版本、参数配置、服务端配置、监控截图和最终报告打包成一个目录按日期归档。看起来有点繁琐,但当你半年后被问"这个接口上次压测是什么结果"的时候,能三分钟内翻出来,这个习惯就值回票价了。

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

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

立即咨询