news 2026/9/11 13:04:11

Locust压测实战指南:从脚本编写到分布式压测的完整攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Locust压测实战指南:从脚本编写到分布式压测的完整攻略

1. 为什么最后选了Locust:压测工具对比后的真实结论

先说个背景。去年底我们给一个支付回调接口做容量评估,线上峰值QPS预估在3000左右,压测目标是把服务打到极限,看看它在什么时候开始报错、数据库连接池什么时候被拖垮。一开始团队里有人提议直接用JMeter,理由是"大家都会";也有人建议上wrk,说C写的性能好。但真的打开JMeter开始录制脚本时,问题就来了:回调接口的签名校验逻辑在代码里,JMeter里想模拟一套完整的签名生成流程,要么写BeanShell脚本,要么自己写个JAR包塞进classpath,折腾半天还没开始压。

后来我提了个方案:用Python Locust重写整套压测逻辑。理由很简单——接口的验签、加解密、请求头拼装这些逻辑,本来就在Python服务端代码里,直接复用一套签名工具函数,在Locust脚本里调用就行,不用做任何跨语言翻译。最终压测顺利跑完,过程也比预想中干净得多。

Locust这个框架,核心就一句话:用纯Python代码描述用户行为,用协程(gevent)模拟海量并发。你不需要学一套XML配置语言,不需要拖组件连线,所有逻辑都写成普通Python方法,想怎么玩就怎么玩。它的并发模型和JMeter的线程池模型有本质区别,JMeter每个虚拟用户就是一个线程,线程切换和内存开销都不小;Locust的每个虚拟用户是一个协程,一个进程可以轻松支撑几千个协程,单机压测能力在绝大多数场景下是够用的。

如果你也在选型,我给一个直白的建议:

  • 如果压测目标是HTTP API,且你需要复杂的业务逻辑(登录态、签名、数据关联),无脑选Locust,别犹豫。
  • 如果压测目标是数据库、文件系统这类底层协议,或者需要依赖JMeter生态里现成的协议插件,那JMeter更合适。
  • 如果只是想快速测一下Nginx的转发性能,wrk就够了,没必要上框架。

这个判断逻辑是:性能测试工具的本质是帮你把压力生成这件事抽象掉,让你专注在业务场景上。业务越复杂、越贴近代码逻辑,越该选代码型工具。

2. 环境准备和安装:版本选择、依赖坑和第一个启动

2.1 Locust版本怎么选,Python版本怎么配

安装看上去简单,pip install locust一行搞定,但版本坑我觉得值得先说清楚。Locust从1.0版本开始API有重大调整,早期的from locust import Locust继承类写法已经废弃了,现在统一用HttpUser这个类。如果你在网上搜到旧教程,代码里还在用class User(Locust),那是0.x时代的写法,在2.x版本下直接跑不起来。

我在生产环境压测用的Python版本是3.8到3.11都验过,没有发现兼容性问题。比较稳妥的选择是Python 3.9或3.10,这两个版本在绝大多数CI环境里都预装了,不用额外折腾。Locust本体依赖的核心库是gevent、geventhttpclient、msgpack、psutil这几个,pip会自动拉取,正常情况下不会出幺蛾子。

安装命令:

python -m venv locust_env source locust_env/bin/activate pip install locust locust --version

这里有一个很多人忽略的细节:建议用虚拟环境,而不是直接装到系统Python里。原因有两个——一个是Locust的依赖版本迭代比较快,直接装全局容易和系统里其他项目的flask、requests版本冲突;另一个是压测机上我经常需要同时跑多个版本的Python项目,虚拟环境隔离避免了互相污染。

2.2 macOS和Windows上的两个"新手劝退"问题

如果你在macOS(尤其是M1/M2芯片)上安装时看到gevent相关的编译报错,比如error: command 'clang' failed with exit status 1,十有八九是因为Homebrew的Python环境缺少编译依赖。解决办法是在装Locust之前先装gevent的二进制wheel:

CFLAGS="-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include" pip install gevent

Windows上的问题不一样。很多人装好了Locust,本地起一个压测脚本发现并发数到1000左右就上不去了,各种ConnectionError满天飞。这个锅不全在Locust,Windows默认的临时端口范围有限。用管理员权限执行下面两条命令扩一下范围再压:

netsh int ipv4 set dynamicport tcp start=10000 num=50000 netsh int ipv4 set dynamicport udp start=10000 num=50000

