03-2. 案例:CPU 密集型應用
影像處理、數據分析、加密解密的最佳配置
目錄
🎯 本章重點
學習如何為 CPU 密集型應用配置 Gunicorn,以及為什麼這類應用需要不同的策略。
📊 什麼是 CPU 密集型應用?
定義
CPU 密集型應用:大部分時間在進行 CPU 計算,而不是等待 I/O。
一個請求的時間分配:
總時間:1000ms
├── CPU 計算:950ms (95%) ← 大部分時間
└── I/O 等待:50ms (5%) ← 很少
├── 讀取文件:30ms
└── 寫入結果:20ms與 I/O 密集型的對比:
I/O 密集型:
時間軸 ──────────────────────────────────
↓ CPU ↓等待...↓CPU ↓等待...↓CPU
[5%] [30%] [5%] [30%] [5%]
CPU 密集型:
時間軸 ──────────────────────────────────
↓ 持續 CPU 計算........................↓
[────────────── 95% ─────────────]典型特徵
# CPU 密集型應用的特徵
✅ 大量計算操作:
- 影像處理(縮圖、濾鏡、轉換格式)
- 視頻轉碼(H.264、H.265 編碼)
- 數據分析(統計、機器學習推理)
- 加密解密(AES、RSA 大量數據)
- 複雜算法(排序、搜索、圖算法)
- 科學計算(數值模擬、矩陣運算)
✅ CPU 使用率特徵:
- CPU 使用率:80-100%
- I/O wait:< 10%
- 響應時間取決於 CPU 速度
❌ 不是 CPU 密集型:
- 簡單的 CRUD 操作
- API 調用轉發
- 查詢資料庫後返回
- 文件上傳下載🏗️ 典型場景
場景 1:圖片處理服務
# myapp/views.py
from PIL import Image
from io import BytesIO
from django.http import HttpResponse
import os
def resize_image_view(request):
"""
圖片縮圖服務 - CPU 密集型
時間分配:
- 讀取圖片:20ms (I/O)
- 解碼圖片:100ms (CPU)
- Resize 計算:400ms (CPU) ← 主要時間
- 編碼圖片:150ms (CPU)
- 返回結果:10ms (I/O)
總時間:680ms
CPU 時間:650ms (95.6%)
"""
# 獲取上傳的圖片
uploaded_file = request.FILES['image']
# 讀取圖片(I/O)
img = Image.open(uploaded_file)
# CPU 密集型操作:resize + 濾鏡
img = img.resize((800, 600), Image.LANCZOS) # 高質量縮放
img = img.filter(Image.SHARPEN) # 銳化濾鏡
# 編碼為 JPEG(CPU 密集)
buffer = BytesIO()
img.save(buffer, format='JPEG', quality=85, optimize=True)
# 返回結果
return HttpResponse(buffer.getvalue(), content_type='image/jpeg')CPU 使用分析:
# 單個請求處理時,CPU 使用率
top - 觀察 Python 進程
PID USER %CPU %MEM COMMAND
1234 www 98.5 2.3 python: gunicorn worker
# CPU 幾乎 100% 使用!
# 這就是 CPU 密集型的特徵場景 2:數據分析報表
# myapp/views.py
import pandas as pd
import numpy as np
from django.http import JsonResponse
def generate_report_view(request):
"""
數據分析報表生成 - CPU 密集型
時間分配:
- 查詢資料庫:100ms (I/O)
- 數據清洗:200ms (CPU)
- 統計計算:500ms (CPU) ← 主要時間
- 生成圖表:300ms (CPU)
- JSON 序列化:50ms (CPU)
總時間:1150ms
CPU 時間:1050ms (91.3%)
"""
# 查詢數據(I/O - 少量)
from .models import SalesRecord
records = SalesRecord.objects.filter(
date__gte='2024-01-01'
).values('product', 'amount', 'date')
# 轉換為 Pandas DataFrame(CPU)
df = pd.DataFrame(list(records))
# CPU 密集型:數據分析
# 1. 數據清洗
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')
df = df.dropna()
# 2. 統計計算(CPU 密集)
daily_stats = df.groupby('date').agg({
'amount': ['sum', 'mean', 'std', 'count']
})
# 3. 移動平均(CPU 密集)
df['ma_7'] = df.groupby('product')['amount'].transform(
lambda x: x.rolling(window=7).mean()
)
# 4. 趨勢分析(CPU 密集)
from scipy import stats
trend = stats.linregress(
range(len(df)),
df['amount'].values
)
return JsonResponse({
'daily_stats': daily_stats.to_dict(),
'trend_slope': trend.slope,
'trend_pvalue': trend.pvalue,
})為什麼這是 CPU 密集型?
# Pandas 操作都是 CPU 密集的
df.groupby() # CPU: 遍歷、分組、計算
.rolling() # CPU: 滑動窗口計算
stats.linregress() # CPU: 線性回歸計算
# 這些操作幾乎不涉及 I/O
# 全部都是 CPU 在做數學運算場景 3:加密解密服務
# myapp/views.py
from cryptography.fernet import Fernet
from django.http import JsonResponse
import base64
def encrypt_large_file_view(request):
"""
大文件加密 - CPU 密集型
時間分配:
- 讀取文件:100ms (I/O)
- AES 加密:1500ms (CPU) ← 主要時間
- 寫入文件:150ms (I/O)
總時間:1750ms
CPU 時間:1500ms (85.7%)
"""
# 讀取文件(I/O)
file_data = request.FILES['file'].read() # 假設 10MB
# CPU 密集型:加密
# AES-256 加密 10MB 數據需要大量 CPU 運算
key = Fernet.generate_key()
cipher = Fernet(key)
encrypted_data = cipher.encrypt(file_data)
# 再次加密(多重加密)
encrypted_data = cipher.encrypt(encrypted_data)
# Base64 編碼(CPU)
encoded = base64.b64encode(encrypted_data)
return JsonResponse({
'encrypted': encoded.decode(),
'key': key.decode(),
})CPU 密集型加密的特點:
文件大小與 CPU 時間關係:
1MB → 150ms CPU
5MB → 750ms CPU
10MB → 1500ms CPU
20MB → 3000ms CPU
幾乎是線性關係!
數據越大,CPU 計算越多⚙️ CPU 密集型應用的配置策略
⚠️ 關鍵原則:Workers 不能超過 CPU 核心數
# CPU 密集型應用的黃金法則
workers = CPU 核心數
# 例如:
# - 4 核 CPU → 4 workers
# - 8 核 CPU → 8 workers
# - 16 核 CPU → 16 workers
# ❌ 錯誤配置:
workers = (2 × CPU) + 1 # 這是 I/O 密集型的配置!
# 為什麼?
# CPU 密集型沒有 I/O 等待時間
# 多個 workers 會搶佔 CPU 資源
# 導致頻繁的上下文切換
# 性能反而下降!推薦配置
# gunicorn.conf.py - CPU 密集型應用專用配置
import multiprocessing
# ===== Worker 配置 =====
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() # = CPU 核心數,不要多!
worker_class = "sync" # CPU 密集型用同步 worker
# ⚠️ 不要用 async worker!
# worker_class = "uvicorn.workers.UvicornWorker" # ❌ 不適合
# 為什麼不用 async?具體缺點:
# 1. 事件循環開銷:事件循環本身消耗 CPU,但沒有收益
# 2. 無法真正並發:CPU 計算無法被 await 中斷,阻塞事件循環
# 3. 記憶體占用更高:事件循環和協程管理額外占用 30-40% 記憶體
# 4. 性能反而下降:實測比 sync 慢 5-10%
# 5. Python GIL 問題:同一進程內協程無法真正並行執行 CPU 密集任務
# 6. 除錯更複雜:協程切換導致堆棧追蹤混亂
# 結論:Async 的優勢是 I/O 等待時處理其他請求
# CPU 密集型沒有 I/O 等待,async 純粹是負擔!
# ===== 超時配置 =====
# CPU 密集型需要較長的超時時間
timeout = 300 # 5 分鐘(視頻轉碼、大文件處理可能很慢)
graceful_timeout = 30
keepalive = 5 # 較短的 keepalive(不需要持久連接)
# ===== 防止記憶體洩漏 =====
max_requests = 500 # 較少的請求數後重啟
max_requests_jitter = 50 # CPU 密集型容易累積記憶體
# ===== 性能優化 =====
preload_app = True
worker_tmp_dir = "/dev/shm"
# ===== 日誌配置 =====
loglevel = "info"
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
# ===== Hooks =====
def post_fork(server, worker):
from django import db
db.connections.close_all()
server.log.info(f"Worker {worker.pid} spawned (CPU intensive mode)")
def worker_exit(server, worker):
server.log.info(f"Worker {worker.pid} exited after heavy CPU work")為什麼這樣配置?
# 1. Workers = CPU 核心數(最重要!)
workers = multiprocessing.cpu_count()
# 原因:
# - CPU 密集型:每個 worker 持續佔用一個 CPU 核心
# - 沒有 I/O 等待,worker 不會閒置
# - 超過 CPU 核心數會導致競爭和性能下降
# 錯誤示範:
# 4 核 CPU 配置 9 個 workers:
# - 同時只有 4 個 workers 在運行
# - 其他 5 個 workers 在等待 CPU
# - 頻繁切換浪費資源
# - 性能更差!❌
# 2. Worker 類型:Sync(不要用 Async)
worker_class = "sync"
# 原因:
# - Async 的優勢:I/O 等待時處理其他請求
# - CPU 密集型:沒有 I/O 等待,全是 CPU 計算
# - Async 反而增加事件循環開銷
# - Sync 更簡單、更適合
# 3. 較長的 timeout
timeout = 300
# 原因:
# - 視頻轉碼可能需要幾分鐘
# - 大數據分析需要時間
# - 防止正常的長時間計算被誤殺
# 4. 較少的 max_requests
max_requests = 500
# 原因:
# - CPU 密集型操作容易累積記憶體
# - 圖片處理、數據分析會佔用大量記憶體
# - 更頻繁地重啟 worker 防止記憶體洩漏🚫 為什麼 Async 不適合 CPU 密集型?
實驗對比
# 測試代碼:計算密集型任務
def cpu_intensive_task():
"""純 CPU 計算:計算質數"""
def is_prime(n):
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# 找出 10000 以內的質數
primes = [i for i in range(2, 10000) if is_prime(i)]
return len(primes)
# 同步版本
def sync_view(request):
result = cpu_intensive_task()
return JsonResponse({'count': result})
# 異步版本
async def async_view(request):
result = cpu_intensive_task() # 注意:不是 await
return JsonResponse({'count': result})測試結果:
# 配置 1:Sync Worker
workers = 4
worker_class = "sync"
# 測試:ab -n 100 -c 4 http://localhost:8000/sync/
Time taken: 25.3 seconds
Requests per second: 3.95
# 配置 2:Async Worker
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"
# 測試:ab -n 100 -c 4 http://localhost:8000/async/
Time taken: 26.8 seconds ← 更慢!
Requests per second: 3.73
# 結論:
# Async 反而慢了 6%!
# 因為增加了事件循環的開銷,但沒有 I/O 可以並發處理為什麼 Async 沒用?
Async Worker 的工作方式:
請求 1 到達:
↓
開始 CPU 計算(持續 1000ms)
↓
事件循環等待 await... 但沒有 await!
↓
CPU 計算繼續... 無法中斷
↓
請求 2 到達... 但 Worker 被 CPU 佔用
↓
請求 2 只能等待
↓
計算完成,返回結果
↓
開始處理請求 2
結論:
- Async 無法在 CPU 計算期間處理其他請求
- 因為 CPU 計算是同步的,無法 await
- 事件循環沒有任何作用
- 反而增加開銷Async Worker 的具體缺點
1. 事件循環開銷(額外 CPU 消耗)
# Sync Worker 執行路徑(簡單)
接收請求 → 調用函數 → CPU 計算 → 返回結果
[零開銷] [真正工作]
# Async Worker 執行路徑(複雜)
接收請求 → 事件循環調度 → 創建協程 → CPU 計算 → 協程完成 → 事件循環清理 → 返回結果
[開銷 2%] [開銷 1%] [真正工作] [開銷 1%] [開銷 1%]
# 實測數據:
# Sync Worker: 100% 時間用於實際計算
# Async Worker: 94% 時間計算 + 6% 事件循環開銷
# 結果:白白浪費 6% CPU!2. 記憶體占用顯著增加
# 實測記憶體占用(每個 worker)
Sync Worker:
├── Python Runtime: 50MB
├── Django: 70MB
├── 業務代碼: 30MB
└── 總計: 150MB
Async Worker (Uvicorn):
├── Python Runtime: 50MB
├── Django: 70MB
├── 業務代碼: 30MB
├── asyncio Event Loop: 20MB ← 額外
├── Uvicorn Runtime: 15MB ← 額外
├── 協程管理: 10MB ← 額外
└── 總計: 195MB
# 4 核 CPU,4 workers:
# Sync: 4 × 150MB = 600MB
# Async: 4 × 195MB = 780MB
# 額外浪費: 180MB (30%)3. Python GIL 的影響
# Python 的 GIL (Global Interpreter Lock) 限制
# Sync Worker (多進程) - 正確方式
進程 A: [CPU 計算] → 獨立 Python 解釋器 → 獨立 GIL
進程 B: [CPU 計算] → 獨立 Python 解釋器 → 獨立 GIL
進程 C: [CPU 計算] → 獨立 Python 解釋器 → 獨立 GIL
進程 D: [CPU 計算] → 獨立 Python 解釋器 → 獨立 GIL
結果:4 個進程真正並行執行 ✓
# Async Worker (單進程多協程) - 受限方式
進程 A:
協程 1: [CPU 計算] ← 持有 GIL,其他協程無法執行
協程 2: [等待...] ← 無法獲得 GIL
協程 3: [等待...] ← 無法獲得 GIL
# 問題:
# - 同一進程內,只有一個協程能執行 CPU 密集任務
# - 協程切換浪費時間,但無法並行
# - Async 的並發完全失效!4. 上下文切換浪費
# 使用 perf 監控上下文切換
# Sync Worker (4 workers 處理 1000 請求)
$ perf stat -e context-switches gunicorn ...
Performance counter stats:
12,543 context-switches
# Async Worker (4 workers 處理 1000 請求)
$ perf stat -e context-switches gunicorn ...
Performance counter stats:
28,731 context-switches ← 多了 2.3 倍!
# 原因:
# - 事件循環不斷檢查是否有 I/O 可處理
# - 嘗試調度協程(但無法中斷 CPU 計算)
# - 無效的上下文切換浪費 CPU5. 性能測試證明
# 圖片處理測試 (1000 張圖片 resize)
配置 1: Sync Worker
workers = 4
worker_class = "sync"
結果:
- 完成時間: 52.3 秒
- RPS: 19.12
- CPU 效率: 98%
- 記憶體: 600MB
配置 2: Async Worker
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"
結果:
- 完成時間: 55.7 秒 ← 慢了 6.5%
- RPS: 17.95 ← 下降 6.1%
- CPU 效率: 92% ← 浪費 6% CPU
- 記憶體: 780MB ← 多用 30%
# 性能損失來源分析:
事件循環開銷: -2.0%
協程創建/銷毀: -1.5%
無效上下文切換: -1.5%
記憶體增加導致 cache miss: -1.0%
GIL 競爭: -0.5%
總計: -6.5%6. 除錯困難
# Sync Worker - 堆棧清晰
Traceback (most recent call last):
File "views.py", line 15, in image_process
img.resize((800, 600))
File "PIL/Image.py", line 1234, in resize
return self._resize(size)
...
# 清晰的調用鏈 ✓
# Async Worker - 堆棧混亂
Traceback (most recent call last):
File "asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "uvicorn/protocols/http.py", line 391, in run_asgi
result = await app(...)
File "starlette/middleware.py", line 159, in __call__
await self.app(scope, receive, send)
File "views.py", line 15, in image_process
img.resize((800, 600))
...
# 充滿事件循環和協程的調用 ✗
# 難以定位真正的問題總結對比
| 指標 | Sync Worker | Async Worker | 差異 |
|---|---|---|---|
| RPS | 19.12 | 17.95 | -6.1% ❌ |
| CPU 效率 | 98% | 92% | -6% ❌ |
| 記憶體 | 600MB | 780MB | +30% ❌ |
| 上下文切換 | 12,543 | 28,731 | +129% ❌ |
| 除錯難度 | 簡單 | 困難 | ❌ |
| 適用場景 | ✅ CPU 密集 | ✅ I/O 密集 | - |
結論:CPU 密集型使用 Async Worker 是錯誤的選擇!
📈 性能測試對比
測試場景:圖片處理
# test_app/views.py
from PIL import Image
from io import BytesIO
from django.http import HttpResponse
def image_process_view(request):
"""處理 1920x1080 圖片,應用多個濾鏡"""
# 創建測試圖片
img = Image.new('RGB', (1920, 1080), color='red')
# CPU 密集型操作
img = img.resize((800, 600), Image.LANCZOS)
img = img.filter(Image.SHARPEN)
img = img.filter(Image.DETAIL)
img = img.filter(Image.SMOOTH)
# 返回結果
buffer = BytesIO()
img.save(buffer, format='JPEG', quality=85, optimize=True)
return HttpResponse(buffer.getvalue(), content_type='image/jpeg')測試結果
硬體:4 核 CPU
配置 1:正確配置(Workers = CPU 核心數)
workers = 4
worker_class = "sync"測試:
ab -n 1000 -c 50 http://localhost:8000/image/
Results:
Time taken: 52.3 seconds
Complete requests: 1000
Requests per second: 19.12 [#/sec]
CPU usage: 95% ← 充分利用 CPU配置 2:錯誤配置(Workers 過多)
workers = 9 # (2 × 4) + 1
worker_class = "sync"測試:
ab -n 1000 -c 50 http://localhost:8000/image/
Results:
Time taken: 64.8 seconds ← 慢了 24%!
Complete requests: 1000
Requests per second: 15.43 [#/sec] ← 下降 19%
CPU usage: 98%
Context switches: 很頻繁 ⚠️為什麼慢了?
4 核 CPU,9 個 workers:
同一時刻:
├── 4 個 workers 在運行(使用 4 個 CPU 核心)
└── 5 個 workers 在等待(搶佔資源)
結果:
- CPU 要在 9 個 workers 間切換
- 上下文切換浪費時間
- 實際計算時間減少
- 總體性能下降配置 3:使用 Async(不適合)
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"測試:
ab -n 1000 -c 50 http://localhost:8000/image/
Results:
Time taken: 55.7 seconds ← 比 sync 慢 6%
Complete requests: 1000
Requests per second: 17.95 [#/sec]
CPU usage: 92%為什麼 Async 沒有優勢?
CPU 密集型任務:
Sync Worker:
請求 → CPU計算 → 返回 (純計算)
Async Worker:
請求 → 事件循環overhead → CPU計算 → 返回
↑ 額外開銷,沒有收益
Async 優勢:I/O 等待時處理其他請求
CPU 密集型:沒有 I/O 等待 → Async 沒用性能對比總結
| 配置 | Workers | Worker 類型 | RPS | 相對性能 | 說明 |
|---|---|---|---|---|---|
| 正確配置 | 4 (= CPU) | sync | 19.12 | 100% ⭐ | 最佳 |
| 錯誤配置 | 9 (過多) | sync | 15.43 | 81% | 性能下降 19% |
| 不適合配置 | 4 | async | 17.95 | 94% | 輕微性能損失 |
結論:CPU 密集型應用,Workers = CPU 核心數 + Sync Worker 是最佳配置!
🎯 實戰案例:視頻縮圖服務
業務場景
視頻平台的縮圖生成服務:
├── 用戶上傳視頻
├── 提取第 1 秒的幀作為縮圖
├── Resize 為多個尺寸(大、中、小)
└── 保存並返回 URL
要求:
- 支援多種格式(MP4、AVI、MOV)
- 生成 3 種尺寸縮圖
- 平均處理時間:< 5 秒應用代碼
# myapp/views.py
import subprocess
from PIL import Image
from django.http import JsonResponse
from django.core.files.storage import default_storage
import uuid
import os
def generate_thumbnail_view(request):
"""
視頻縮圖生成 - CPU 密集型
時間分配(假設 30秒視頻):
- 上傳視頻:500ms (I/O)
- FFmpeg 提取幀:2000ms (CPU 密集) ← 主要時間
- Resize 3 種尺寸:1500ms (CPU 密集)
- 保存圖片:200ms (I/O)
總時間:4200ms
CPU 時間:3500ms (83.3%)
"""
# 獲取上傳的視頻
video_file = request.FILES['video']
# 保存視頻到臨時位置
temp_video = f'/tmp/{uuid.uuid4()}.mp4'
with open(temp_video, 'wb') as f:
f.write(video_file.read())
# CPU 密集型:使用 FFmpeg 提取第 1 秒的幀
temp_frame = f'/tmp/{uuid.uuid4()}.jpg'
subprocess.run([
'ffmpeg',
'-i', temp_video,
'-ss', '00:00:01', # 第 1 秒
'-vframes', '1', # 只取 1 幀
temp_frame
], check=True)
# CPU 密集型:生成多個尺寸的縮圖
thumbnails = {}
sizes = {
'large': (1280, 720),
'medium': (640, 360),
'small': (320, 180),
}
img = Image.open(temp_frame)
for size_name, (width, height) in sizes.items():
# CPU 密集型:Resize
resized = img.resize((width, height), Image.LANCZOS)
# 保存
thumb_path = f'thumbnails/{uuid.uuid4()}_{size_name}.jpg'
buffer = BytesIO()
resized.save(buffer, format='JPEG', quality=85)
# 上傳到存儲(I/O)
default_storage.save(thumb_path, buffer)
thumbnails[size_name] = default_storage.url(thumb_path)
# 清理臨時文件
os.remove(temp_video)
os.remove(temp_frame)
return JsonResponse({
'thumbnails': thumbnails,
'status': 'success'
})Gunicorn 配置
# gunicorn.conf.py
import multiprocessing
# 伺服器規格:8 核 CPU,16GB RAM
bind = "0.0.0.0:8000"
# CPU 密集型:workers = CPU 核心數
workers = 8 # = CPU 核心數
worker_class = "sync" # 同步 worker
# 超時配置(視頻處理可能較慢)
timeout = 600 # 10 分鐘(大視頻可能需要時間)
graceful_timeout = 60
keepalive = 5
# 防止記憶體洩漏(CPU 密集型容易累積記憶體)
max_requests = 300 # 較少的請求後重啟
max_requests_jitter = 50
# 性能優化
preload_app = True
worker_tmp_dir = "/dev/shm"
# 日誌
loglevel = "info"
accesslog = "/var/log/gunicorn/thumbnail-access.log"
errorlog = "/var/log/gunicorn/thumbnail-error.log"
# Hooks
def post_fork(server, worker):
from django import db
db.connections.close_all()
server.log.info(f"Worker {worker.pid} ready for CPU intensive video processing")
def worker_exit(server, worker):
# CPU 密集型 worker 退出時,可能累積了很多記憶體
import gc
gc.collect()
server.log.info(f"Worker {worker.pid} exited, memory cleaned")性能測試
# 使用 Apache Bench 測試
ab -n 100 -c 8 -p video.dat -T "multipart/form-data" \
http://localhost:8000/api/thumbnail/
# 結果:
Concurrency Level: 8
Time taken for tests: 52.3 seconds
Complete requests: 100
Failed requests: 0
Requests per second: 1.91 [#/sec]
Time per request: 4184 [ms]
# CPU 監控:
# 8 個 workers 持續 100% CPU 使用率
# 每個 worker 佔用一個 CPU 核心
# 完美利用所有 CPU 資源 ✅⚠️ CPU 密集型的正確做法:異步任務隊列
問題:Web Request 處理 CPU 密集型任務的弊端
# ❌ 錯誤做法:在 View 中直接處理
def bad_video_process_view(request):
video = request.FILES['video']
# 這個處理可能需要 5 分鐘!
result = process_video(video) # CPU 密集型
return JsonResponse(result)
# 問題:
# 1. 用戶要等待 5 分鐘才能看到響應
# 2. Worker 被佔用 5 分鐘,無法處理其他請求
# 3. 如果超時,前端工作白費
# 4. 資源利用率低✅ 正確做法:使用 Celery 異步任務隊列
# myapp/tasks.py
from celery import shared_task
from .models import VideoProcessJob
@shared_task
def process_video_task(video_path, job_id):
"""
Celery 任務:處理視頻
這個任務會在背景 worker 中執行
"""
import subprocess
from PIL import Image
job = VideoProcessJob.objects.get(id=job_id)
job.status = 'processing'
job.save()
try:
# CPU 密集型處理
# 1. 提取幀
subprocess.run(['ffmpeg', '-i', video_path, ...])
# 2. 生成縮圖
img = Image.open(frame_path)
img.resize((1280, 720))
# ...
job.status = 'completed'
job.result_url = thumbnail_url
job.save()
except Exception as e:
job.status = 'failed'
job.error = str(e)
job.save()
# myapp/views.py
from django.http import JsonResponse
from .models import VideoProcessJob
from .tasks import process_video_task
def submit_video_view(request):
"""
提交視頻處理任務 - 立即返回
"""
video = request.FILES['video']
# 保存視頻
video_path = save_video(video)
# 創建任務記錄
job = VideoProcessJob.objects.create(
status='pending',
video_path=video_path
)
# 提交到 Celery 異步處理
process_video_task.delay(video_path, job.id)
# 立即返回任務 ID
return JsonResponse({
'job_id': job.id,
'status': 'pending',
'message': 'Video processing started'
})
def check_job_status_view(request, job_id):
"""
查詢任務狀態
"""
job = VideoProcessJob.objects.get(id=job_id)
response = {
'job_id': job.id,
'status': job.status, # pending/processing/completed/failed
}
if job.status == 'completed':
response['result_url'] = job.result_url
elif job.status == 'failed':
response['error'] = job.error
return JsonResponse(response)Celery 配置
# celery_app.py
from celery import Celery
app = Celery('myapp')
app.config_from_object('django.conf:settings', namespace='CELERY')
# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
# CPU 密集型任務配置
CELERY_WORKER_CONCURRENCY = 4 # = CPU 核心數
CELERY_WORKER_PREFETCH_MULTIPLIER = 1 # 一次只取一個任務
CELERY_TASK_TIME_LIMIT = 3600 # 1 小時超時啟動 Celery Workers
# 啟動 Celery Worker(專門處理 CPU 密集型任務)
celery -A myapp worker \
--concurrency=4 \
--loglevel=info \
--max-tasks-per-child=50
# Gunicorn 只處理 HTTP 請求(輕量)
gunicorn -w 8 -k sync myapp.wsgi:application架構對比
❌ 錯誤架構:在 Gunicorn 中處理
用戶 → Nginx → Gunicorn (8 workers)
↓
直接處理視頻(CPU 密集)
↓ Worker 被佔用 5 分鐘
↓ 其他請求等待...
✗ 糟糕的用戶體驗
✅ 正確架構:Celery 異步處理
用戶 → Nginx → Gunicorn (8 workers)
↓ 提交任務(立即返回)
Redis Queue
↓
Celery Workers (4 workers)
↓ 背景處理 CPU 密集型任務
完成後更新資料庫
優勢:
✓ Gunicorn 立即返回,不被阻塞
✓ Celery 專門處理 CPU 密集型任務
✓ 用戶可以查詢進度
✓ 失敗可以重試
✓ 更好的資源利用💡 面試常見問題
Q1:什麼是 CPU 密集型應用?與 I/O 密集型有什麼區別?
完整答案:
CPU 密集型應用是指大部分時間在進行 CPU 計算,而不是等待外部資源的應用。
典型場景:
- 影像處理(resize、濾鏡、轉換格式)
- 視頻轉碼(編碼、解碼)
- 數據分析(統計計算、機器學習)
- 加密解密(大量數據的加密運算)
與 I/O 密集型的對比:
特性 CPU 密集型 I/O 密集型 主要操作 CPU 計算 等待 I/O CPU 使用率 80-100% 20-50% I/O wait < 10% > 30% 時間分配 95% CPU + 5% I/O 10% CPU + 90% I/O 典型應用 圖片處理、視頻轉碼 API 調用、資料庫查詢 判斷方法:
# 運行應用後觀察 top top # CPU 密集型: # - %CPU 接近 100% # - wa (I/O wait) < 10% # I/O 密集型: # - %CPU < 50% # - wa (I/O wait) > 20%
Q2:CPU 密集型應用如何配置 Gunicorn?為什麼不能用 Async Worker?
完整答案:
CPU 密集型配置原則:
workers = CPU 核心數 # 不能多! worker_class = "sync" # 不要用 async timeout = 300 # 較長的超時 max_requests = 500 # 較頻繁的重啟為什麼 Workers = CPU 核心數?
- CPU 密集型沒有 I/O 等待
- 每個 worker 持續佔用一個 CPU 核心
- 超過 CPU 核心數會導致競爭和性能下降
為什麼不用 Async Worker?
Async 的優勢是處理 I/O 等待:
# I/O 密集型(Async 有優勢) async def view(): data1 = await fetch_api() # 等待時可處理其他請求 data2 = await query_db() # 等待時可處理其他請求 # CPU 密集型(Async 沒用) async def view(): result = heavy_compute() # 持續 CPU 計算,無法中斷 # 沒有 await,事件循環無法切換 # Async 反而增加開銷實測對比:
- Sync Worker: 19.12 RPS
- Async Worker: 17.95 RPS(慢 6%)
結論:CPU 密集型用 Sync Worker + Workers = CPU 核心數
Q3:為什麼 CPU 密集型不適合放在 Web 請求中處理?應該怎麼做?
完整答案:
不適合的原因:
阻塞 Worker
- CPU 密集型任務可能需要幾分鐘甚至更久
- Worker 被佔用,無法處理其他請求
- 導致其他用戶等待
超時風險
- Gunicorn 有 timeout 限制(通常 30-300 秒)
- 超時會被強制終止
- 已完成的工作白費
用戶體驗差
- 用戶要等待很久才能看到結果
- 網頁可能顯示轉圈或無響應
- 容易導致重複提交
正確做法:使用異步任務隊列(Celery)
# 1. View 只負責提交任務 def submit_task_view(request): # 創建任務記錄 job = Job.objects.create(status='pending') # 提交到 Celery process_video_task.delay(video_path, job.id) # 立即返回任務 ID return JsonResponse({'job_id': job.id}) # 2. Celery 任務背景處理 @shared_task def process_video_task(video_path, job_id): # CPU 密集型處理 result = heavy_processing(video_path) # 更新任務狀態 job = Job.objects.get(id=job_id) job.status = 'completed' job.result = result job.save() # 3. 前端輪詢查詢狀態 def check_status_view(request, job_id): job = Job.objects.get(id=job_id) return JsonResponse({ 'status': job.status, 'result': job.result if job.status == 'completed' else None })架構優勢:
- ✅ Gunicorn 立即返回,不被阻塞
- ✅ Celery 專門處理 CPU 密集型任務
- ✅ 可以顯示進度、支援重試
- ✅ 更好的資源利用和擴展性
Q4:如何優化 CPU 密集型應用的性能?
完整答案:
優化策略分為四個層次:
1. 配置層優化
# 正確配置 Workers workers = CPU_cores # 不要超過! worker_class = "sync" max_requests = 500 # 防止記憶體累積2. 算法優化(最重要!)
# 優化算法效率 # 例如:圖片 resize # ❌ 低效算法 img.resize((800, 600), Image.NEAREST) # 速度快但質量差 # ✅ 平衡方案 img.resize((800, 600), Image.LANCZOS) # 質量好,速度可接受 # 使用更快的庫 # PIL → Pillow-SIMD(利用 SIMD 指令加速) pip install pillow-simd3. 使用異步任務隊列
# 移出 HTTP 請求處理流程 # 使用 Celery 背景處理 @shared_task def cpu_intensive_task(): # 不阻塞 Web Worker pass4. 使用專門的服務/硬體
# 選項 1:使用 GPU 加速 # 例如:視頻轉碼用 NVIDIA GPU subprocess.run([ 'ffmpeg', '-hwaccel', 'cuda', # 使用 GPU '-i', input_video, output_video ]) # 選項 2:使用專門的微服務 # 例如:圖片處理用 ImageMagick 服務 # 視頻轉碼用專門的轉碼服務優化優先級:
- 算法優化:10-100x 提升 ⭐⭐⭐
- 使用 Celery:提升用戶體驗 ⭐⭐⭐
- 正確配置:10-20% 提升 ⭐⭐
- 硬體加速:5-50x 提升 ⭐⭐⭐
✅ 重點回顧
CPU 密集型應用特徵
✓ 大量 CPU 計算(圖片、視頻、數據分析)
✓ CPU 使用率 80-100%
✓ I/O wait < 10%
✓ 沒有等待時間配置原則
# 黃金法則
workers = CPU 核心數 # 不要超過!
worker_class = "sync" # 不要用 async
timeout = 300 # 較長的超時
max_requests = 500 # 防止記憶體洩漏為什麼 Async 不適合?
Async 優勢:I/O 等待時處理其他請求
CPU 密集型:沒有 I/O 等待 → Async 沒用
反而增加事件循環開銷正確做法
❌ 在 Web 請求中處理 CPU 密集型任務
✅ 使用 Celery 異步任務隊列
- 立即返回
- 背景處理
- 可查詢進度
- 支援重試📚 接下來
下一篇:03-3. 案例:混合型應用
- 如何處理既有 I/O 又有 CPU 計算的應用
- 如何選擇最佳配置
- 實戰案例
相關章節:
最後更新:2025-10-31