如何使用Locust对图片上传类Flask应用进行压力测试
Flask目标检测应用Locust压测问题解答
问题背景
开发了一款具备目标检测功能的Flask应用,目前通过如下curl命令可正常获取预期返回结果:
curl -X POST -F images=@images/dog.jpeg http://localhost:8000/detections
计划使用Locust工具对该应用开展压力测试,初始编写的测试代码如下:
class UserBehavior(TaskSet): def _get_image_part(self, file_path, file_content_type='image/jpeg'): file_name = os.path.basename(file_path) file_content = open(file_path, 'rb') return file_name, file_content, file_content_type @task def detections(self): url = '/detections' files = { "images": self._get_image_part("images/dog.jpeg"), } response = self.client.post(url, files=files, verify=False) print(response.text) class MyLocust(HttpUser): tasks = [UserBehavior] wait_time = between(0.1, 0.2)
待解决两个疑问:
- 如何加载测试图片目录形成图片列表,实现每次发起请求时随机选取一张图片上传?
- 上述测试代码在低负载场景(2-3个并发用户、每秒请求数<5)下运行正常,但提升并发量、请求数升高后应用会抛出错误,最常见的报错为FileNotFoundError,该现象是否合理?
问题解答
1. 随机选取目录图片上传的实现
直接按如下逻辑修改代码即可:
- 测试启动阶段一次性遍历图片目录,过滤出符合格式的图片文件存为列表,所有虚拟用户共享该列表,避免每次请求都遍历目录产生额外开销
- 每次发起检测请求前,从预加载的图片列表中随机抽取一个文件路径
- 读取文件时使用上下文管理器自动关闭文件,避免文件句柄泄漏
修改后的完整参考代码:
import os import random from locust import HttpUser, TaskSet, task, between class UserBehavior(TaskSet): # 配置图片目录和允许的图片后缀 IMAGE_DIR = "images/" ALLOW_IMG_EXT = (".jpg", ".jpeg", ".png", ".bmp") # 类加载阶段就生成图片路径列表,全局复用 img_path_list = [ os.path.join(IMAGE_DIR, f) for f in os.listdir(IMAGE_DIR) if f.lower().endswith(ALLOW_IMG_EXT) and os.path.isfile(os.path.join(IMAGE_DIR, f)) ] def _get_image_part(self, file_path, file_content_type='image/jpeg'): file_name = os.path.basename(file_path) # 用with自动管理文件开闭,读完就释放句柄 with open(file_path, 'rb') as f: file_content = f.read() return file_name, file_content, file_content_type @task def detections(self): url = '/detections' # 每次请求随机选一张图 selected_img = random.choice(self.img_path_list) files = { "images": self._get_image_part(selected_img), } response = self.client.post(url, files=files, verify=False) print(response.text) class MyLocust(HttpUser): tasks = [UserBehavior] wait_time = between(0.1, 0.2)
2. 高并发下FileNotFoundError的原因说明
这个现象是合理的,问题几乎都出在最初写的测试代码上,和Flask应用本身逻辑无关:
初始版本的_get_image_part方法里用open打开文件后,从来没有执行关闭操作。低并发下请求量小,打开的文件句柄总数少,不会触发系统限制;并发量提升后,短时间内会产生大量未释放的文件句柄,很快就会触及进程的最大文件句柄数阈值,此时操作系统会拒绝新的文件打开请求,很多场景下会直接抛出FileNotFoundError的误报——不是文件真的不存在,是进程已经没有多余资源打开文件了。
如果按照上面的参考代码修改完文件读取逻辑后,Flask服务端依然报FileNotFoundError,再排查服务端逻辑:比如是否没配置合理的请求体大小限制,高并发下上传的临时文件被系统提前清理,或者文件存储目录的写入权限在高并发下出现异常。
内容的提问来源于stack exchange,提问作者helloworld
相关产品推荐
相关产品推荐