而且Windows上的gevent对select模型的适配不如Linux,我们后来规范化为:本地开发用macOS或Windows做语法验证和单机小并发调试,正式压测一律跑在Linux机器上。这不是偏见,是实测数据说话。同样一个脚本,Windows上跑800并发就开始报错,同样的机器配置换Linux跑3000并发稳如老狗。

2.3 最小可用示例:先让压测跑起来

环境装好后,我习惯先用一个最小的脚本验证框架可用性,再往里面填充业务逻辑。这个最小脚本长这样:

from locust import HttpUser, task, between class QuickUser(HttpUser): wait_time = between(1, 3) @task def hello(self): self.client.get("/health")

locust --host http://127.0.0.1:8080/启动之后,浏览器打开http://localhost:8089,填入用户数和 spawn 速率,就能看到压测曲线了。

别小看这个脚本,里面每一个配置都有讲究:wait_time控制的是每个虚拟用户在执行完一次task之后、开始下一次task之前的等待时间,模拟的是真实用户的思考时间;@task标记了这是用户会执行的行为;self.clientHttpUser内置的HTTP客户端,它本质上是requests.Session,会自动处理Cookie、连接复用。所以你在脚本里向服务端登录之后,后续所有请求都会自动带着登录态,这是Locust脚本比JMeter脚本好写很多的一个点。

3. 写压测脚本的核心:任务集、权重、等待时间和用户数据

3.1 @task和TaskSet的正确打开方式

很多刚上手Locust的人会问一个问题:一个压测场景里,用户行为有登录、浏览、下单、支付,怎么组织脚本结构?答案是TaskSet类。

TaskSet可以把一组相关任务绑定在一起,并且支持任务间权重控制:

from locust import HttpUser, TaskSet, task, between class UserBehavior(TaskSet): def on_start(self): self.client.post("/login", json={"username": "demo", "password": "123456"}) @task(5) def browse_product(self): self.client.get("/product/1001") @task(1) def create_order(self): self.client.post("/order", json={"product_id": 1001, "quantity": 1}) @task(1) def pay_order(self): self.client.post("/pay", json={"order_id": self.order_id}) def on_stop(self): self.client.post("/logout") class WebsiteUser(HttpUser): tasks = [UserBehavior] wait_time = between(1, 5)

解释几个关键点。

@task(5)里的数字是权重。上面这个例子里,browse_product背负了5份权重,create_order和pay_order各1份,意味着在随机分配任务时,浏览商品的概率是创建订单的5倍。这个权重值应该怎么定?最理想的情况是从线上埋点数据里统计真实用户的行为比例。没有埋点数据的话,就让业务方按经验给一个估算值,总比所有任务等概率分布要贴近真实场景。

on_start方法在每个虚拟用户启动时执行一次,这个设计用来做登录、初始化、读取种子数据这类一次性操作。注意它是在协程里运行的,所以在这里做的耗时操作(比如远程读取一批测试账号)会阻塞这个虚拟用户后续的任务调度。如果你压测场景需要大量用户,每个人都去远程拉数据,那压力生成阶段会非常慢,数据接口本身反而成了瓶颈。

3.2 wait_time四种策略怎么选

等待时间的设置看似简单,实则是压测结果可信度的关键。如果完全不等待,每个虚拟用户会尽快循环执行任务,这会造成一种"比真实场景更猛烈"的压力,适合做极限打满测试,但不适合做容量评估。

  • constant(3):每个任务后固定等3秒,适合模拟机械性操作,比如机器人轮询接口。
  • between(1, 5):每个任务后随机等待1到5秒,模拟真实用户的行为波动。
  • constant_pacing(5):保证每个任务从开始到下一次开始之间的时间跨度固定为5秒。如果你的目标是让每个用户每秒固定发出N个请求,用这个。
  • constant_throughput(2):这个策略最特殊,它把用户吞吐量换算成等待时间,目标是让每个用户的请求频率恒定在每秒2次。注意它里面的total_time是任务执行时间和等待时间之和,所以在做慢接口压测时要小心,如果接口响应时间本身超过了设定的吞吐间隔,它会补偿等待时间为0。

我实际用得最多的是betweenconstant_pacing。前者做容量评估,后者做"用户行为不可变、后端响应变慢"的排队场景模拟。有一点必须提醒:如果被压接口响应时间很长(比如超过wait_time),虚拟用户的任务循环会退化成"等响应+立马发下一个请求"的模式,这时候压力曲线就完全由响应耗时驱动了。这种情况要么是设计问题(目标就是测长任务),要么就是后端已经快挂了,你要能分辨清楚。

