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

Django应用并行调用外部API问题求助(Apache mod_wsgi部署)

解决Django+mod_wsgi下外部API并发调用的问题

我之前处理过类似的中间件场景,你的问题核心是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做反向代理。

步骤:

  1. 安装依赖:
pip install aiohttp uvicorn
  1. 写异步视图:
# 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)
  1. 修改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()
  1. 用Uvicorn启动ASGI服务:
uvicorn your_project.asgi:application --host 127.0.0.1 --port 8000
  1. 配置Apache反向代理:
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/

这种方式比线程更高效,适合高并发场景。

4. 引入任务队列(适合非实时场景)

如果你的业务不需要实时返回响应,可以把API调用和组装逻辑放到异步任务队列里,比如用Celery配合Redis/RabbitMQ。这样用户请求过来后,直接返回一个任务ID,后台任务完成后再通知请求方,或者让请求方轮询结果。

简要步骤:

  1. 安装Celery和Redis:
pip install celery redis
  1. 配置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()
  1. 定义任务:
# 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
  1. 视图里调用任务:
# 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:09:15