起因
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 的流程
- 评估:这 view 主要 IO 类型是什么?外部 HTTP > DB 查询 > 其它 →
值得改 - 安装 ASGI server (uvicorn)
- 把 view 函数
def→async def - 把
requests换httpx/aiohttp - 把 ORM 调用换
aget/acreate/ async iter(如果用得到) - middleware:检查是否 async-aware,老 middleware 加
async_capable = True - 测试 + 性能 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 改了一半发现没什么用,回滚保持同步。
踩过的坑
-
在 async view 里调同步 view function:直接调会 block。
要await sync_to_async(other_view)(request)。 -
middleware 不 async:所有 middleware 必须 async-compatible,
否则 Django 退化到 sync 模式。第三方 middleware 检查文档支持
async。 -
DB connection pool:async views 处理并发更高 → 同时打开的 DB
connection 多 → DB 连接耗尽。配 PgBouncer 在前面。 -
@login_required等装饰器:检查是否 async-compatible。
Django 4.1+ 内置装饰器都改了;第三方需要确认。 -
测试:
AsyncClient替代Client:
python async def test_view(): client = AsyncClient() response = await client.get('/api/...')
登录后参与评论。