3.3 参数化和数据隔离:别让所有用户都用一个账号

压测里最怕的就是环境不支持高并发登录,然后你的脚本又让每个虚拟用户复用同一个账号去登录,结果压测还没开始,登录接口先被打崩了。

解决方案是创建用户数据文件,每个虚拟用户使用自己独立的数据。我用得比较顺手的方式是从CSV里读取账号,在on_start里按虚拟用户序号取一行。

import random from locust import HttpUser, task, between class DataDrivenUser(HttpUser): wait_time = between(1, 2) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.user_data = None def on_start(self): with open("users.csv", "r") as f: lines = f.readlines() line = random.choice(lines) username, password = line.strip().split(",") resp = self.client.post("/login", json={"username": username, "password": password}) if resp.status_code == 200: self.token = resp.json()["token"] self.client.headers.update({"Authorization": f"Bearer {self.token}"})

注意这里有一个容易被忽略的点:on_start里的文件读取不能写成加载一堆数据到内存里分发给所有用户,因为每个用户协程都是独立执行的,你的一次open()只对当前用户生效。如果你有1000个虚拟用户,这个文件会被打开1000次。真正生产级的做法是提前加载到内存,然后用self.environment.runner.user_classes配合用户ID做索引,或者用一个全局的itertools.cycle生成器,让每个用户按顺序拿测试数据,避免重复随机选择导致的碰撞。压测过程中如果客户端的token失效了,还要在后续任务执行前主动做一次重新登录,否则压测后期的请求全返回401,统计结果没什么参考价值。

3.4 失败处理和依赖任务的组织

压测脚本里还有一个很容易被忽视的点:失败任务的语义。默认情况下,self.client.get()方法如果遇到网络错误或超时,会抛异常,Locust会记录失败次数。但如果接口返回4xx、5xx状态码,requests库不会主动抛异常,Locust也不认为这是失败,只会记录状态码统计。如果你想把这些非200响应标记为失败,需要显式判断:

with self.client.get("/order/list", catch_response=True) as resp: if resp.status_code == 500: resp.failure("Server error") elif resp.status_code == 200: if "expected_data" not in resp.text: resp.failure("Response content mismatch")

这个catch_response上下文管理器是Locust里最实用的API之一,它不会吞掉响应,只是允许你自定义成败判定。在企业内部压测时,比"有没有报错"更重要的是"返回的响应内容是否符合预期"——接口200了,但返回的是一段错误兜底JSON,这在压测报告里很难看,必须靠脚本层面拦住。

任务依赖又是一个比较细的点。比如下单任务依赖登录,登录任务依赖注册。Locust里没有现成的"前置任务"机制,但你可以用实例变量保存状态,然后在每个任务开头判断前置条件是否满足,不满足就直接跳过当前任务,或者调用self.interrupt()跳出当前TaskSet。我用过的模式是:登录请求放在on_start,下单任务里如果发现token失效,就重新登录再下单。这样的设计让任务之间保持低耦合,后面接分布式压测时不容易踩坑。

4. 跑起来:Web模式、命令行参数和结果怎么读

4.1 Web模式的操作节奏和技巧

本地调试时我基本都用Web模式。启动命令:

locust --host https://api.example.com --web-host 0.0.0.0 --web-port 8089

浏览器打开后,界面会让填两个参数:Number of users(总用户数)和Spawn rate(每秒启动多少用户)。我第一次用的时候也是草草填个100、10就开始压了,但实际压测的节奏是有讲究的:

  • 第一个阶段:设置一个较小的并发(比如50),观察接口延迟,确认脚本逻辑没问题、没有权限报错。
  • 第二个阶段:逐步提高并发,观察RPS和P95延迟的曲线变化,找到拐点。
  • 第三个阶段:保持高并发持续运行一段时间(至少10分钟),看是否有内存泄漏、连接池耗尽这类延迟出现的问题。

Web模式的图表页签展示的是RPS、响应时间、用户数三个核心指标的时间曲线。Charts页里能直接看到直方图,Request Stats表格里能看到每个接口的平均响应时间、中位数、90%分位、95%分位、99%分位和失败率。全绿不代表系统健康,要重点看的是P95曲线有没有随用户数增加而急剧拉升。

