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等分布式任务队列,将文件写入作为异步任务投递后直接返回路径,任务队列自带重试、失败回调能力,稳定性更高:
- 首先定义异步写入任务
# 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)
- 调整接口逻辑
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
相关产品推荐
相关产品推荐

