Django 面試精華:ASGI vs WSGI

從同步到異步:理解 Python Web 服務器接口的演進

前言

當你部署 Django 應用時,可能會看到兩種不同的啟動方式:

# 傳統方式:WSGI
gunicorn myproject.wsgi:application

# 新方式:ASGI
uvicorn myproject.asgi:application

這兩者有什麼區別?什麼時候該用哪個?

想像一個實際場景:

# 傳統同步視圖(WSGI)
def get_user_data(request):
    user = User.objects.get(id=request.user.id)  # 數據庫查詢:100ms
    posts = Post.objects.filter(user=user)       # 數據庫查詢:150ms
    comments = Comment.objects.filter(user=user) # 數據庫查詢:120ms
    # 總耗時:370ms(串行執行)

    return JsonResponse({
        'user': user,
        'posts': posts,
        'comments': comments,
    })


# 異步視圖(ASGI)
async def get_user_data(request):
    # 並行執行三個查詢!
    user, posts, comments = await asyncio.gather(
        User.objects.aget(id=request.user.id),     # 100ms
        Post.objects.filter(user=user).all(),      # 150ms
        Comment.objects.filter(user=user).all(),   # 120ms
    )
    # 總耗時:150ms(並行執行,取最慢的那個)

    return JsonResponse({
        'user': user,
        'posts': posts,
        'comments': comments,
    })

異步能將響應時間從 370ms 降到 150ms! 但這需要 ASGI 的支持。

這篇文章將深入比較 WSGI 和 ASGI,幫助你做出正確的技術選型。


1. WSGI:同步時代的標準

1.1 什麼是 WSGI

WSGI (Web Server Gateway Interface) 是 Python Web 服務器和 Web 應用之間的標準接口,定義於 PEP 3333

定義(2003 年發布):

  • 一個簡單的同步調用約定
  • 服務器調用應用,應用返回響應
  • 一次請求,一個線程/進程

1.2 WSGI 工作原理

請求處理流程

客戶端請求
    ↓
Nginx (反向代理)
    ↓
Gunicorn (WSGI 服務器)
    ↓ 分配 Worker
Worker 1 (線程/進程) → Django 應用
Worker 2 (線程/進程) → Django 應用
Worker 3 (線程/進程) → Django 應用
    ↓
返回響應

WSGI 應用示例

# wsgi.py

def application(environ, start_response):
    """
    WSGI 應用的標準簽名

    Args:
        environ: 包含請求資訊的字典(HTTP headers, method, path 等)
        start_response: 用於返回狀態碼和響應頭的回調函數

    Returns:
        可迭代對象(通常是列表),包含響應體
    """
    # 解析請求
    path = environ['PATH_INFO']
    method = environ['REQUEST_METHOD']

    # 設置響應
    status = '200 OK'
    headers = [('Content-Type', 'text/plain')]
    start_response(status, headers)

    # 返回響應體
    return [b'Hello World!']


# Django 的 WSGI 應用
from django.core.wsgi import get_wsgi_application
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()

1.3 WSGI 的特點

特性說明
同步模型一個請求占用一個線程/進程,直到完成
阻塞 I/O數據庫查詢、文件讀寫會阻塞線程
成熟穩定20+ 年歷史,生態完善
簡單易懂概念簡單,調試方便
並發限制受限於 Worker 數量(通常 2-4 × CPU 核心數)

阻塞示例

def sync_view(request):
    # 1. 查詢數據庫(阻塞 100ms)
    user = User.objects.get(id=1)  # 線程等待...

    # 2. 調用外部 API(阻塞 500ms)
    response = requests.get('https://api.example.com/data')  # 線程等待...

    # 3. 讀取文件(阻塞 50ms)
    with open('data.txt') as f:  # 線程等待...
        data = f.read()

    # 總耗時:650ms
    # 在這 650ms 內,Worker 線程被占用,無法處理其他請求
    return HttpResponse('OK')

並發處理能力

假設:
- Gunicorn 配置:4 個 Worker
- 每個請求平均耗時:1 秒

理論 QPS:4 請求/秒
━━━━━━━━━━━━━━━━━━━━━━━━
Worker 1: [請求1-1秒][請求5-1秒][請求9-1秒]
Worker 2: [請求2-1秒][請求6-1秒][請求10-1秒]
Worker 3: [請求3-1秒][請求7-1秒][請求11-1秒]
Worker 4: [請求4-1秒][請求8-1秒][請求12-1秒]