Web模式最大的坑是它本身也是占用资源的。压测时浏览器刷新图表会比较耗CPU,Web服务本身也跑在Locust进程里。所以大规模压测时(比如分布式、几千并发),我从来不在压测机上开Web界面,全程用headless模式跑,压完了再生成HTML报告。

4.2 命令行模式:CI里跑压测的正统姿势

命令行模式更适合脚本化、自动化。直接给参数,让Locust跑完自动退出:

locust --host http://target.example.com --headless -u 300 -r 20 --run-time 10m --csv=result --html=report.html

这里-u 300表示模拟300个用户,-r 20表示每秒启动20个用户,--run-time 10m表示运行10分钟。如果只跑30秒就结束,数据波动太大没有参考意义;我一般根据接口实时性设定,最短5分钟。

--csv=result会生成三个文件:

  • result_stats.csv:聚合统计,每个接口一行,包含请求数、平均响应时间、各分位数、RPS、失败率。
  • result_stats_history.csv:按秒采样的时间序列数据,适合自己写脚本画图。
  • result_failures.csv:失败明细。

--html=report.html生成的是离线HTML报告,比Web界面上的数据更全,含图表和分位数汇总。这个报告可以直接发给团队其他人看,不需要他们再装Locust环境。

生产环境里我还常用一个参数:--stop-timeout 30。它控制的是在到达--run-time之后,优雅停止虚拟用户前等待的时间,用来让已经开始的任务跑完。如果不设置,Locust可能在任务执行一半时强行停止,导致日志里出现大量被中断的请求,影响失败率统计。

4.3 报告怎么读才是关键

一个很多人会犯的错:只看平均值。平均响应时间是一个很欺骗性的指标——接口偶尔延迟3秒,但绝大多数请求是50ms,平均值可能就只有70ms,看起来一切正常,实际上用户体验已经很差了。我通常按这个顺序读报告:

  • 第一看RPS(每秒请求数)是不是达到预期目标。
  • 第二看失败率,任何一种失败(超时、TCP连接被重置、HTTP 5xx)都要定位原因。
  • 第三看P95、P99相比P50的放大倍数,如果P95是P50的5倍以上,说明系统存在明显的长尾延迟。
  • 第四把RPS曲线和P95曲线放在一起看,找到两者交叉的区域——这个区域往往就是系统吞吐的瓶颈点。

压测做完之后,经常要回答一个问题:"系统能扛多少并发?"我的回答从来没有一个数字。因为"能扛"取决于你对延迟的容忍底线。同样是500并发,如果P95在200ms以内是"能扛",如果P95在5秒以上就算"不能扛"。所以报告里一定要同时标注目标和实测,不要只给一串平均值。

5. 分布式压测:主从模式配置和真正的瓶颈来源

5.1 什么情况下你才需要分布式

单机Locust一个进程能跑几千协程,那么在什么情况下需要考虑分布式?

  • 目标QPS远高于单机能生成的范围(比如网关接口目标QPS过万)。
  • 压测目标机器的带宽和CPU受限于压测机本机,需要分散压力源。
  • 业务脚本里本身有复杂的CPU计算(比如签名、加解密),每个协程占用CPU时间过长,单进程的并发上限被压缩。

我遇到的最典型的例子是:压测一个带AES-256解密+验签的网关接口,单机2000个虚拟用户时,压测机自己CPU跑满100%,后端服务还没到瓶颈,瓶颈出现在压测机上了。这种情况不上分布式不行。

5.2 主从模式的启动方式

分布式压测的架构是:一个master节点负责调度和汇总结果,N个worker节点负责实际发送请求。

# master节点启动 locust --host https://api.example.com --master --master-bind-host 0.0.0.0 --master-bind-port 5557 # worker节点启动(可以在不同机器上) locust --host https://api.example.com --worker --master-host=192.168.1.100 --master-port=5557

启动之后,在master的Web界面上能看到所有worker在线,设置好用户数和启动速率,master会把用户分发到各个worker上执行。

分布式模式下的用户数是全局的。比如你设置2000个虚拟用户,有两台worker,那么每台worker上大约跑1000个虚拟用户。你可以通过--master界面的Worker列表面板确认这一点。

