02-2. 基礎配置參數

⏱️ 閱讀時間: 10 分鐘 🎯 難度: ⭐⭐ (簡單)


🎯 本篇重點

掌握 Gunicorn 最重要的基礎配置參數:timeout、keepalive、max_requests、backlog 等。


📋 核心配置參數總覽

最重要的 5 個參數

參數作用默認值推薦值
timeoutWorker 超時時間30 秒30-120 秒
keepaliveKeep-alive 超時2 秒2-10 秒
max_requestsWorker 重啟閾值0(不重啟)1000-2000
backlog等待連接隊列20482048-4096
worker_connectionsWorker 並發連接數10001000-10000

⏱️ timeout(超時時間)

什麼是 timeout?

Worker 處理單個請求的最大時間。超過這個時間,Master 會殺掉 Worker 並重啟。

工作原理

請求到達 Worker
  ↓
開始計時(timeout 開始)
  ↓
處理請求...
  ↓
├─ 30 秒內完成 → ✅ 正常返回
└─ 超過 30 秒 → ❌ Worker 被殺掉 → 💥 502 錯誤

配置方式

# gunicorn.conf.py

# 快速 API(資料庫查詢為主)
timeout = 30  # 30 秒

# 一般 Web 應用
timeout = 60  # 1 分鐘

# 長時間處理(報表生成、大文件處理)
timeout = 120  # 2 分鐘

# WebSocket / 長連接(ASGI)
timeout = 300  # 5 分鐘

# 極端情況(不推薦)
timeout = 0  # 無限等待(危險!)

命令行方式

gunicorn app:application --timeout 60

timeout 常見問題

問題 1:Worker timeout 頻繁發生

症狀:

[CRITICAL] WORKER TIMEOUT (pid:12345)
[INFO] Booting worker with pid: 12346

原因分析:

# ❌ 原因 1:同步阻塞代碼
def slow_view(request):
    import time
    time.sleep(40)  # 超過 timeout = 30
    return HttpResponse("Done")

# ❌ 原因 2:慢速資料庫查詢
def slow_db_view(request):
    # 複雜查詢,需要 60 秒
    users = User.objects.all()  # 100 萬筆資料
    for user in users:
        user.process()  # 很慢
    return HttpResponse("Done")

# ❌ 原因 3:外部 API 無響應
def api_view(request):
    import requests
    # 外部 API 掛了,一直等待
    response = requests.get('https://slow-api.com', timeout=None)
    return JsonResponse(response.json())

解決方案:

# ✅ 方案 1:增加 timeout
# gunicorn.conf.py
timeout = 120  # 給予更多時間

# ✅ 方案 2:優化代碼
def optimized_view(request):
    # 使用分頁
    users = User.objects.all()[:100]
    # 使用 select_related 減少查詢
    users = User.objects.select_related('profile').all()[:100]
    return render(request, 'users.html', {'users': users})

# ✅ 方案 3:異步處理(Celery)
from .tasks import process_users

def async_view(request):
    # 丟到背景任務處理
    task = process_users.delay()
    return JsonResponse({'task_id': task.id})

# ✅ 方案 4:設定 API 超時
def safe_api_view(request):
    import requests
    try:
        response = requests.get(
            'https://api.example.com',
            timeout=10  # 10 秒超時
        )
        return JsonResponse(response.json())
    except requests.Timeout:
        return JsonResponse({'error': 'API timeout'}, status=504)

問題 2:如何判斷合理的 timeout 值?

計算公式:

timeout = P95_響應時間 × 2 + 安全邊距

# 例子:
# P95 響應時間 = 15 秒(95% 的請求在 15 秒內完成)
# 計算:15 × 2 + 10 = 40 秒
timeout = 40

測試方法:

# 1. 使用 Django Debug Toolbar 查看請求時間
pip install django-debug-toolbar

# 2. 使用 New Relic / Datadog 等監控工具
# 查看 P95、P99 響應時間

# 3. 壓力測試
ab -n 1000 -c 50 http://localhost:8000/
# 查看最慢的請求時間

🔄 keepalive(保持連接)

什麼是 keepalive?

HTTP Keep-Alive 的等待時間。允許同一個 TCP 連接處理多個 HTTP 請求。