第 5 個請求必須等待 Worker 空閒

2. ASGI:異步時代的標準

2.1 什麼是 ASGI

ASGI (Asynchronous Server Gateway Interface) 是 WSGI 的精神繼承者,為異步 Python Web 應用設計。

定義(2016 年發布,Django Channels 項目啟動):

  • 支持異步和同步應用
  • 支持長連接(WebSocket、Server-Sent Events)
  • 支持 HTTP/2
  • 一個 Worker 可以同時處理多個請求

2.2 ASGI 工作原理

請求處理流程

客戶端請求
    ↓
Nginx (反向代理)
    ↓
Uvicorn/Daphne (ASGI 服務器)
    ↓ 事件循環
單個 Worker (事件循環)
    ├─ 協程 1 → Django 應用
    ├─ 協程 2 → Django 應用
    ├─ 協程 3 → Django 應用
    └─ 協程 N → Django 應用
    ↓
返回響應

ASGI 應用示例

# asgi.py

async def application(scope, receive, send):
    """
    ASGI 應用的標準簽名

    Args:
        scope: 包含連接資訊的字典(類似 WSGI 的 environ)
        receive: 異步可調用對象,用於接收事件(請求體)
        send: 異步可調用對象,用於發送事件(響應)
    """
    # 檢查連接類型
    if scope['type'] == 'http':
        # HTTP 請求
        await send({
            'type': 'http.response.start',
            'status': 200,
            'headers': [[b'content-type', b'text/plain']],
        })
        await send({
            'type': 'http.response.body',
            'body': b'Hello World!',
        })

    elif scope['type'] == 'websocket':
        # WebSocket 連接
        await send({'type': 'websocket.accept'})
        message = await receive()
        await send({
            'type': 'websocket.send',
            'text': 'Hello WebSocket!',
        })


# Django 的 ASGI 應用
from django.core.asgi import get_asgi_application
import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_asgi_application()

2.3 ASGI 的特點

特性說明
異步模型使用 async/await 語法
非阻塞 I/OI/O 操作不阻塞事件循環
高並發單個 Worker 可處理數千個並發連接
長連接支持WebSocket, Server-Sent Events, HTTP/2
向後兼容可以運行同步 WSGI 應用

非阻塞示例

async def async_view(request):
    # 1. 異步查詢數據庫(不阻塞,100ms)
    user = await User.objects.aget(id=1)  # 切換到其他協程

    # 2. 異步調用外部 API(不阻塞,500ms)
    async with aiohttp.ClientSession() as session:
        async with session.get('https://api.example.com/data') as response:
            data = await response.json()  # 切換到其他協程

    # 3. 異步讀取文件(不阻塞,50ms)
    async with aiofiles.open('data.txt') as f:
        content = await f.read()  # 切換到其他協程

    # 總耗時:650ms
    # 但在這 650ms 內,Worker 可以處理其他請求!
    return HttpResponse('OK')

並發處理能力

假設:
- Uvicorn 配置:2 個 Worker
- 每個請求平均耗時:1 秒(但 90% 是 I/O 等待)

理論 QPS:200+ 請求/秒(取決於 I/O 等待時間)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Worker 1 (事件循環):
  協程1: [請求1--等待I/O--][處理][完成]
  協程2:    [請求2--等待I/O--][處理][完成]
  協程3:       [請求3--等待I/O--][處理][完成]
  協程4:          [請求4--等待I/O--][處理][完成]
  ... (同時處理數百個請求)

Worker 2 (事件循環):
  協程1: [請求101--等待I/O--][處理][完成]
  協程2:    [請求102--等待I/O--][處理][完成]
  ... (同時處理數百個請求)

請求不需要等待 Worker 空閒!

3. WSGI vs ASGI 對比

3.1 核心差異

特性WSGIASGI
發布時間20032016
執行模型同步(阻塞)異步(非阻塞)
請求處理一個線程/進程一個請求一個事件循環多個請求
並發模型多線程/多進程協程(單線程)
長連接❌ 不支持✅ 支持 WebSocket, SSE
HTTP/2❌ 不支持✅ 支持
性能受限於 Worker 數量高並發(I/O 密集型)
複雜度簡單較複雜
生態非常成熟快速發展
Django 支持Django 1.0+Django 3.0+