这里有几个实战中的细节:

  • worker和master之间通过消息传递,默认端口是5557,如果跑在云上,安全组要放通这个端口。
  • master节点不发送任何压测请求,它只做任务分发和结果汇总。
  • 如果worker节点上的脚本和master不一致(包括函数名、类名、任务权重),worker会收到切换任务失败的错误。所以我每次改动脚本后,都要同时同步到所有worker。

5.3 分布式的三个隐藏瓶颈

分布式不是无脑加机器就完事,有几个隐藏瓶颈值得先说清楚。

首先是用户数据在多worker间的隔离问题。如果你的脚本从同一个CSV文件里读账号,且每个worker启动时随机选择账号,可能出现两个worker选到同一个账号,导致服务端登录冲突。解决办法是:按worker序号分片,让每个worker读取CSV文件中对应该worker的区间,保证数据不重叠。

import os import csv # 假设每个worker有一个环境变量WORKER_ID,从0开始 worker_id = int(os.environ.get("WORKER_ID", "0")) worker_num = int(os.environ.get("WORKER_NUM", "1")) with open("users.csv") as f: rows = list(csv.reader(f)) my_rows = [row for i, row in enumerate(rows) if i % worker_num == worker_id]

其次是master节点本身可能成为瓶颈。压测过程中,所有worker会把每个请求的统计数据实时上报给master,如果请求量非常大,master节点上的网络带宽和消息处理会成为新的瓶颈。压测时我会单独观察master机器的CPU和内存占用,如果master的CPU持续超过80%,说明汇总链路已经吃紧了,要么减少worker数量,要么放宽统计上报的频率。

最后是网络链路阻赛的干扰。分布式压测的多个worker如果在不同机柜、不同机房,他们到目标服务的网络延迟本身就是不一致的。压测结果里的响应时间统计会包含跨机房的固定网络开销,这会让P99被拉高。所以大规模分布式压测我一般建议:worker和被测服务部署在同一内网,避免跨公网压测导致的数据失真。

6. 进阶玩法:自定义负载策略、断言和持续集成

6.1 用LoadTestShape实现"阶梯加压",别一步到位

默认的压测模式固定了用户数和每分钟启动速率。有时候我们想模拟更复杂的负载曲线:先让500个用户在2分钟内启动,稳定5分钟,然后在医疗器械3分钟内升到1000个用户,再稳定5分钟,最后一步步退。这种阶梯式加压在Locust里有内置支持。

from locust import LoadTestShape class StepLoadShape(LoadTestShape): time_limit = 1800 def tick(self): run_time = self.get_run_time() if run_time < 120: return (500, 10) elif run_time < 420: return (500, 0) elif run_time < 600: return (1000, 8) elif run_time < 900: return (1000, 0) else: return None

tick方法在每个时间点都会被调用,返回一个(user_count, spawn_rate)元组,表示当前阶段的目标用户数和启动速率。返回None表示停止压测。这个类的好处是:整个压测过程不需要人工干预,一次性跑完,并且能形成完整的阶梯曲线,方便观察系统在哪个阶段开始出现拐点。

我自己实际过程中发现,阶梯式加压对定位容量拐点非常有帮助。如果系统在800用户时P95还在100ms以内,但900用户时突然飙到2秒,这个"断崖式"下跌在阶梯图上非常直观。

6.2 断言和失败归类:让报告能直接给结论

默认情况下,Locust只把异常和HTTP非2xx响应记为失败。但在业务压测里,状态码是200不代表业务逻辑成功。比如一个订单接口,返回200但code字段是50001,业务上其实是失败的。

我会在脚本里做业务断言:

from locust import HttpUser, task, between class OrderApiUser(HttpUser): wait_time = between(0.5, 1.5) @task def create_order(self): with self.client.post( "/api/order/create", json={"sku_id": "A1001", "quantity": 1}, catch_response=True, ) as resp: data = resp.json() if data.get("code") != 0: resp.failure(f"Business error: {data.get('message')}") elif resp.status_code != 200: resp.failure(f"HTTP {resp.status_code}")

报告里除了HTTP错误,还会出现Business error这类业务失败明细,这对研发定位问题非常高效。

6.3 跑在CI流水线里:压测结果自动化判断

分布式压测做完之后,脚本化、自动化和阈值判断往往是一套连招。把它集成到GitLab CI或者Jenkins里,最关键的一步是能够自动判断"这次压测是成功还是失败"

Locust没有内置的断言机制,但命令行提供了--exit-code-on-error参数,它控制的是在运行时如果出现了失败(异常或断言失败),进程以非0码退出。默认情况下进程是以0码退出,不管压测结果如何,这就导致CI流水线没法自动判断。设置成:

