如何避免Django视图因执行时间过长被重复调用?
问题分析
你遇到的重复调用问题,核心原因几乎都是请求超时触发的重试机制:
- 客户端(浏览器/前端)默认超时时间较短,当请求耗时超过阈值时会主动重试;
- WSGI服务器(如Gunicorn)的超时设置过短,会中断长耗时请求并重启处理;
- 同一
id的请求没有互斥机制,导致重复执行子进程。
以下是不用Celery等异步框架的解决方案:
解决方案1:给请求加跨进程互斥锁(基于ID)
利用文件锁实现跨进程的请求互斥,确保同一id的请求同一时间只有一个在执行,后续重复请求直接返回"处理中"状态。
修改后的视图代码:
import fcntl import os from django.http import JsonResponse from time import perf_counter import logging log = logging.getLogger(__name__) def process_images(request, id): lock_file_path = f"{settings.MEDIA_ROOT}/{id}/processing.lock" # 确保锁文件目录存在 os.makedirs(os.path.dirname(lock_file_path), exist_ok=True) with open(lock_file_path, 'w') as lock_file: try: # 尝试获取非阻塞排他锁 fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: # 已有进程在处理该ID,直接返回处理中状态 return JsonResponse({"status": "processing"}, status=202) log.debug("Processing images view...") start = perf_counter() try: # 执行远程处理脚本 result = subprocess.run( ["ssh", "<REMOTE SERVER>", "bash", "process_images.sh"], capture_output=True, text=True ) log.debug("Result stdout:\n" + result.stdout) # 读取处理结果 with open(f"{settings.MEDIA_ROOT}/{id}/image.json", "r") as f: response_data = json.load(f) time_took = perf_counter() - start log.debug(f"View time took {time_took} seconds") return JsonResponse(response_data, status=201) finally: # 释放锁并清理锁文件 fcntl.flock(lock_file, fcntl.LOCK_UN) os.unlink(lock_file_path)
解决方案2:调整服务器与客户端超时设置
如果是超时导致的重试,直接延长超时时间可以从根源解决:
- WSGI服务器调整:比如Gunicorn启动时添加
--timeout 300(设置超时为300秒,根据实际处理时间调整),避免服务器主动中断长耗时请求; - 客户端调整:如果是前端发起的请求,修改前端请求的超时参数(如Axios的
timeout配置),确保超时时间大于实际处理时长。
解决方案3:提前返回响应+客户端轮询
如果长连接超时无法避免,可以将请求拆分为"提交任务"和"查询结果"两步,避免长时间占用连接:
1. 修改视图代码
import fcntl import os import threading from django.http import JsonResponse import subprocess import logging log = logging.getLogger(__name__) def process_images(request, id): lock_file_path = f"{settings.MEDIA_ROOT}/{id}/processing.lock" os.makedirs(os.path.dirname(lock_file_path), exist_ok=True) with open(lock_file_path, 'w') as lock_file: try: fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) except BlockingIOError: return JsonResponse({"status": "processing", "id": id}, status=202) # 非阻塞启动远程处理进程 process = subprocess.Popen( ["ssh", "<REMOTE SERVER>", "bash", "process_images.sh"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) # 后台线程等待进程完成并清理锁 def wait_and_cleanup(): try: process.wait() log.debug("Remote processing completed") finally: os.unlink(lock_file_path) threading.Thread(target=wait_and_cleanup, daemon=True).start() return JsonResponse({"status": "accepted", "id": id}, status=202) # 新增查询结果视图 def get_process_result(request, id): json_path = f"{settings.MEDIA_ROOT}/{id}/image.json" lock_path = f"{settings.MEDIA_ROOT}/{id}/processing.lock" if os.path.exists(lock_path): return JsonResponse({"status": "processing"}, status=202) if os.path.exists(json_path): with open(json_path, 'r') as f: response_data = json.load(f) return JsonResponse(response_data, status=200) return JsonResponse({"status": "not_found"}, status=404)
2. 更新urls.py
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), path('<id>/process', views.process_images, name='process'), path('<id>/result', views.get_process_result, name='result'), # 新增结果查询路由 path('<id>/', views.model, name='model') ] app_name = "render"
客户端逻辑改为:先调用/process获取202响应,然后每隔一段时间轮询/result接口,直到拿到最终处理结果。
内容的提问来源于stack exchange,提问作者Andrei
相关产品推荐
相关产品推荐