工作原理

沒有 Keep-Alive(每次都建立新連接):

請求 1:
客戶端 → 建立 TCP → 發送 HTTP → 收到回應 → 關閉連接
請求 2:
客戶端 → 建立 TCP → 發送 HTTP → 收到回應 → 關閉連接
請求 3:
客戶端 → 建立 TCP → 發送 HTTP → 收到回應 → 關閉連接

每次都要 TCP 三次握手 + 四次揮手!
總耗時:(建立連接 + 請求處理 + 關閉連接) × 3

---

有 Keep-Alive(複用連接):

客戶端 → 建立 TCP → 發送請求 1 → 收到回應
                   → 發送請求 2 → 收到回應
                   → 發送請求 3 → 收到回應
                   → 等待 5 秒(keepalive)
                   → 沒有新請求 → 關閉連接

只需一次 TCP 握手和揮手!
總耗時:建立連接 + (請求處理 × 3) + 關閉連接
節省時間:約 30-50%

配置方式

# gunicorn.conf.py

# 短 keepalive(API 服務)
keepalive = 2  # 默認值

# 中等 keepalive(一般 Web 應用)
keepalive = 5

# 長 keepalive(高頻互動應用)
keepalive = 10

# 極長 keepalive(內部服務)
keepalive = 30

效能影響

# 測試案例:連續發送 100 個請求

# 沒有 Keep-Alive
ab -n 100 -c 1 -k http://localhost:8000/
結果
Time per request: 52.3 ms

# 有 Keep-Alive (keepalive=5)
ab -n 100 -c 1 -k http://localhost:8000/
結果
Time per request: 32.1 ms

效能提升38.5%

keepalive 最佳實踐

場景 1:API 服務(大量短請求)

# gunicorn.conf.py
keepalive = 5

# 理由:
# - 客戶端經常連續發送多個 API 請求
# - 5 秒足夠完成一組操作
# - 不會長時間占用連接

場景 2:靜態網站(Nginx 做 reverse proxy)

# gunicorn.conf.py
keepalive = 2  # 短一點

# Nginx 配置
# proxy_http_version 1.1;
# proxy_set_header Connection "";
# Nginx 會維護到 Gunicorn 的長連接池

場景 3:內部微服務

# gunicorn.conf.py
keepalive = 30  # 較長

# 理由:
# - 服務間頻繁通訊
# - 節省大量連接建立時間
# - 內網環境,連接穩定

🔁 max_requests(防止記憶體洩漏)

什麼是 max_requests?

Worker 處理 N 個請求後自動重啟。防止記憶體洩漏、資源累積。

為什麼需要?

# 記憶體洩漏範例

class MyView:
    # ❌ 全局列表,不斷累積
    cache = []

    def handle_request(self, request):
        # 每個請求都添加資料
        self.cache.append(request.data)
        # cache 永遠不清空!
        return HttpResponse("OK")

# Worker 記憶體變化:
啟動: 100 MB
處理 1000 個請求後: 200 MB
處理 5000 個請求後: 500 MB
處理 10000 個請求後: 1000 MB  💥 OOM Kill!

配置方式

# gunicorn.conf.py

# 基礎配置
max_requests = 1000  # 處理 1000 個請求後重啟

# 添加隨機性(避免所有 workers 同時重啟)
max_requests = 1000
max_requests_jitter = 50  # 隨機 ±50
# 實際重啟範圍:950-1050 個請求

工作原理

Worker 1 啟動
  ↓
處理請求 1, 2, 3, ... , 998, 999, 1000
  ↓
達到 max_requests = 1000
  ↓
處理完當前請求 → 優雅關閉 → Master 啟動新的 Worker 1'
  ↓
新 Worker 1' 記憶體重置為初始狀態 ✅

max_requests 配置建議

高流量應用(頻繁重啟)

# gunicorn.conf.py
max_requests = 500  # 較少
max_requests_jitter = 50

# 理由:
# - 流量大,很快就達到 500 個
# - 頻繁重啟,確保記憶體不累積
# - 對用戶影響小(workers 多)

中等流量應用

# gunicorn.conf.py
max_requests = 1000
max_requests_jitter = 100

