02-1. Workers 數量計算
⏱️ 閱讀時間: 10 分鐘
🎯 難度: ⭐⭐⭐ (重要!面試必考)
🎯 本篇重點
學會如何科學地計算和設定 Workers 數量,這是 Gunicorn 配置中最重要的參數。
📐 經典公式
最常見的公式
workers = (2 × CPU_cores) + 1例子:
# 4 核 CPU
workers = (2 × 4) + 1 = 9
# 8 核 CPU
workers = (2 × 8) + 1 = 17
# 2 核 CPU
workers = (2 × 2) + 1 = 5這個公式的由來
為什麼是 2 × CPU?
假設 4 核 CPU,workers = 9:
├── 4 個 workers:正在使用 CPU(計算)
├── 4 個 workers:正在等待 I/O(資料庫、網絡)
└── 1 個 worker:緩衝(應對突發流量)
當 Worker A 在等待 I/O 時:
→ CPU 空閒
→ Worker B 可以使用 CPU
→ CPU 永遠有事做 ✅視覺化:
時間軸:
0ms → Worker 1: 使用 CPU
→ Worker 2: 等待 I/O(DB 查詢)
10ms → Worker 1: 等待 I/O(API 呼叫)
→ Worker 2: 使用 CPU ← 剛好接手!
20ms → Worker 1: 使用 CPU
→ Worker 2: 等待 I/O
...持續輪替,CPU 不閒置為什麼要 +1?
workers = (2 × CPU) + 1
↑
這個 +1 的作用:
1. 緩衝突發流量
- 正常時:9 個 workers 夠用
- 突然來一波:第 9 個 worker 頂上
2. 應對 Worker 重啟
- Worker 達到 max_requests 重啟時
- 其他 workers 繼續服務
3. 容錯空間
- 某個 Worker 卡住
- 還有其他 workers 工作🎯 按 Worker 類型調整
Sync Worker
# CPU 密集型應用
workers = CPU_cores
# 例如:4 核 CPU
gunicorn app:application --workers 4
# 原因:
# - 每個 worker 佔用一個 CPU 核心
# - 沒有 I/O 等待,不需要額外 workers
# - 超過 CPU 數量會導致競爭,降低效能# 混合型應用(有一些 I/O)
workers = (2 × CPU_cores) + 1
# 例如:4 核 CPU
gunicorn app:application --workers 9
# 原因:
# - 有 I/O 等待時間
# - 額外的 workers 可以利用等待時間Gevent Worker(⚠️ 舊專案維護)
注意:Gevent 主要用於舊專案維護。新專案請使用 ASGI (Uvicorn) + asyncio。
# Gevent 配置(舊專案)
workers = (2 × CPU_cores) + 1
worker_connections = 1000
# 例如:4 核 CPU
gunicorn app:application \
--workers 9 \
--worker-class gevent \
--worker-connections 1000
# 總並發:9 × 1000 = 9000現代替代方案(新專案推薦):
# ASGI + Uvicorn(Django 3.0+)
pip install uvicorn[standard]
# 開發環境
uvicorn myproject.asgi:application --reload
# 生產環境
uvicorn myproject.asgi:application \
--workers 4 \
--host 0.0.0.0 \
--port 8000
# Workers 數量:
workers = CPU_cores # 例如:4 核 CPU = 4 workers
# 每個 worker 自動處理數千並發(asyncio 原生支持)Gevent 調整重點(舊專案):
# 低並發(< 1000)
workers = 4
worker_connections = 500
# 中並發(1000-5000)
workers = 4
worker_connections = 1000
# 高並發(> 5000)
workers = 8
worker_connections = 2000Gthread Worker
# 混合型應用
workers = (2 × CPU_cores) + 1
threads = 2-4
# 例如:4 核 CPU
gunicorn app:application \
--workers 9 \
--worker-class gthread \
--threads 4
# 總並發:9 × 4 = 36
# 原因:
# - 結合進程和線程的優勢
# - 每個進程內多個線程調整重點:
# 低並發(< 50)
workers = 5
threads = 2
# 總並發:10
# 中並發(50-200)
workers = 9
threads = 4
# 總並發:36
# 高並發(200-500)
workers = 9
threads = 8
# 總並發:72💾 記憶體限制考量
計算記憶體需求
# 單個 Worker 記憶體占用(估算)
base_memory = 50 MB # Python + 基礎庫
django_memory = 30-80 MB # Django 應用
data_memory = 20-100 MB # 運行時資料
單個 Worker ≈ 100-200 MB計算總記憶體:
總記憶體 = (單個 Worker 記憶體 × Workers 數量) + 系統預留
# 例子 1:4 核 CPU,8GB 記憶體
單個 Worker = 150 MB
Workers = 9
總需求 = 150 × 9 = 1350 MB = 1.35 GB
系統預留 = 2 GB
剩餘 = 8 - 1.35 - 2 = 4.65 GB ✅ 足夠
# 例子 2:4 核 CPU,2GB 記憶體
單個 Worker = 150 MB
Workers = 9
總需求 = 150 × 9 = 1350 MB = 1.35 GB
系統預留 = 500 MB
剩餘 = 2 - 1.35 - 0.5 = 0.15 GB ⚠️ 緊張
# 調整方案:
workers = 6 # 減少 workers
總需求 = 150 × 6 = 900 MB
剩餘 = 2 - 0.9 - 0.5 = 0.6 GB ✅ 勉強可以記憶體限制公式
max_workers = (可用記憶體 - 系統預留) / 單個 Worker 記憶體
# 例子:
可用記憶體 = 4 GB = 4096 MB
系統預留 = 1 GB = 1024 MB
單個 Worker = 150 MB
max_workers = (4096 - 1024) / 150
= 3072 / 150
= 20.48
≈ 20 workers
# 但要考慮 CPU:
CPU = 4 核
理論 workers = (2 × 4) + 1 = 9
# 最終選擇:min(20, 9) = 9 workers ✅監控記憶體使用
# 查看每個 Worker 的記憶體
ps aux | grep gunicorn
# 輸出範例:
# USER PID %CPU %MEM VSZ RSS COMMAND
# www 12345 0.5 3.2 250000 128000 gunicorn: master
# www 12346 2.1 4.5 280000 180000 gunicorn: worker [1]
# www 12347 1.8 4.3 275000 172000 gunicorn: worker [2]
# www 12348 2.3 4.7 285000 188000 gunicorn: worker [3]
# RSS (常駐記憶體) 才是真實使用量
# Worker 1: 180 MB
# Worker 2: 172 MB
# Worker 3: 188 MB
# 平均: 180 MB📊 實際案例計算
案例 1:小型 CMS 網站
需求:
- 伺服器:2 核 CPU,4GB 記憶體
- 應用:部落格系統
- 預估流量:每秒 10 個請求
- 平均響應時間:100ms
計算:
# 1. 並發需求
並發 = RPS × 響應時間
= 10 × 0.1
= 1 個請求
# 2. CPU 建議
workers = (2 × 2) + 1 = 5
# 3. 記憶體檢查
單個 Worker = 120 MB
總需求 = 120 × 5 = 600 MB
可用 = 4096 - 1024 = 3072 MB
剩餘 = 3072 - 600 = 2472 MB ✅
# 4. 最終配置
gunicorn myapp:application \
--workers 5 \
--worker-class sync \
--timeout 30案例 2:中型電商網站
需求:
- 伺服器:4 核 CPU,8GB 記憶體
- 應用:電商平台
- 預估流量:每秒 100 個請求
- 平均響應時間:500ms
計算:
# 1. 並發需求
並發 = RPS × 響應時間
= 100 × 0.5
= 50 個請求
# 2. CPU 建議
workers = (2 × 4) + 1 = 9
threads = 4
總並發 = 9 × 4 = 36 個
# 36 < 50,需要調整!
# 調整方案:
workers = 9
threads = 6
總並發 = 9 × 6 = 54 個 ✅
# 3. 記憶體檢查
單個 Worker = 150 MB
總需求 = 150 × 9 = 1350 MB
可用 = 8192 - 2048 = 6144 MB
剩餘 = 6144 - 1350 = 4794 MB ✅
# 4. 最終配置
gunicorn myapp:application \
--workers 9 \
--worker-class gthread \
--threads 6 \
--timeout 60案例 3:高並發 API 服務
需求:
- 伺服器:8 核 CPU,16GB 記憶體
- 應用:API 網關(呼叫多個外部服務)
- 預估流量:每秒 500 個請求
- 平均響應時間:2 秒(I/O 等待)
計算:
# 1. 並發需求
並發 = RPS × 響應時間
= 500 × 2
= 1000 個請求
# 2. Worker 類型選擇
# I/O 密集 + 高並發 → Gevent
# 3. CPU 建議
workers = (2 × 8) + 1 = 17
worker_connections = 1000
總並發 = 17 × 1000 = 17000 ✅ 遠超需求
# 可以減少 workers:
workers = 8
worker_connections = 1000
總並發 = 8 × 1000 = 8000 ✅ 足夠
# 4. 記憶體檢查
單個 Worker = 180 MB(Gevent 稍大)
總需求 = 180 × 8 = 1440 MB
可用 = 16384 - 4096 = 12288 MB
剩餘 = 12288 - 1440 = 10848 MB ✅
# 5. 最終配置
gunicorn myapp:application \
--workers 8 \
--worker-class gevent \
--worker-connections 1000 \
--timeout 120🧪 壓力測試與調優
使用 Apache Bench (ab)
# 測試工具
ab -n 1000 -c 50 http://localhost:8000/
# 參數說明:
# -n 1000:總共發送 1000 個請求
# -c 50:並發 50 個請求
# 輸出範例:
# Requests per second: 120 [#/sec]
# Time per request: 416.667 [ms]
# Failed requests: 0
# 分析:
# - 120 req/s:每秒處理 120 個請求
# - 417ms:平均響應時間
# - 0 失敗:配置良好 ✅使用 Locust
# locustfile.py
from locust import HttpUser, task, between
class WebsiteUser(HttpUser):
wait_time = between(1, 3)
@task
def index(self):
self.client.get("/")
@task(3)
def api(self):
self.client.get("/api/users/")
# 運行測試
# locust -f locustfile.py --host=http://localhost:8000
# 在網頁上設定:
# - 用戶數:100
# - 每秒增加:10
# - 觀察響應時間和失敗率調優流程
Step 1: 初始配置(基於公式)
├── workers = (2 × CPU) + 1
├── 啟動服務
└── 記錄基準數據
Step 2: 壓力測試
├── 使用 ab 或 locust
├── 模擬真實流量
└── 觀察指標:
├── RPS (每秒請求數)
├── 響應時間
├── 錯誤率
├── CPU 使用率
└── 記憶體使用率
Step 3: 分析瓶頸
├── CPU 100%?
│ └── 增加 workers(但不超過記憶體限制)
├── 記憶體不足?
│ └── 減少 workers
├── 響應時間長?
│ ├── I/O 等待多 → 改用 Gevent 或增加 threads
│ └── CPU 計算多 → 優化代碼或升級硬體
└── 錯誤率高?
└── workers 太多,系統過載 → 減少 workers
Step 4: 調整配置
├── 根據分析結果調整
├── 重新測試
└── 重複 Step 2-4 直到最優
Step 5: 生產環境驗證
├── 部署到生產環境
├── 監控真實流量
└── 根據實際情況微調調優案例
# 初始配置
gunicorn app:application --workers 9 --worker-class sync
# 測試結果:
# RPS: 50
# CPU: 40%
# 響應時間: 800ms ← 太慢!
# 分析:大量 I/O 等待(資料庫、API)
# 調整 1:改用 Gthread
gunicorn app:application --workers 9 --worker-class gthread --threads 4
# 測試結果:
# RPS: 120
# CPU: 60%
# 響應時間: 350ms ← 好多了!
# 分析:還有優化空間,試試 Gevent
# 調整 2:改用 Gevent
gunicorn app:application --workers 4 --worker-class gevent --worker-connections 1000
# 測試結果:
# RPS: 200
# CPU: 70%
# 響應時間: 200ms ← 完美!✅🎤 面試常見問題
Q1: Workers 數量如何計算?
完整答案:
Workers 數量的計算需要考慮三個因素:
1. CPU 核心數
- 基礎公式:workers = (2 × CPU_cores) + 1
- 例如 4 核:(2 × 4) + 1 = 9 workers
2. 應用類型
- CPU 密集型:workers = CPU_cores
- I/O 密集型:workers = (2 × CPU_cores) + 1
- 混合型:workers = (2 × CPU_cores) + 1
3. 記憶體限制
- max_workers = 可用記憶體 / 單個 Worker 記憶體
- 取 min(CPU 建議值, 記憶體限制值)
4. 實際驗證
- 通過壓力測試找到最優值
- 監控 CPU、記憶體、響應時間
- 根據實際負載調整
Q2: 為什麼是 (2 × CPU) + 1?
完整答案:
這個公式基於以下假設:
1. I/O 和 CPU 的平衡
- 假設應用 50% 時間在 CPU 計算
- 50% 時間在等待 I/O
- 當一半 workers 等待 I/O 時,另一半使用 CPU
- 所以需要 2 倍 CPU 核心數的 workers
2. +1 的作用
- 應對突發流量
- Worker 重啟時的緩衝
- 容錯空間
3. 不是絕對的
- CPU 密集型:workers = CPU_cores
- I/O 極度密集:可能需要更多 workers
- 具體還是要測試
Q3: 如果記憶體不夠怎麼辦?
完整答案:
記憶體不足時有幾個解決方案:
方案 1:減少 Workers
# 原本:9 workers × 150MB = 1350MB # 改為:6 workers × 150MB = 900MB gunicorn app:application --workers 6方案 2:優化應用記憶體占用
- 減少全局變量
- 優化查詢(避免 N+1)
- 使用快取減少記憶體
- 定期重啟 workers(max_requests)
方案 3:改用 Gevent
- Gevent workers 記憶體占用相對較低
- 可以用更少的 workers 達到更高並發
方案 4:升級硬體
- 增加記憶體
- 這是最直接的解決方案
Q4: 壓力測試時應該關注哪些指標?
完整答案:
壓力測試時需要關注五個核心指標:
1. RPS (Requests Per Second)
- 每秒處理的請求數
- 越高越好
- 代表系統吞吐量
2. 響應時間
- 平均響應時間
- P95、P99 響應時間(95%、99% 的請求)
- 越低越好
3. 錯誤率
- 失敗請求的百分比
- 應該接近 0%
- 超過 1% 表示系統過載
4. CPU 使用率
- 理想:60-80%
- < 50%:資源浪費
90%:接近極限
5. 記憶體使用率
- 理想:< 80%
90%:風險區域
- 觀察是否有記憶體洩漏
這五個指標要綜合判斷,找到最佳平衡點。
✅ 重點回顧
計算公式
基礎公式:
workers = (2 × CPU_cores) + 1按類型調整:
- Sync (CPU 密集):workers = CPU_cores
- Sync (混合型):workers = (2 × CPU) + 1
- Gevent:workers = (2 × CPU) + 1, connections = 1000
- Gthread:workers = (2 × CPU) + 1, threads = 2-8
限制因素
CPU 限制
- 不要超過合理範圍
- CPU 密集:= CPU 核心數
- I/O 密集:≤ 2 × CPU 核心數
記憶體限制
- max_workers = 可用記憶體 / 單個 Worker 記憶體
- 預留系統記憶體(至少 1-2GB)
並發需求
- 計算實際並發數
- 選擇合適的 Worker 類型
調優步驟
- 根據公式計算初始值
- 壓力測試驗證
- 監控關鍵指標
- 分析瓶頸
- 調整配置
- 重複測試直到最優
📚 接下來
現在你知道如何計算 Workers 數量了!下一篇我們會學習其他重要的配置參數:
- timeout(超時時間)
- keepalive(保持連接)
- max_requests(防止記憶體洩漏)
- backlog(連接隊列)
- 其他重要參數
🤓 小測驗
8 核 CPU,使用 Sync Worker(混合型),應該設定幾個 workers?
4GB 記憶體,每個 Worker 佔 200MB,預留 1GB 系統,最多幾個 workers?
每秒 100 請求,平均響應 500ms,需要多少並發?
CPU 密集型應用,4 核 CPU,應該設定幾個 Sync Workers?
上一篇: 01-8. 現代方案:Gunicorn + Uvicorn Workers 🆕 下一篇: 02-2. 基礎配置參數
最後更新:2025-10-30