3.2 性能對比

場景 1:I/O 密集型(數據庫查詢、API 調用)

# WSGI 同步視圖
def wsgi_view(request):
    user = User.objects.get(id=1)        # 100ms
    posts = Post.objects.filter(user=user).all()  # 200ms
    return JsonResponse({'user': user, 'posts': posts})
    # 總耗時:300ms
    # Worker 被占用:300ms


# ASGI 異步視圖
async def asgi_view(request):
    user, posts = await asyncio.gather(
        User.objects.aget(id=1),          # 100ms
        Post.objects.filter(user=user).all(),  # 200ms
    )
    return JsonResponse({'user': user, 'posts': posts})
    # 總耗時:200ms(並行執行)
    # Worker 實際占用時間:< 1ms(其餘時間可處理其他請求)

性能提升

  • 響應時間:減少 33%(300ms → 200ms)
  • 吞吐量:提升 10-50 倍(取決於 I/O 等待時間)

場景 2:CPU 密集型(數據處理、圖像處理)

# WSGI 同步視圖
def wsgi_cpu_intensive(request):
    result = complex_calculation()  # 純 CPU 計算,1 秒
    return JsonResponse({'result': result})
    # 總耗時:1 秒


# ASGI 異步視圖(並無優勢)
async def asgi_cpu_intensive(request):
    result = complex_calculation()  # 仍然阻塞事件循環,1 秒
    return JsonResponse({'result': result})
    # 總耗時:1 秒
    # ⚠️ 而且阻塞了事件循環,影響其他協程!

結論

  • I/O 密集型:ASGI 有巨大優勢
  • CPU 密集型:ASGI 無優勢,甚至更差(阻塞事件循環)

3.3 支持的協議

協議WSGIASGI
HTTP/1.0
HTTP/1.1
HTTP/2
WebSocket
Server-Sent Events (SSE)❌ (需 hack)

WebSocket 示例(ASGI)

# 只能在 ASGI 下運行
from channels.generic.websocket import AsyncWebsocketConsumer
import json

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

    async def receive(self, text_data):
        data = json.loads(text_data)
        message = data['message']

        # 發送消息給客戶端
        await self.send(text_data=json.dumps({
            'message': message
        }))

    async def disconnect(self, close_code):
        pass

4. Django 中的 WSGI 和 ASGI

4.1 Django 對兩者的支持

Django 版本WSGIASGI
Django < 3.0✅ 完整支持❌ 不支持
Django 3.0 - 3.1✅ 完整支持🟡 實驗性支持(僅 HTTP)
Django 3.2+✅ 完整支持✅ 穩定支持(HTTP + WebSocket)
Django 4.0+✅ 完整支持✅ 完整支持(包括異步 ORM)

4.2 項目結構對比

WSGI 項目

myproject/
├── myproject/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py          # WSGI 入口
├── myapp/
│   ├── views.py         # 同步視圖
│   └── models.py
└── manage.py

ASGI 項目

myproject/
├── myproject/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── wsgi.py          # WSGI 入口(向後兼容)
│   └── asgi.py          # ASGI 入口(新增)
├── myapp/
│   ├── views.py         # 可以是同步或異步視圖
│   ├── async_views.py   # 異步視圖
│   ├── models.py
│   └── consumers.py     # WebSocket 處理器(可選)
└── manage.py

4.3 啟動方式對比

WSGI 啟動

# 開發環境
python manage.py runserver  # Django 開發服務器(WSGI)

# 生產環境
gunicorn myproject.wsgi:application --workers 4

ASGI 啟動

# 開發環境
python manage.py runserver  # Django 3.0+ 自動檢測並使用 ASGI

# 生產環境
uvicorn myproject.asgi:application --workers 2
# 或
daphne myproject.asgi:application
# 或(推薦)
gunicorn myproject.asgi:application -k uvicorn.workers.UvicornWorker --workers 2

5. 常見 ASGI 服務器對比

服務器特點適用場景GitHub Stars
Uvicorn基於 uvloop, 速度極快生產環境首選⭐ 8.2k
DaphneDjango Channels 官方,穩定需要 WebSocket⭐ 2.3k
Hypercorn支持 HTTP/2, HTTP/3需要最新協議⭐ 1.1k
Gunicorn + Uvicorn Workers結合 Gunicorn 的進程管理生產環境(推薦)-

