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 = 2000

Gthread 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 MBGevent 稍大
總需求 = 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

限制因素

  1. CPU 限制

    • 不要超過合理範圍
    • CPU 密集:= CPU 核心數
    • I/O 密集:≤ 2 × CPU 核心數
  2. 記憶體限制

    • max_workers = 可用記憶體 / 單個 Worker 記憶體
    • 預留系統記憶體(至少 1-2GB)
  3. 並發需求

    • 計算實際並發數
    • 選擇合適的 Worker 類型

調優步驟

  1. 根據公式計算初始值
  2. 壓力測試驗證
  3. 監控關鍵指標
  4. 分析瓶頸
  5. 調整配置
  6. 重複測試直到最優

📚 接下來

現在你知道如何計算 Workers 數量了!下一篇我們會學習其他重要的配置參數:

02-2. 基礎配置參數

  • timeout(超時時間)
  • keepalive(保持連接)
  • max_requests(防止記憶體洩漏)
  • backlog(連接隊列)
  • 其他重要參數

🤓 小測驗

  1. 8 核 CPU,使用 Sync Worker(混合型),應該設定幾個 workers?

  2. 4GB 記憶體,每個 Worker 佔 200MB,預留 1GB 系統,最多幾個 workers?

  3. 每秒 100 請求,平均響應 500ms,需要多少並發?

  4. CPU 密集型應用,4 核 CPU,應該設定幾個 Sync Workers?


上一篇: 01-8. 現代方案:Gunicorn + Uvicorn Workers 🆕 下一篇: 02-2. 基礎配置參數


最後更新:2025-10-30

0%