# 平衡:
# - 不會太頻繁重啟
# - 也能防止記憶體洩漏

低流量應用

# gunicorn.conf.py
max_requests = 5000
max_requests_jitter = 500

# 理由:
# - 流量低,不需要頻繁重啟
# - 減少 worker 重啟開銷

記憶體敏感應用

# gunicorn.conf.py
max_requests = 100  # 非常低
max_requests_jitter = 10

# 場景:
# - 處理大文件、大資料集
# - 記憶體容易洩漏
# - 頻繁重啟確保穩定

監控 max_requests 效果

# gunicorn.conf.py

def worker_exit(server, worker):
    """Worker 退出時的 hook"""
    from resource import getrusage, RUSAGE_SELF

    # 獲取記憶體使用情況
    mem_mb = getrusage(RUSAGE_SELF).ru_maxrss / 1024
    server.log.info(
        f"Worker {worker.pid} exited. "
        f"Memory usage: {mem_mb:.2f} MB"
    )

# 日誌輸出範例:
# [INFO] Worker 12345 exited. Memory usage: 234.56 MB
# [INFO] Worker 12346 exited. Memory usage: 189.23 MB

📊 backlog(連接隊列)

什麼是 backlog?

等待 Workers 處理的連接隊列大小。當所有 Workers 都忙碌時,新連接會進入隊列等待。

工作原理

流量突增場景:

            等待隊列(backlog = 2048)
            ┌─────────────────┐
新請求 →    │ 等待 等待 等待...│  → Workers(都忙碌中)
            └─────────────────┘
              ↑               ↑
           隊列頭           隊列尾

場景 1:隊列未滿(< 2048)
新請求 → 進入隊列 → 等待 Worker 空閒 → 處理 ✅

場景 2:隊列已滿(= 2048)
新請求 → ❌ 連接被拒絕 → 客戶端收到錯誤

配置方式

# gunicorn.conf.py

# 默認值(適合大多數情況)
backlog = 2048

# 高流量站點
backlog = 4096

# 極高流量(需要配合系統設定)
backlog = 8192

如何判斷 backlog 是否足夠?

監控指標

# Linux 系統監控
netstat -an | grep :8000 | grep SYN_RECV | wc -l
# 如果數量接近 backlog 值,說明隊列快滿了

# 查看連接被拒絕的統計
netstat -s | grep "listen queue"
# SYNs to LISTEN sockets dropped: 123  ← 有丟棄!

計算公式

需要的 backlog = 峰值 RPS × 平均響應時間 × 安全係數

# 例子:
# 峰值 RPS = 500
# 平均響應時間 = 2 秒
# Workers = 4(同時只能處理 4 個)
#
# 等待的請求 = 500 × 2 - 4 = 996
# 安全係數 × 2 = 1992
# backlog 設定 = 2048 ✅

backlog 最佳實踐

配置 1:一般 Web 應用

# gunicorn.conf.py
workers = 4
backlog = 2048  # 默認值足夠

# 可以處理的流量:
# - 正常:4 個並發
# - 高峰:4 個處理中 + 2048 個等待

配置 2:高並發 API

# gunicorn.conf.py
workers = 8
worker_class = 'uvicorn.workers.UvicornWorker'
backlog = 4096  # 加倍

# 搭配系統設定:
# /etc/sysctl.conf
# net.core.somaxconn = 4096
# 執行:sysctl -p

配置 3:微服務(內部調用)

# gunicorn.conf.py
workers = 4
backlog = 1024  # 較小

# 理由:
# - 內網環境,流量可控
# - 不需要很大的隊列
# - 節省記憶體

🔌 worker_connections(Worker 並發連接)

適用於哪些 Worker?

僅適用於異步 Workers:

  • gevent
  • eventlet
  • uvicorn.workers.UvicornWorker
  • sync(不適用)
  • gthread(不適用)

配置方式

# gunicorn.conf.py

# Gevent Worker
workers = 4
worker_class = 'gevent'
worker_connections = 1000  # 每個 worker 最多 1000 並發

# 總並發 = 4 × 1000 = 4000

