Django应用并行调用外部API问题求助(Apache mod_wsgi部署)
我之前处理过类似的中间件场景,你的问题核心是IO密集型任务的并发优化,而你遇到的线程没生效,大概率不是Django本身的单线程限制(Django请求处理是支持在视图内启动线程的),而是你的线程实现方式不对,或者mod_wsgi的配置没跟上。下面给你几个可行的解决方案,按优先级和复杂度排序:
1. 先搞定线程并发的正确实现(最快见效)
如果你的场景是需要实时返回组装后的响应,那么在视图内用线程池并发调用外部API是最直接的方案。之前没生效可能是你没有正确等待线程执行完成,或者用了不合适的线程方式。
举个实际的代码例子,用concurrent.futures.ThreadPoolExecutor来实现:
# views.py import requests from concurrent.futures import ThreadPoolExecutor from django.http import JsonResponse def fetch_external_api(url): # 封装外部API调用逻辑 try: response = requests.get(url, timeout=10) response.raise_for_status() return response.json() except Exception as e: return {"error": str(e)} def middleware_view(request): # 假设需要调用的3个外部API地址 api_urls = [ "https://api.example.com/service1", "https://api.example.com/service2", "https://api.example.com/service3" ] # 用线程池并发执行 with ThreadPoolExecutor(max_workers=3) as executor: # 提交所有API调用任务 futures = [executor.submit(fetch_external_api, url) for url in api_urls] # 等待所有任务完成并收集结果 results = [future.result() for future in futures] # 组装响应 assembled_response = { "service1_data": results[0], "service2_data": results[1], "service3_data": results[2], "status": "success" } return JsonResponse(assembled_response)
关键注意点:
- 确保你的
requests库是线程安全的(它本身是线程安全的,只要不共享同一个Session实例,或者每个线程用独立的Session) - 不要在视图里手动创建
threading.Thread然后直接启动却不等待,那样会导致视图提前返回,线程还没执行完就被销毁。
2. 调整mod_wsgi配置,确保支持多线程
如果上面的代码还是没生效,那可能是你的mod_wsgi配置限制了线程。默认的mod_wsgi嵌入式模式(embedded mode)线程支持有限,建议改用daemon模式,并明确设置线程数:
在你的Apache配置文件里修改:
# 定义daemon进程组,设置Python虚拟环境路径和线程数 WSGIDaemonProcess my_django_app python-home=/path/to/your/venv threads=15 # 指定应用使用这个daemon进程组 WSGIProcessGroup my_django_app # 挂载Django的WSGI脚本 WSGIScriptAlias / /path/to/your/django/project/wsgi.py process-group=my_django_app # 设置静态文件路径(如果需要) Alias /static/ /path/to/your/django/project/static/ <Directory /path/to/your/django/project/static> Require all granted </Directory> <Directory /path/to/your/django/project> <Files wsgi.py> Require all granted </Files> </Directory>
这里threads=15表示每个daemon进程可以处理15个并发请求,同时视图内部的线程也能正常运行。
3. 改用Django异步视图(更高效的IO并发)
从Django 3.1开始支持异步视图,对于IO密集型任务,异步比线程更高效,因为没有线程切换的开销。不过mod_wsgi对异步视图的支持并不好,所以需要改用ASGI服务器(比如Uvicorn或Daphne),然后用Apache做反向代理。
步骤:
- 安装依赖:
pip install aiohttp uvicorn
- 写异步视图:
# views.py import aiohttp import asyncio from django.http import JsonResponse from django.views import View from django.utils.decorators import classonlymethod async def fetch_external_api(session, url): try: async with session.get(url, timeout=10) as response: response.raise_for_status() return await response.json() except Exception as e: return {"error": str(e)} class AsyncMiddlewareView(View): @classonlymethod def as_view(cls, **initkwargs): view = super().as_view(**initkwargs) view._is_coroutine = asyncio.coroutines._is_coroutine return view async def get(self, request, *args, **kwargs): api_urls = [ "https://api.example.com/service1", "https://api.example.com/service2", "https://api.example.com/service3" ] async with aiohttp.ClientSession() as session: # 并发执行所有API请求 tasks = [fetch_external_api(session, url) for url in api_urls] results = await asyncio.gather(*tasks) assembled_response = { "service1_data": results[0], "service2_data": results[1], "service3_data": results[2], "status": "success" } return JsonResponse(assembled_response)
- 修改Django的
asgi.py文件:
# asgi.py import os from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') application = get_asgi_application()
- 用Uvicorn启动ASGI服务:
uvicorn your_project.asgi:application --host 127.0.0.1 --port 8000
- 配置Apache反向代理:
ProxyPass / http://127.0.0.1:8000/ ProxyPassReverse / http://127.0.0.1:8000/
这种方式比线程更高效,适合高并发场景。
4. 引入任务队列(适合非实时场景)
如果你的业务不需要实时返回响应,可以把API调用和组装逻辑放到异步任务队列里,比如用Celery配合Redis/RabbitMQ。这样用户请求过来后,直接返回一个任务ID,后台任务完成后再通知请求方,或者让请求方轮询结果。
简要步骤:
- 安装Celery和Redis:
pip install celery redis
- 配置Celery:
# celery.py import os from celery import Celery os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') app = Celery('your_project') app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks()
- 定义任务:
# tasks.py import requests from celery import shared_task @shared_task def fetch_and_assemble_data(api_urls): results = [] for url in api_urls: try: response = requests.get(url, timeout=10) response.raise_for_status() results.append(response.json()) except Exception as e: results.append({"error": str(e)}) # 组装数据,这里可以保存到数据库或者发送通知 assembled = {"data": results, "status": "done"} # 比如保存到模型或者调用回调API通知请求系统 return assembled
- 视图里调用任务:
# views.py from django.http import JsonResponse from .tasks import fetch_and_assemble_data def middleware_view(request): api_urls = [ "https://api.example.com/service1", "https://api.example.com/service2", "https://api.example.com/service3" ] task = fetch_and_assemble_data.delay(api_urls) return JsonResponse({"task_id": task.id, "status": "pending"})
这种方式可以彻底解放Django请求线程,适合对响应时间要求不高的场景。
总结一下:
- 如果需要实时响应,优先用线程池+正确的mod_wsgi配置,或者异步视图+ASGI服务器
- 如果不需要实时响应,用Celery任务队列
内容的提问来源于stack exchange,提问作者Manoj Purohit

