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

Python3.8+写入文件到Linux挂载点时避免请求超时错误的方案问询

可行实现方案

核心思路是将文件写入逻辑和接口响应逻辑解耦,先返回生成的文件路径,再后台异步执行文件写入,完全避免请求等待写入完成导致的超时问题。

方案1:轻量异步实现(适合中小流量场景)

用Flask自带的after_this_request装饰器,实现响应返回后再执行文件写入操作,不需要额外引入中间件:

from flask import after_this_request

@migration_blueprint.route("/migration/upload", methods=["POST"])
def upload_migration_file():
    file_abs_path = ""
    try:
        file = request.files['files']
        logger.debug("The file recieved is '{}'".format(file))
        file_name = str(datetime.now().strftime("%H%M%s")) + file.filename 
        proxy_bin = db.find_one("bins", query={"bin_type":"proxy", "status":"active"})
        if not proxy_bin:
            raise Exception("Proxy Bin not found")
        base_proxy_path = "/mnt/share_{}/migration/".format(proxy_bin['_id'])
        if not os.path.exists(base_proxy_path):
            os.makedirs(base_proxy_path)
        file_abs_path = os.path.join(base_proxy_path, file_name)
        # 提前读取文件内容,避免请求上下文销毁后文件对象失效
        file_content = file.read()

        # 定义响应返回后执行的写入逻辑
        @after_this_request
        def save_file_after_response(response):
            try:
                with open(file_abs_path, 'wb') as f:
                    f.write(file_content)
                # 可额外写入状态标记到Redis/数据库,标记文件写入完成
            except Exception as e:
                logger.exception(f"File save failed: {str(e)}")
                # 异常处理:删除半写文件、记录错误日志等
            return response
    except Exception as ex:
        logger.exception("Error : {}".format(str(ex)))
        abort("500",{"message" : str(ex)})
    # 直接返回路径,不需要等待写入完成
    return {"path" : file_abs_path}

注意:该方案依赖服务进程稳定,若写入过程中服务进程重启,会出现写入中断无重试的问题,适合非核心、小文件为主的场景。

方案2:任务队列实现(适合生产/大文件/高并发场景)

引入Celery/RQ等分布式任务队列,将文件写入作为异步任务投递后直接返回路径,任务队列自带重试、失败回调能力,稳定性更高:

  1. 首先定义异步写入任务
# tasks.py
from celery import Celery
import os

celery = Celery('file_tasks', broker='redis://localhost:6379/0')

@celery.task
def save_uploaded_file(file_content, file_abs_path):
    try:
        with open(file_abs_path, 'wb') as f:
            f.write(file_content)
        # 更新文件状态为完成
        return True
    except Exception as e:
        # 写入失败可以自动重试
        raise save_uploaded_file.retry(exc=e, countdown=60, max_retries=3)
  1. 调整接口逻辑
from tasks import save_uploaded_file

@migration_blueprint.route("/migration/upload", methods=["POST"])
def upload_migration_file():
    file_abs_path = ""
    try:
        file = request.files['files']
        logger.debug("The file recieved is '{}'".format(file))
        file_name = str(datetime.now().strftime("%H%M%s")) + file.filename 
        proxy_bin = db.find_one("bins", query={"bin_type":"proxy", "status":"active"})
        if not proxy_bin:
            raise Exception("Proxy Bin not found")
        base_proxy_path = "/mnt/share_{}/migration/".format(proxy_bin['_id'])
        if not os.path.exists(base_proxy_path):
            os.makedirs(base_proxy_path)
        file_abs_path = os.path.join(base_proxy_path, file_name)
        # 读取文件内容投递异步任务
        file_content = file.read()
        save_uploaded_file.delay(file_content, file_abs_path)
        # 可选:写入Redis标记当前路径文件处于写入中状态
    except Exception as ex:
        logger.exception("Error : {}".format(str(ex)))
        abort("500",{"message" : str(ex)})
    return {"path" : file_abs_path}

额外优化建议

  • 新增文件状态查询接口,后续操作拿到路径后先查询文件是否写入完成,避免操作未写完的文件报错
  • 调整反向代理(如Nginx)的client_max_body_size配置,避免大文件上传请求在进入接口前被拦截
  • 超大文件场景可以搭配前端分块上传,接口首次请求就返回路径,后续分块追加写入,进一步降低单请求压力

内容的提问来源于stack exchange,提问作者SrTan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.12 07:33:03