Django 4+ async views:什么时候真的有用,什么时候踩坑

起因

Django 4.0+ 支持 async views,文档里说"并发 IO 性能提升"。
直接把所有 view 改 async def?踩了几个坑:

  • ORM 默认是同步的,async view 里 User.objects.get() 会 block event loop
  • middleware 不全 async,会有性能 penalty
  • 不是所有场景都受益

理解什么时候用、什么时候别用,才有价值。

解决方案:分场景

场景 A:view 里发 N 个外部 HTTP 请求(async 真的快)

# 同步版(用 requests)
def aggregate_view(request):
    weather = requests.get('https://api.weather/...').json()
    news = requests.get('https://api.news/...').json()
    stocks = requests.get('https://api.stocks/...').json()
    return JsonResponse({'weather': weather, 'news': news, 'stocks': stocks})

3 个串行 IO,每个 300ms → 总 900ms。

# async 版
import httpx

async def aggregate_view(request):
    async with httpx.AsyncClient() as client:
        weather, news, stocks = await asyncio.gather(
            client.get('https://api.weather/...'),
            client.get('https://api.news/...'),
            client.get('https://api.stocks/...'),
        )
    return JsonResponse({
        'weather': weather.json(),
        'news': news.json(),
        'stocks': stocks.json(),
    })

3 个并行,总 300ms(max)。3x 加速。

场景 B:view 主要查 ORM(async 没用)

async def post_list(request):
    posts = Post.objects.filter(visibility='public')[:20]   # ❌
    return ...

Django ORM 默认同步。在 async view 里调用同步 ORM → 内部跑
sync_to_async 用线程池执行 → 比纯同步 view 还慢一点(多一次
context switch)。

Django 4.1+ 加了 async ORM:

async def post_list(request):
    posts = [p async for p in Post.objects.filter(visibility='public')[:20]]
    # 或:
    posts = await Post.objects.filter(...).a_in_bulk([1, 2, 3])
    post = await Post.objects.aget(pk=1)
    await post.adelete()
    return ...

a* 系列方法是 async 版。但要点:

  • 这不让你"并发查多条"——还是单连接顺序查
  • 唯一收益:不 block event loop(同进程能服务其它 async 请求)
  • 高 QPS + 复杂查询:Django ORM 仍是性能瓶颈(不是 async 能解的)

场景 C:streaming response(async 是必须的)

async def chat_stream(request):
    async def generator():
        async for chunk in llm.stream(prompt):
            yield f'data: {json.dumps(chunk)}\n\n'

    return StreamingHttpResponse(
        generator(),
        content_type='text/event-stream',
    )

LLM streaming / 长 polling / SSE → 必须 async 才能正常工作。
同步 view 在 stream 完成前 block 一个 worker。

场景 D:mixed sync + async

需要在 async view 里调同步代码(如某个老 lib):

from asgiref.sync import sync_to_async

@sync_to_async
def heavy_sync_work(x):
    return cpu_bound_calc(x)

async def view(request):
    result = await heavy_sync_work(42)
    return JsonResponse({'result': result})

sync_to_async 把同步函数包成 awaitable,在线程池跑。
反向 async_to_sync 让 sync 调 async。

启动方式

async views 需要 ASGI server(不是 WSGI):

# 之前:gunicorn (WSGI)
gunicorn myapp.wsgi

# 现在:uvicorn (ASGI)
uvicorn myapp.asgi:application --workers 4

# 或 gunicorn + uvicorn worker
gunicorn -k uvicorn.workers.UvicornWorker -w 4 myapp.asgi:application

myapp/asgi.py 默认 Django 已生成。

WSGI server 跑 async view 也能跑(Django 自动用 sync_to_async 适配),
但性能不如 ASGI。

性能测试(实际数据)

我对一个 endpoint 测试:3 个外部 API + 1 个 DB 查询。

wrk -t8 -c100 -d30s RPS P95 latency
同步 gunicorn(4 worker) 35 2.9s
async uvicorn(4 worker) 320 380ms

外部 IO 密集场景 async 收益巨大。

但纯 DB 查询 endpoint:

RPS P95
同步 gunicorn 1200 80ms
async uvicorn 1100 90ms

async 反而略慢(额外的协程开销 + ORM 仍同步)。

什么场景该用 async

✅ 适合:

  • 外部 API / webhook 调用密集
  • SSE / streaming response
  • WebSocket (Django Channels)
  • 长 polling
  • AI / LLM 调用

❌ 不适合:

  • 纯 CRUD(ORM 同步主导)
  • CPU 密集(GIL,async 没用)
  • 老 lib 没 async 版本

实战:把现有 view 改 async 的流程

  1. 评估:这 view 主要 IO 类型是什么?外部 HTTP > DB 查询 > 其它 →
    值得改
  2. 安装 ASGI server (uvicorn)
  3. 把 view 函数 defasync def
  4. requestshttpx / aiohttp
  5. 把 ORM 调用换 aget / acreate / async iter(如果用得到)
  6. middleware:检查是否 async-aware,老 middleware 加 async_capable = True
  7. 测试 + 性能 benchmark 对比

Channels(WebSocket)

如果要 WebSocket / SSE 大量并发,Django Channels 是标准:

pip install channels channels-redis
# routing.py
from channels.routing import ProtocolTypeRouter, URLRouter
from chat.consumers import ChatConsumer

application = ProtocolTypeRouter({
    'websocket': URLRouter([
        path('ws/chat/', ChatConsumer.as_asgi()),
    ]),
})
# consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()

    async def receive(self, text_data):
        await self.send(text_data=f'echo: {text_data}')

Channels 让 Django 处理 WebSocket / SSE / HTTP / Channel layer
(跨 worker 消息)全面 async。

效果

我们一个 API gateway 类服务(聚合多个内部 service 数据)改 async:

  • P95 延迟从 1.2s → 230ms
  • 同硬件下 RPS 从 200 → 1500
  • worker 数 from 16 减到 4(每个 worker 并发服务)
  • 内存占用减半

而我们的 CRUD 类 admin backend 改了一半发现没什么用,回滚保持同步。

踩过的坑

  1. 在 async view 里调同步 view function:直接调会 block。
    await sync_to_async(other_view)(request)

  2. middleware 不 async:所有 middleware 必须 async-compatible,
    否则 Django 退化到 sync 模式。第三方 middleware 检查文档支持
    async。

  3. DB connection pool:async views 处理并发更高 → 同时打开的 DB
    connection 多 → DB 连接耗尽。配 PgBouncer 在前面。

  4. @login_required 等装饰器:检查是否 async-compatible。
    Django 4.1+ 内置装饰器都改了;第三方需要确认。

  5. 测试AsyncClient 替代 Client
    python async def test_view(): client = AsyncClient() response = await client.get('/api/...')

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。