# Uvicorn Worker
workers = 4
worker_class = 'uvicorn.workers.UvicornWorker'
worker_connections = 1000  # 默認已經很高

# 高並發需求
worker_connections = 10000

📝 完整配置範例

範例 1:小型 Web 應用

# gunicorn.conf.py
import multiprocessing

# Worker 配置
workers = multiprocessing.cpu_count() * 2 + 1  # 9 (假設 4 核)
worker_class = 'sync'
threads = 1

# 超時配置
timeout = 30
keepalive = 2

# 記憶體管理
max_requests = 1000
max_requests_jitter = 50

# 網絡配置
bind = '0.0.0.0:8000'
backlog = 2048

# 日誌
loglevel = 'info'
accesslog = '-'
errorlog = '-'

適用場景:

  • 個人部落格、小型 CMS
  • 流量:< 100 並發
  • 響應快速:< 100ms

範例 2:中型 API 服務

# gunicorn.conf.py
import multiprocessing

# Worker 配置
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gthread'
threads = 4

# 超時配置
timeout = 60
keepalive = 5

# 記憶體管理
max_requests = 1000
max_requests_jitter = 100

# 網絡配置
bind = '0.0.0.0:8000'
backlog = 2048

# 日誌
loglevel = 'warning'
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'

# 效能優化
preload_app = True
worker_tmp_dir = '/dev/shm'

適用場景:

  • RESTful API 服務
  • 流量:100-500 並發
  • 有資料庫查詢和外部 API 調用

範例 3:高並發 ASGI 應用

# gunicorn.conf.py
import multiprocessing

# Worker 配置(ASGI)
workers = multiprocessing.cpu_count()
worker_class = 'uvicorn.workers.UvicornWorker'

# 超時配置(長連接支持)
timeout = 120
graceful_timeout = 30
keepalive = 20

# 記憶體管理
max_requests = 500  # 高流量時更頻繁重啟
max_requests_jitter = 50

# 網絡配置
bind = 'unix:/run/gunicorn.sock'
backlog = 4096
worker_connections = 10000

# 日誌
loglevel = 'warning'
accesslog = None  # 由 Nginx 記錄
errorlog = '/var/log/gunicorn/error.log'

# 效能優化
preload_app = True
worker_tmp_dir = '/dev/shm'

# Hooks
def on_starting(server):
    print(f"Starting with {workers} workers")

def worker_exit(server, worker):
    from resource import getrusage, RUSAGE_SELF
    mem = getrusage(RUSAGE_SELF).ru_maxrss / 1024
    print(f"Worker {worker.pid} exited, memory: {mem:.2f}MB")

適用場景:

  • Django 3.0+ 異步應用
  • WebSocket 支持
  • 流量:> 500 並發
  • 長連接、實時通訊

範例 4:背景任務處理

# gunicorn.conf.py

# Worker 配置
workers = 4  # 較少 workers
worker_class = 'sync'

# 超時配置(長時間任務)
timeout = 300  # 5 分鐘
keepalive = 2

# 記憶體管理(重要!防止記憶體洩漏)
max_requests = 100  # 很低
max_requests_jitter = 10

# 網絡配置
bind = '127.0.0.1:8001'  # 僅內網訪問
backlog = 512  # 較小的隊列

# 日誌
loglevel = 'info'
accesslog = '/var/log/gunicorn/worker_access.log'
errorlog = '/var/log/gunicorn/worker_error.log'

適用場景:

  • 報表生成、數據導出
  • 長時間運行的任務
  • 不需要高並發
  • 記憶體敏感

🎤 面試常見問題

Q1: timeout 設置多少合適?

完整答案:

timeout 的設置取決於應用類型:

一般原則:

timeout = P95_響應時間 × 2 + 10

常見場景:

  1. 快速 API:30 秒(默認)
  2. 一般 Web 應用:60 秒
  3. 報表生成、文件處理:120-300 秒
  4. WebSocket / 長連接:300 秒或更長

注意事項:

  • 不要設置太短,會導致正常請求被殺掉
  • 不要設置太長,會導致問題請求長時間占用 worker
  • 配合 max_requests 定期重啟 worker

監控指標:

  • 如果頻繁出現 “Worker timeout”,考慮增加
  • 如果很少超時,可以保持默認 30 秒

