API压测框架-locust
约 1564 字大约 5 分钟...
关于性能测试
- 目标:性能测试是软件开发生命周期中的一个关键阶段,旨在识别潜在的瓶颈,并确保应用程序在预期的用户负载下满足其性能标准
一、性能测试指标
- 响应时间
- ❌ 平均响应时间(ART)意义不大
- ✅ 最大响应时间(MRT)
- ✅ 百分位响应时间:P50、P90、P99
- 吞吐量
- ✅ 请求速率(RPS)
- ✅ 网络吞吐量(MB/s)
- ❌ 事务速率(TPS)
- 容量指标
- ✅ 最大并发用户数(MCU)
- ✅ 系统承载峰值
二、性能测试中关键可视化工具
退化曲线(Degradation Curve)
- 说明:描绘
系统的负载
(用户数量)与响应时间
之间的关系 - 坐标说明:以横轴上的区间代表
系统负载/用户数量
,纵轴上的点表示对应负载下的响应时间
- 作用:
- 确定系统的负载极限
- 关键区域:
- 单用户区域:最佳性能基准
- 性能平稳区:没有明显性能下降的情况下实现最佳性能
- 压力区域:负载增加后系统开始优雅地降级,标志着性能问题的开始
- 性能拐点:性能下降突变的地方
- 样例:
直方图(Histograms)
- 说明:描绘测试数据的分布情况,将数据分成多个区间,显示每个区间的频数或频率。
- 作用:
- 很好地说明响应时间的集中和分散情况
- 对极值/蜂刺值的洞察
- 坐标说明:以横轴上的区间代表
数据范围
,纵轴上的条形表示这些范围内数据的频率
,性能测试的直方图通常呈现为正态分布 - 关键区域:
- 最长的矩形:表示大多数数据点聚集在此处
- 样例:
三、性能测试工具选择
测试工具 | Locust | wrk | ab | JMeter |
---|---|---|---|---|
并发模型 | 协程(gevent) | 多线程 + Epoll | 多进程 | 线程组 |
协议支持 | 全协议(需代码实现) | HTTP | HTTP | 多协议(支持插件扩展) |
测试脚本 | Python 代码 | Lua 脚本 | 无脚本 | GUI+XML |
分布式 | 原生支持 | 需第三方工具 | 不支持 | 需插件 |
报告能力 | Web UI+CSV | 基础控制台输出 | 基础控制台输出 | 丰富 HTML + 图表 |
学习曲线 | 中等(需 Python 基础) | 低(基础使用) | 极低 | 高(功能复杂) |
结论:如果要系统性的测试,在可视化呈现效果、上手度上,推荐使用Locust
测试案例1:比较两个接口的性能
背景:
新、旧服务入参为不同的图片格式(HEIF、JPG),输出OCR结果,需要对新、旧两个接口进行测试,评估在耗时上的差异。 已知:HEIF格式图片大小更小,传输更快;JPG图片解码库速度更快;需要比较传输、解码最终对接口的影响哪个更大
测试思路:
- 控制新、旧服务使用相同内容、不通格式的图片
- 控制测试的API在同一个时间、同样的服务器
- 选择业务场景的常用尺寸,另外选择一些其他不同尺寸
为什么选择locust
locust脚本可以通过权重设置,同时进行两个接口的测试(同时测试这样可以排除服务器压力等因素干扰) locust的报告可以同时显示两个接口的分析结果(不需要额外进行数据的加工、展示) locust支持的协议更好,可以直接使用内部写好的Class(脚本更便捷)
locust脚本
from locust import HttpUser, task, between
import os
class OCRUser(HttpUser):
wait_time = between(1, 3)
# 定义测试文件集及对应权重 [文件路径, 权重]
test_files = [
("./sample/1.jpg", 1), # 1启用的测试用例
("./sample/1.heif", 1),
("./sample/2.jpg", 1),
("./sample/2.heif", 1),
("./sample/3.jpg", 1),
("./sample/3.heif", 1),
("./sample/4.jpg", 1),
("./sample/4.heif", 1),
("./sample/5.jpg", 1),
("./sample/5.heif", 1),
]
def on_start(self):
"""检查所有测试文件是否存在"""
for path, _ in self.test_files:
if not os.path.exists(path):
raise FileNotFoundError(f"测试文件 {path} 不存在")
@task
def perform_ocr_test(self):
"""参数化测试入口"""
for file_path, weight in self.test_files:
if weight <= 0:
continue
self._execute_ocr_test(file_path)
def _execute_ocr_test(self, file_path):
"""通用测试执行逻辑"""
file_size = os.path.getsize(file_path) // 1024
file_type = os.path.splitext(file_path)[1][1:].upper()
with self._post_file(file_path, file_type) as res:
self._validate_response(res, file_path)
def _post_file(self, file_path, file_type):
"""统一封装文件上传操作"""
task_name = f"{os.path.basename(file_path)}-{os.path.getsize(file_path)//1024}k-{file_type}"
return self.client.post(
"/recognize/document",
files={"image": (
os.path.basename(file_path),
open(file_path, "rb"),
self._get_mime_type(file_type)
)},
name=task_name,
catch_response=True
)
def _validate_response(self, response, file_path):
"""统一响应验证逻辑"""
if response.status_code != 200:
response.failure(f"[{file_path}] 状态码异常: {response.status_code}")
elif not response.json():
response.failure(f"[{file_path}] 无效的JSON响应")
def _get_mime_type(self, file_type):
"""MIME类型映射保持不变"""
file_type = file_type.lower()
return {
"jpg": "image/jpeg",
"heif": "image/heif"
}.get(file_type, "application/octet-stream")
设置负载情况
注意:因为是要对比两个接口的性能,所以我选择的是最佳性能基准
,选择单个用户的测试场景
可视化效果
测试结论
- 在常见的300~500kb的图片内,对应接口的HEIF格式比JPG格式耗时大约高200ms左右
- 随着尺寸变大,超过1000kb,HEIF的传输优势才变明显
- 后续可以选择更多的大尺寸进行测试,观察HEIF的优异表现是否一致
测试案例2:对新、旧接口进行压力测试,评估其可支持的RPS
选择业务常用尺寸的HEIF和JPG大小
locust脚本
复用上述脚本,分别开启 1.jpg和1.heif
("./sample/1.jpg", 1), # 1启用的测试用例
("./sample/1.heif", 1),
设置负载情况
可视化效果
测试结论
- 对应接口的最高RPS为2.4,即使用户数量增加。
- 在RPS为1.5时,耗时开始明显增加,即出现性能骤降的拐点
- 进一步验证:设置用户数量为2,RPS为2,耗时比较稳定。表明系统的最佳负载是PRS为2左右。