You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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)

待解决两个疑问:

  1. 如何加载测试图片目录形成图片列表,实现每次发起请求时随机选取一张图片上传?
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:30:45