Q2: max_requests 的作用是什麼?

完整答案:

max_requests 用於防止記憶體洩漏,worker 處理 N 個請求後自動重啟。

為什麼需要:

  1. Python 的記憶體管理不完美,可能有洩漏
  2. 第三方庫可能有記憶體問題
  3. 全局變量、快取可能不斷累積

設置建議:

# 高流量
max_requests = 500-1000

# 中流量
max_requests = 1000-2000

# 低流量
max_requests = 5000

# 記憶體敏感
max_requests = 100-500

搭配使用:

max_requests = 1000
max_requests_jitter = 100  # 添加隨機性
# 避免所有 workers 同時重啟

Q3: keepalive 有什麼作用?

完整答案:

keepalive 是 HTTP Keep-Alive 的超時時間,允許在同一個 TCP 連接上處理多個 HTTP 請求。

效能影響:

  • 節省 TCP 握手時間(30-50ms)
  • 減少服務器負載
  • 提升用戶體驗

設置建議:

# API 服務(頻繁請求)
keepalive = 5-10

# 一般 Web 應用
keepalive = 2-5

# 內部微服務
keepalive = 30

注意:

  • Nginx 作為反向代理時,Nginx 會維護連接池
  • 不要設置太長,會占用連接資源
  • 客戶端也要支持 Keep-Alive

Q4: backlog 太小會怎樣?

完整答案:

backlog 是等待隊列的大小。太小會導致:

問題:

  1. 連接被拒絕:客戶端收到 “Connection refused”
  2. 丟失請求:流量高峰時丟失部分請求
  3. 502 錯誤:Nginx 無法連接到 Gunicorn

判斷方法:

# 查看連接隊列統計
netstat -s | grep "SYNs to LISTEN"
# 如果有數字,說明隊列已滿

解決方案:

# 1. 增加 backlog
backlog = 4096

# 2. 配合系統設置
# /etc/sysctl.conf
net.core.somaxconn = 4096

# 3. 增加 workers
workers = cpu_count() * 2

# 4. 使用異步 workers
worker_class = 'uvicorn.workers.UvicornWorker'

✅ 重點回顧

五大核心參數

  1. timeout

    • Worker 處理請求的最大時間
    • 默認 30 秒,根據應用調整
    • 太短會殺掉正常請求,太長會積累問題
  2. keepalive

    • HTTP Keep-Alive 超時
    • 默認 2 秒,API 服務建議 5-10 秒
    • 節省 TCP 連接時間
  3. max_requests

    • Worker 處理 N 個請求後重啟
    • 防止記憶體洩漏
    • 建議 1000-2000,搭配 jitter
  4. backlog

    • 等待連接的隊列大小
    • 默認 2048,高流量可設 4096
    • 需配合系統設置 somaxconn
  5. worker_connections

    • 僅用於異步 workers
    • Gevent/Uvicorn 的並發連接數
    • 默認 1000,可調整到 10000

配置檢查清單

  • timeout 根據應用響應時間設置
  • keepalive 根據客戶端行為調整
  • max_requests 設置為 1000-2000
  • max_requests_jitter 設置為 max_requests 的 10%
  • backlog 檢查是否足夠(監控連接拒絕)
  • worker_connections 根據並發需求設置(異步 workers)
  • 添加日誌記錄超時和重啟事件
  • 監控 worker 記憶體使用情況

📚 接下來

現在你掌握了 Gunicorn 的基礎配置參數!下一篇我們會學習:

02-3. 進階配置技巧

  • preload_app 預載入應用
  • graceful_timeout 優雅關閉
  • worker_tmp_dir 臨時目錄優化
  • Hook functions 生命週期鉤子
  • 日誌配置進階技巧

🤓 小測驗

  1. 如果應用 P95 響應時間是 10 秒,timeout 應該設置多少?

  2. max_requests 為什麼要加 max_requests_jitter?

  3. keepalive 設置太長有什麼問題?

  4. worker_connections 對 sync worker 有效嗎?


上一篇: 02-1. Workers 數量計算 下一篇: 02-3. 進階配置技巧


相關閱讀:


最後更新:2025-10-30

0%