locust --headless --host http://target.example.com -u 100 -r 10 --run-time 5m --exit-code-on-error 1

只要有失败请求,进程就会返回非0码,CI流水线自动标记失败。

不过这个判断颗粒度太粗了。我是更倾向于写一个后处理脚本,解析CSV报告,然后判断多个阈值条件。比如:

import csv import sys with open("result_stats.csv") as f: reader = csv.DictReader(f) for row in reader: if row["Name"] == "/api/order/create": p95 = float(row["95%"]) fail_ratio = float(row["Failure %"].strip("%")) if p95 > 200: print(f"P95 too high: {p95}ms") sys.exit(1) if fail_ratio > 0.01: print(f"Failure ratio too high: {fail_ratio}%") sys.exit(1)

这样CI阶段的判断标准就和业务目标强关联:P95不能超过200ms,失败率不能超过1%。如果这次压测只关注吞吐量,就把RPS也加进判断条件。阈值没有通用值,每个系统不同,但逻辑结构是完整的。

7. 实战中踩过的坑和规避方法:一份排查清单

7.1 压测机自身资源成为瓶颈

很多人忽略了压测脚本里加密解密、签名计算对CPU的消耗。如果你的接口每个请求都要做一次RSA签名,压测机的CPU会先于被测系统被打满。判断方法是压测时在压测机上看一下top——如果用户态CPU占用接近100%,说明脚本本身太重了,压测结果反映的是压测机的性能上限,而不是服务端的性能上限。

遇到这种情况我的做法是,把签名运算从虚拟用户协程里挪到一个独立的预热阶段完成,把签名结果缓存到全局字典里,请求时直接取。这样压测机的CPU占用能明显降下来。如果业务逻辑不允许缓存,那就只能通过分布式压测把计算压力分散到多台机器上。

7.2 本机连接数限制和内核参数

Linux单进程能打开的socket数量不是无限的,默认的ulimit -n通常是1024,对压测来说远远不够。如果不改,压测刚开始就会出现一堆[Errno 24] Too many open files的错误,很多人误以为是目标服务挂了,其实是压测机自己的连接数被打满了。

压测前在压测机上执行:

ulimit -n 1048576 sysctl -w net.ipv4.ip_local_port_range="1024 65535" sysctl -w net.ipv4.tcp_tw_reuse=1 sysctl -w net.ipv4.tcp_fin_timeout=15

ip_local_port_range是分配给本机发起连接时使用的端口范围,默认从32768开始,总共才3万多一点,对于一个高并发的压测机来说可能不够用。tcp_tw_reusetcp_fin_timeout是调整TIME_WAIT状态的快速回收,压测结束时可以看到大量TIME_WAIT连接,这些都是主动关闭连接留下的,不调整的话端口可能被占满,导致后续连接无法建立。

7.3 被压服务前的负载均衡或网关成为"隐藏瓶颈"

有一次压测遇到一个诡异的情况:直接把服务部署的实例扩容到了10个,压测结果却显示RPS始终上不去,而且三台压测机同时打着同一个被压域名。后来排查发现,问题是入口的Nginx配置的worker_connections默认只有1024,而这是被压服务的前置网关。

这种场景非常典型。压测结果不好时,排查链路应该是:压测机到网关的连接是否建立了?网关到后端服务的连接池是否够用?网关的keepalive是否开启?服务端的TCP backlog是否足够?有时候我们要压的是单个微服务,但压测请求需要经过API网关、鉴权服务、路由层,任何一层性能不够,压测结果都是在测那一层的上限,而不是被测服务的上限。

如果你只想压测一个具体的后端服务,最好的方式是绕过网关,直接用内部服务地址去压,这样才能得到这个服务的真实容量。压测网关本身是另一个场景,要把两个场景分开来看。

7.4 gevent和常见Python库的兼容性

Locust底层是gevent,而gevent会把标准库的网络调用patch成非阻塞模式。如果你的压测脚本里用到了某些不走标准库的第三方HTTP客户端(比如httpx),或者用了自己封装的Socket连接,可能会出现请求卡住不返回或者行为奇怪的问题。最稳妥的做法是:所有网络请求都通过self.client发,它是gevent兼容的。

