Locust 性能测试实战:从脚本到可复现的压测报告

压测最常见的翻车不是脚本写不出来,而是"结论没法复现"——同一脚本今天 P99 800ms 明天 P99 2s,谁也说不清是系统变了还是测试本身的问题。这篇讲用 Locust 做压测时,如何把"脚本"升级成"可复现的实验"。

一、脚本设计:用户模型,不是请求列表

Locust 的 HttpUser 是"一个真实用户的行为模型",不是接口调用清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ChatUser(HttpUser):
wait_time = between(2, 8) # 操作间隔:真实用户不是机器

def on_start(self):
# 登录 + 拉初始会话列表:每个虚拟用户的"开场动作"
self.client.post("/api/login", json=self.cred)
self.client.get("/api/conversations")

@task(3) # 权重 3
def open_conversation(self):
cid = self.random_conversation()
self.client.get(f"/api/conversations/{cid}/messages?limit=20")

@task(1) # 权重 1
def send_message(self):
cid = self.random_conversation()
self.client.post(f"/api/conversations/{cid}/messages", json={...})

@task(1)
def search(self):
self.client.get(f"/api/search?q={self.random_keyword()}")

要点

  • 权重比就是流量模型:开会话 : 发消息 : 搜索 = 3:1:1,这个数字应该来自线上 access log 的统计,不是拍脑袋;
  • wait_time 用分布而不是固定值:between(2,8) 模拟人类节奏,固定 3s 会让 QPS 曲线出现假性规律;
  • on_start 里的登录不要放进 @task——登录只该发生一次,否则登录接口 QPS 被虚高 10 倍。

二、压测曲线:阶梯式,不要一上来满负载

"直接上 500 并发看崩溃"得不到任何有用结论。阶梯曲线每一级回答一个问题:

1
2
3
4
5
6
7
8
9
10
11
# locustfile 外部控制(locust -f locustfile.py --headless 不适用曲线控制,
# 用 locust 的 custom shapes 或外部脚本调 REST API 改并发数)
class StepLoadShape(LoadTestShape):
STEPS = [(50, 300), (100, 300), (200, 300), (400, 300)] # (并发, 持续秒)

def tick(self):
elapsed = time.time() - self.start_time
for users, duration in self.STEPS:
if elapsed < duration:
return users
return None # 全部阶梯完成,结束

每级阶梯记录:平均 RT / P95 / P99 / 错误率 / 依赖侧 CPU。判读规则:

  • P99 开始非线性上涨的那一级 = 拐点,拐点并发数就是容量的工作值;
  • 错误率在拐点前就出现(如 504)= 依赖先于自身瓶颈(DB 连接池、下游限流),去查依赖;
  • 每级之间留 30s 的"稳定窗口"再采样,爬坡期的数据不要进结论。

三、复现性:压测报告必须附"实验元数据"

这是"结论没法复现"的直接解药——每次压测落一份元数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
run_id: perf-20240615-02
timestamp: 2024-06-15T20:40:00+08:00
target:
base_url: https://staging.api.xxx.com
git_sha: a3f9c21 # 被测系统版本(问运维要)
deploy_env: staging
locust:
version: 2.24.1
users_curve: [50, 100, 200, 400]
spawn_rate: 10
host_overrides: []
data:
user_pool: "perf_users_500" # 测试账号池(预生成,登录态预置)
conversation_pool: 200 # 预热的会话数据量
metrics:
at_200_users: {p95: 180, p99: 410, err_rate: 0.0}
at_400_users: {p95: 620, p99: 1900, err_rate: 0.02}
inflection: 200-400 # 拐点区间

三条纪律:

  1. 被测系统必须锁定 git sha——"staging 环境"不是版本,版本是 sha;
  2. 数据池预生成:测试账号、会话、商品数据在压测前准备好(和 2026 年那篇"数据工厂"衔接),压测中现造数据会把 DB 写入延迟混进结论;
  3. 同机压测要声明:压测机与被测机同机房/同网段时,结论只对该网络条件成立,元数据里写明拓扑。

四、常见误读

现象 常见误读 实际可能
高并发下 P99 暴涨、P50 正常 应用慢 连接池耗尽后的排队(看依赖侧等待时间拆分)
错误率 0.1% 集中在某接口 接口偶发 bug 该接口超时阈值 < 其 P99(阈值和真实分布不匹配)
阶梯每一级 P95 都涨 20% 线性劣化 正常——RT 随并发上升是必然,斜率突变才是信号
压测机 CPU 100% 系统到瓶颈了 压测机先饱和,被测系统还没到(查 locust 进程 CPU)

五、小结

Locust 只是"发压的枪",压测的含金量在流量模型(权重+节奏)、曲线设计(阶梯+稳定窗)、实验元数据(sha+数据池+拓扑)。做到这三点,压测报告才是可复现的实验记录,而不是"看起来很快"的截图。