性能對比(請求/秒):

簡單 Hello World 基準測試:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Uvicorn           │████████████████│ 25,000 req/s
Daphne            │██████████      │ 15,000 req/s
Hypercorn         │███████████     │ 17,000 req/s
Gunicorn (WSGI)   │████            │  6,000 req/s

推薦配置

# 生產環境最佳實踐
gunicorn myproject.asgi:application \
    --bind 0.0.0.0:8000 \
    --workers 2 \
    --worker-class uvicorn.workers.UvicornWorker \
    --access-logfile - \
    --error-logfile -

6. 何時選擇 WSGI 或 ASGI

6.1 選擇 WSGI 的場景

推薦使用 WSGI

  1. 傳統 Django 應用(CRUD、表單處理)
  2. CPU 密集型應用(數據分析、圖像處理)
  3. 依賴大量同步庫(老舊的第三方包)
  4. 團隊不熟悉異步編程
  5. 追求穩定性(WSGI 生態更成熟)

示例場景

# 傳統 CMS 系統
def article_list(request):
    articles = Article.objects.filter(published=True)
    return render(request, 'articles.html', {'articles': articles})

# CPU 密集型任務
def generate_report(request):
    data = Report.objects.all()
    pdf = generate_pdf(data)  # CPU 密集型,500ms
    return FileResponse(pdf)

6.2 選擇 ASGI 的場景

推薦使用 ASGI

  1. I/O 密集型應用(大量 API 調用、數據庫查詢)
  2. 需要 WebSocket(實時聊天、推送通知)
  3. 需要 HTTP/2
  4. 微服務架構(大量服務間調用)
  5. 高並發場景(每秒數千請求)

示例場景

# 聚合多個微服務數據
async def dashboard(request):
    user, orders, notifications, analytics = await asyncio.gather(
        fetch_user_service(request.user.id),
        fetch_order_service(request.user.id),
        fetch_notification_service(request.user.id),
        fetch_analytics_service(request.user.id),
    )
    # 4 個服務調用並行執行,總耗時取最慢的那個

    return JsonResponse({
        'user': user,
        'orders': orders,
        'notifications': notifications,
        'analytics': analytics,
    })


# WebSocket 聊天
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.channel_layer.group_add("chat", self.channel_name)
        await self.accept()

    async def receive(self, text_data):
        await self.channel_layer.group_send("chat", {
            'type': 'chat_message',
            'message': text_data
        })

6.3 決策流程圖

開始
  ↓
需要 WebSocket 或 HTTP/2?
  ├─ Yes → 選擇 ASGI ✅
  └─ No → 繼續
           ↓
應用類型?
  ├─ I/O 密集型(API 調用、數據庫查詢多)
  │  ↓
  │  團隊熟悉異步編程?
  │  ├─ Yes → 選擇 ASGI ✅(性能提升明顯)
  │  └─ No → 選擇 WSGI ✅(降低複雜度)
  │
  ├─ CPU 密集型(計算、圖像處理多)
  │  ↓
  │  選擇 WSGI ✅(ASGI 無優勢)
  │
  └─ 混合型
     ↓
     並發需求高(> 1000 QPS)?
     ├─ Yes → 選擇 ASGI ✅
     └─ No → 選擇 WSGI ✅(更穩定)

7. 混合部署策略

實際項目中,可以同時使用 WSGI 和 ASGI:

架構示例