另外不要在主协程里做需要等待大量任务完成的同步操作(比如time.sleep),虽然它在gevent下也有效,但它会让当前协程挂起,影响任务调度精度。需要等待的话可以用gevent.sleep,它和协程调度器的结合更紧密。

7.5 分位数口径不要搞混

报告里的50%95%99%是响应时间的百分位,但注意Locust的统计逻辑是基于聚合周期的——默认每2秒记录一个采样点,然后合入全局统计。如果你在压测中还需要关注某个时间段的极值,光看最终报告是看不出突刺的,要配合stats_history.csv文件里的时间序列数据来分析。我曾经在处理一个P99飙升的问题时,只看最终报告觉得还行,后来拉了时间序列文件才发现,某个3秒的窗口内P99高达8秒,而后面的采样点把它稀释了。这种瞬时突刺在最终报告里几乎看不出来,但对线上用户的直接影响是巨大的。

7.6 主从模式下任务不一致的典型报错

分布式模式时最容易出的报错是:

worker received incorrect message, could not unpack

这个报错十有八九是master和worker节点的Locust版本不一致。两个节点执行locust --version,把版本统一成同一个就解决了。

还有一种情况是worker节点启动时没有传--host参数,虽然任务分发时会带上目标地址,但有时会因为host缺失报一些莫名其妙的错误。建议所有worker节点都显式传--host,避免不必要的麻烦。

写在最后

压测这事做久了,你会越来越清楚一个道理:工具只是手段,压测的价值在于让系统在真实流量到来之前暴露出它最脆弱的地方。Locust最大的优势不是它比JMeter功能更多、比wrk性能更高,而是它用Python把压测脚本的门槛降到了最低——你不需要额外学一套技能,只要会写Python,就能把业务逻辑复刻到压测场景里。

我最后再分享一个自己的习惯:正式压测跑之前,我总会先用--headless -u 1 -r 1 --run-time 30s跑一遍全流程脚本,确认每个任务都不报错,而且观察一次完整请求的响应时间。这一步虽然简单,但能在真正压测前过滤掉80%的脚本低级问题。

如果你的项目中还没有引入Locust,我建议从一个小接口、一次小规模压测开始试水,别一上来就搞分布式。等熟悉了它的模型、读得懂图表了,再逐步把压测推广到核心业务链路里。这个框架的快乐是实实在在的,越用越顺手。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 13:04:01

5 个场景讲透 electerm:一台电脑管完所有远程连接

5 个场景讲透 electerm&#xff1a;一台电脑管完所有远程连接 【免费下载链接】electerm &#x1f4fb;Free and open-sourced terminal/ssh/sftp/ftp/telnet/serialport/RDP/VNC/Spice client(Linux, Mac, Windows, Android, HarmonyOS, iOS) 项目地址: https://gitcode.com…

作者头像 李华
网站建设 2026/9/11 13:02:17

微网群分布式优化调度:目标级联法原理与Matlab实现

1. 项目背景与核心价值微网群分布式优化调度是当前能源互联网领域的前沿研究方向。随着可再生能源渗透率不断提高&#xff0c;传统集中式调度方法在计算效率、隐私保护和扩展性等方面面临严峻挑战。目标级联法&#xff08;Analytical Target Cascading, ATC&#xff09;作为一种…

作者头像 李华
网站建设 2026/9/11 13:01:25

CSDN技术文章标题与摘要优化全攻略

1. 文章标题与摘要的黄金法则&#xff1a;如何提升CSDN文章曝光率在CSDN这样的技术社区发布内容时&#xff0c;标题和摘要的质量直接决定了文章的点击率和传播效果。作为拥有十多年内容创作经验的博主&#xff0c;我见过太多优质内容因为标题和摘要的失误而被埋没。今天我们就来…

作者头像 李华
网站建设 2026/9/11 12:59:21

Hyperframes超帧技术实战:从插帧补帧到运动补偿的完整指南

做视频这一行&#xff0c;帧率是绕不过去的话题。无论是后期剪辑、慢动作制作&#xff0c;还是把老片子转成高帧率重新发布&#xff0c;我们天天都在跟 frame 打交道。今天要聊的是个偏进阶的方向&#xff0c;我习惯叫它 hyperframes&#xff0c;也就是"超帧"——通过…

作者头像 李华
网站建设 2026/9/11 12:55:11

SAP PP新项目实战要点:主数据、MRP与生产订单全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 12:54:51

2026智能叫班系统选型指南:从定时响铃到AIoT数据闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华