Django中阻塞等待Celery任务完成会导致多用户访问时站点卡顿吗?求优化方案
Django中阻塞等待Celery任务完成会导致多用户访问时站点卡顿吗?求优化方案
肯定会卡顿!你现在的写法完全浪费了Celery的异步优势——用task.wait()会让处理当前请求的Django worker进程彻底阻塞,直到Celery任务执行完毕。如果同时有多个用户上传文件,所有可用的Django worker都会被这些阻塞的请求占满,新的请求只能排队等待,站点自然就会变慢甚至无响应。
你的代码核心问题
你当前的流程是:用户上传文件 → Django启动Celery任务 → Django原地等待任务完成 → 返回结果。这本质上还是同步处理,Celery只是帮你把任务移到了另一个进程,但Django这边并没有释放资源去处理其他请求,完全没发挥异步架构的作用。
优化思路:改成异步非阻塞的「上传-轮询-下载」流程
核心就是让Django在启动Celery任务后立即返回响应,把等待的逻辑交给前端,后台任务完成后再提供下载入口。具体步骤如下:
1. 修改上传视图,返回任务ID
不再等待任务完成,启动任务后直接返回任务ID给前端,让用户知道任务正在处理。
修改后的views.py上传部分:
@csrf_protect def main_page(request: HttpRequest): if request.method == "GET": form = FileUploadForm() # 原GET请求代码有误,无需传POST/FILES参数 context = {"form": form} return render(request, 'main/main_page.html', context) if request.method == 'POST' and request.FILES.get('file'): form = FileUploadForm(request.POST, request.FILES) if form.is_valid(): file = request.FILES['file'] size_limit = 2 * 1024 * 1024 # 限制2MB # 保存临时PDF文件 with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file: if file.size > size_limit: for chunk in file.chunks(): temp_file.write(chunk) else: temp_file.write(file.read()) temp_pdf_path = temp_file.name # 启动Celery任务,不等待结果 task = tasks.convert_pdf_to_docx.delay(temp_pdf_path) # 返回任务ID和原文件名,供前端后续使用 return JsonResponse({ "task_id": task.id, "original_filename": str(file).replace(".pdf", "") }) else: context = {"form": form, "submit_error": True} return render(request, 'main/main_page.html', context)
2. 新增任务状态查询视图
用来让前端定期查询任务是否完成,以及获取下载链接:
from celery.result import AsyncResult def check_task_status(request: HttpRequest): task_id = request.GET.get('task_id') if not task_id: return JsonResponse({"error": "缺少task_id参数"}, status=400) task = AsyncResult(task_id) if task.state == 'SUCCESS': return JsonResponse({ "state": task.state, "download_url": f"/download/{task_id}/" }) elif task.state == 'FAILURE': return JsonResponse({ "state": task.state, "error": str(task.info) }) else: # 任务仍在处理中,返回当前状态(如PENDING、STARTED) return JsonResponse({"state": task.state})
3. 新增下载视图
当任务完成后,提供文件下载,并在下载后清理临时文件:
def download_converted_file(request: HttpRequest, task_id): task = AsyncResult(task_id) if task.state != 'SUCCESS': return HttpResponse("任务未完成或执行失败", status=400) temp_docx_path = task.result original_filename = request.GET.get('original_filename', 'converted_file') try: with open(temp_docx_path, 'rb') as docx_file: file_data = docx_file.read() response = HttpResponse(file_data, content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document') response['Content-Disposition'] = f'attachment; filename={original_filename}.docx' response['Content-Length'] = len(file_data) return response finally: # 下载完成后清理临时文件 if os.path.exists(temp_docx_path): os.remove(temp_docx_path) pdf_path = temp_docx_path.replace(".docx", ".pdf") if os.path.exists(pdf_path): os.remove(pdf_path)
4. 前端配合(简单示例)
在页面中添加JavaScript,实现上传后自动轮询任务状态:
<script> document.querySelector('form').addEventListener('submit', async function(e) { e.preventDefault(); const formData = new FormData(this); const response = await fetch(window.location.href, { method: 'POST', body: formData }); const result = await response.json(); if (result.task_id) { // 显示转换中提示 const statusBox = document.createElement('div'); statusBox.style.padding = '10px'; statusBox.textContent = '文件转换中,请稍候...'; document.body.appendChild(statusBox); // 每秒轮询一次任务状态 const pollTimer = setInterval(async () => { const statusRes = await fetch(`/check-task-status/?task_id=${result.task_id}`); const statusData = await statusRes.json(); if (statusData.state === 'SUCCESS') { clearInterval(pollTimer); statusBox.textContent = '转换完成!'; // 添加下载按钮 const downloadBtn = document.createElement('a'); downloadBtn.href = `${statusData.download_url}?original_filename=${result.original_filename}`; downloadBtn.textContent = '点击下载DOCX文件'; downloadBtn.style.display = 'block'; downloadBtn.style.marginTop = '10px'; document.body.appendChild(downloadBtn); } else if (statusData.state === 'FAILURE') { clearInterval(pollTimer); statusBox.textContent = `转换失败:${statusData.error}`; } }, 1000); } }); </script>
5. 额外优化建议
- 临时文件管理:不要依赖系统临时文件,建议用Django的
FileStorage(本地或云存储),避免服务器重启或临时文件自动清理导致文件丢失。 - Celery任务配置:给任务添加超时时间(如
@shared_task(time_limit=300)),避免任务无限挂起;添加重试机制处理转换失败的情况。 - Worker并发调整:你用了
gevent作为并发模型,适合IO密集型任务,可以根据服务器配置调整--concurrency参数,增加worker数量提升并发处理能力。
这样修改后,Django worker不会再被阻塞,能同时处理大量用户的上传请求,Celery在后台异步处理转换任务,用户体验也更好——不会看到页面一直加载,而是有明确的状态提示。
内容来源于stack exchange
相关产品推荐
相关产品推荐