Nginx (反向代理)
    ↓
    ├─ /api/*  → Uvicorn (ASGI) → 異步視圖
    │              └─ 處理 I/O 密集型 API
    │
    ├─ /ws/*   → Uvicorn (ASGI) → WebSocket
    │              └─ 實時功能
    │
    └─ /*      → Gunicorn (WSGI) → 同步視圖
                   └─ 傳統頁面渲染

Nginx 配置

upstream asgi_backend {
    server 127.0.0.1:8000;  # Uvicorn
}

upstream wsgi_backend {
    server 127.0.0.1:8001;  # Gunicorn
}

server {
    listen 80;

    # ASGI 路由
    location /api/ {
        proxy_pass http://asgi_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    location /ws/ {
        proxy_pass http://asgi_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # WSGI 路由
    location / {
        proxy_pass http://wsgi_backend;
    }
}

8. 面試常見問題

Q1: WSGI 和 ASGI 的主要區別是什麼?

答案

維度WSGIASGI
執行模型同步(阻塞)異步(非阻塞)
並發方式多線程/多進程協程(事件循環)
協議支持僅 HTTPHTTP, WebSocket, HTTP/2
性能I/O 密集型受限I/O 密集型優秀

關鍵點:ASGI 是 WSGI 的超集,向後兼容,但提供了異步和長連接支持。

Q2: 什麼時候應該從 WSGI 遷移到 ASGI?

答案

考慮遷移的信號:

  1. 需要 WebSocket:實時聊天、推送通知
  2. I/O 瓶頸:大量外部 API 調用
  3. 高並發需求:Worker 數量不夠用
  4. 響應時間長:大量 I/O 等待時間

不建議遷移的情況:

  1. CPU 密集型應用:ASGI 無優勢
  2. 依賴同步庫:遷移成本高
  3. 團隊經驗不足:增加複雜度

Q3: Django 可以同時支持 WSGI 和 ASGI 嗎?

答案

可以!Django 3.0+ 同時提供 wsgi.pyasgi.py

# 同一個 Django 項目
myproject/
├── wsgi.py   # WSGI 入口
└── asgi.py   # ASGI 入口

# 可以選擇不同的啟動方式
gunicorn myproject.wsgi:application  # WSGI
uvicorn myproject.asgi:application   # ASGI

而且同一個視圖可以同時在兩種模式下運行:

# 同步視圖在 WSGI 和 ASGI 下都能運行
def sync_view(request):
    return HttpResponse("OK")

# 異步視圖只能在 ASGI 下運行
async def async_view(request):
    return HttpResponse("OK")

Q4: Uvicorn 和 Daphne 該選哪個?

答案

特性UvicornDaphne
性能更快(基於 uvloop)較慢
穩定性穩定非常穩定
社區活躍Django Channels 官方
HTTP/2需要 Hypercorn✅ 原生支持
推薦場景生產環境首選需要 Channels 全功能

推薦

  • 一般場景:Uvicorn
  • 使用 Django Channels:Daphne
  • 需要 HTTP/2/3:Hypercorn

Q5: ASGI 一定比 WSGI 快嗎?

答案

不一定! 取決於應用類型:

ASGI 更快(I/O 密集型):

async def async_view(request):
    # 大量 I/O 操作
    data1 = await fetch_api1()  # 100ms
    data2 = await fetch_api2()  # 100ms
    data3 = await fetch_api3()  # 100ms
    # 並行執行,總耗時 100ms

WSGI 可能更快(CPU 密集型):

def sync_view(request):
    # CPU 密集型計算
    result = complex_calculation()  # 1 秒
    # WSGI: 1 秒
    # ASGI: 1 秒 + 事件循環開銷,而且阻塞其他協程

結論

  • I/O 密集型:ASGI 可快 10-50 倍
  • CPU 密集型:ASGI 可能更慢
  • 🟡 混合型:需要測試評估

9. 總結

9.1 核心要點

  1. WSGI(2003)

    • 同步、阻塞、成熟穩定
    • 適合傳統 Web 應用和 CPU 密集型任務
    • 並發受限於 Worker 數量
  2. ASGI(2016)

    • 異步、非阻塞、高並發
    • 適合 I/O 密集型和實時應用
    • 支持 WebSocket 和 HTTP/2
  3. 選擇標準

    • 需要 WebSocket?→ ASGI
    • I/O 密集型?→ ASGI
    • CPU 密集型?→ WSGI
    • 追求穩定?→ WSGI
  4. Django 支持

    • Django 3.0+ 同時支持兩者
    • 可以混合部署
    • 異步 ORM 需要 Django 4.1+

9.2 快速對比表

場景WSGIASGI推薦
傳統 CRUD 應用WSGI
API 密集型🟡ASGI
WebSocketASGI
CPU 密集型WSGI
高並發(> 1000 QPS)🟡ASGI
團隊經驗不足🟡WSGI

參考資料

  1. 官方文檔

  2. ASGI 服務器

  3. 深入閱讀


下一篇預告:12-2. Django Async Views - 深入理解 Django 異步視圖的實現與最佳實踐

0%