本文 AI 產出,尚未審核

FastAPI 效能與最佳化:在多核心環境下使用 Gunicorn + Uvicorn Workers


簡介

在現代的 Web 應用程式中,效能往往是使用者體驗與營運成本的關鍵因素。FastAPI 以其非同步(async)特性與自動產生 OpenAPI 文件的便利性,已成為 Python 生態系中最受歡迎的框架之一。但即使是最快的框架,若只跑在單一執行緒或單一核心上,也無法發揮硬體的全部潛能。

本單元將說明如何在 多核心 CPU 上部署 FastAPI,結合 Gunicorn(作為主控程式)與 Uvicorn workers(作為 ASGI 伺服器)來同時處理大量請求。透過正確的設定與調校,你的 API 可以在同一台機器上同時跑上 數十甚至上百個工作執行緒,大幅提升吞吐量與響應速度。


核心概念

1. 為什麼需要多個 Worker?

  • CPU 核心利用率:單一 Uvicorn worker 只會佔用一個 CPU 核心。若伺服器有 8 核心,僅跑一個 worker 就只能使用 12.5% 的 CPU。
  • 阻塞 I/O:即使使用 async,仍有可能因外部資源(資料庫、檔案系統)阻塞而導致 worker 空閒。多 worker 能在一個 worker 被阻塞時,讓其他 worker 繼續服務請求。
  • 容錯與彈性:當某個 worker 發生未捕獲的例外並崩潰時,Gunicorn 會自動重新啟動它,確保服務不中斷。

2. Gunicorn 與 Uvicorn 的角色

元件 功能 為什麼要一起使用
Gunicorn WSGI/ASGI 的 process manager,負責啟動、監控、重啟 worker。 提供 pre‑fork 模式,使多個 worker 以獨立的 Python process 執行,避免 GIL(Global Interpreter Lock)的限制。
Uvicorn 高效能的 ASGI server,支援 asynciouvloophttptools 等底層加速庫。 作為 Gunicorn 的 worker 程式,負責實際的 HTTP 請求處理與路由分發。

重點:Gunicorn 本身只能跑 WSGI 應用,若要跑 ASGI(如 FastAPI),必須透過 uvicorn.workers.UvicornWorker 作為 worker 類別。

3. 計算適當的 Worker 數量

最常見的公式是:

workers = (cpu_cores * 2) + 1
  • cpu_cores:機器的實體核心數(不含超執行緒)。
  • +1:確保即使所有核心皆忙碌,仍有一個備用 worker 可接受新連線。

當你的服務主要是 I/O 密集型(例如大量資料庫查詢、外部 API 呼叫),可以適度 增加 worker 數量;若是 CPU 密集型(例如大量影像處理),則不宜超過實體核心數。

4. Uvicorn Worker 的常用參數

參數 說明 範例
--workers / -w 設定 worker 數量(由 Gunicorn 控制)。 -w 5
--bind / -b 綁定的 IP:Port。 -b 0.0.0.0:8000
--log-level 日誌等級(debug、info、warning、error)。 --log-level info
--timeout worker 無回應的最大秒數,超過則重啟。 --timeout 30
--keep-alive HTTP keep‑alive 時間(秒)。 --keep-alive 5

程式碼範例

以下示範三個常見的部署方式,從最簡單的單一 worker 到正式環境的 Docker + Systemd 配置。

範例 1:最簡單的本機測試(單一 worker)

# 直接使用 uvicorn(不需要 gunicorn)
uvicorn main:app --host 0.0.0.0 --port 8000 --reload

說明--reload 只適合開發環境,會在程式碼變更時自動重新載入。此指令只會啟動 一個 worker,適合測試功能。


範例 2:使用 Gunicorn + Uvicorn Workers(多核心)

# 假設 CPU 有 4 核心,依公式計算 workers = (4*2)+1 = 9
gunicorn main:app \
    -w 9 \
    -k uvicorn.workers.UvicornWorker \
    -b 0.0.0.0:8000 \
    --log-level info \
    --timeout 30 \
    --keep-alive 5

重點

  • -k uvicorn.workers.UvicornWorker 告訴 Gunicorn 使用 Uvicorn 作為 ASGI worker。
  • -w 99 個獨立的 Python process,每個 process 內部仍然是單執行緒的 event loop。

範例 3:Docker 化部署(Dockerfile + docker‑compose)

Dockerfile

# 使用官方的 Python slim 版映像
FROM python:3.11-slim

# 安裝系統相依套件(例如 gcc、libpq-dev)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev && rm -rf /var/lib/apt/lists/*

# 建立工作目錄
WORKDIR /app

# 複製需求檔與安裝
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製程式碼
COPY . .

# 設定環境變數
ENV PYTHONUNBUFFERED=1

# 使用 gunicorn 作為入口點
CMD ["gunicorn", "main:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"]

docker-compose.yml

version: "3.9"
services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - TZ=Asia/Taipei
    restart: unless-stopped
    # 可根據宿主機 CPU 核心數調整 workers
    command: >
      gunicorn main:app
      -w ${WORKER_COUNT:-4}
      -k uvicorn.workers.UvicornWorker
      -b 0.0.0.0:8000
      --log-level info

說明

  • WORKER_COUNT 可以在執行 docker-compose up -d 前以環境變數方式覆寫。
  • Docker 容器內的 CPU 限制可以透過 docker run --cpus="2"docker-compose.ymldeploy.resources.limits.cpus 來限制,從而讓 workers 設定更貼近實際可用核心。

範例 4:使用 Systemd 管理服務(適合在實體 VM 或裸機上)

# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI with Gunicorn/Uvicorn
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/opt/fastapi
Environment="PATH=/opt/fastapi/venv/bin"
ExecStart=/opt/fastapi/venv/bin/gunicorn main:app \
    -w 8 \
    -k uvicorn.workers.UvicornWorker \
    -b 0.0.0.0:8000 \
    --log-level info \
    --timeout 30 \
    --keep-alive 5

Restart=on-failure
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
# 啟用與啟動服務
sudo systemctl daemon-reload
sudo systemctl enable fastapi.service
sudo systemctl start fastapi.service

重點:Systemd 可以自動監控服務狀態,發生崩潰時會自動重啟,且支援 LimitNOFILE(檔案描述子上限)以避免因大量連線導致「Too many open files」錯誤。


範例 5:動態調整 Worker 數量(使用 preload_app--preload

在某些情況下,資料庫連線池大型模型(如機器學習模型)需要在所有 worker 之間共享初始化成本。Gunicorn 的 --preload 參數可以在 fork 前先載入應用程式。

gunicorn main:app \
    -w 6 \
    -k uvicorn.workers.UvicornWorker \
    --preload \
    -b 0.0.0.0:8000

注意:使用 --preload 時,所有 worker 會共享同一個記憶體區段(寫時複製),若應用程式在啟動時建立了全域的資料庫連線,必須在 startup 事件中重新建立連線,否則會因連線在 fork 後失效而產生錯誤。

# main.py
from fastapi import FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

app = FastAPI()
engine = None
SessionLocal = None

@app.on_event("startup")
def startup_event():
    global engine, SessionLocal
    # 在每個 worker 內部重新建立連線池
    engine = create_engine("postgresql://user:pw@db:5432/mydb")
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

常見陷阱與最佳實踐

陷阱 可能的結果 解決方式
忘記設定 workers 只跑單一核心,效能低下。 依照 CPU 核心數使用公式 (cores * 2) + 1
--preload 後使用全域 DB 連線 子進程繼承的連線失效,拋出 psycopg2.OperationalError startup 事件內部重新建立連線或使用 @app.on_event("shutdown") 正確關閉。
過度設定 workers 超過實體核心 產生過多上下文切換,CPU 使用率飆升,反而變慢。 觀察 htop/top,保持在合理範圍。
未調整 ulimit -n 大量併發時出現 OSError: [Errno 24] Too many open files 在 Systemd 或容器內設定 LimitNOFILEulimit -n 65535
使用同步函式阻塞事件迴圈 單個 worker 被卡住,其他請求被延遲。 盡量使用 async 函式;若必須使用同步 I/O,將其包在 run_in_threadpoolstarlette.concurrency.run_in_threadpool
忽略 keep-alive 設定 連線頻繁建立與關閉,增加 TCP 三次握手開銷。 設定 --keep-alive 5(或根據需求調整)。

進階最佳實踐

  1. 使用 uvloop:在 main.py 內部載入 uvloop,可提升事件迴圈效能。
    import uvloop
    import asyncio
    
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    
  2. 啟用 httptools:Uvicorn 預設已使用 httptools,確保安裝時包含 uvicorn[standard]
  3. 設定適當的 timeout:對於長時間的背景任務,可考慮在 worker 中使用 asyncio.wait_for 或將任務交給 Celery、RQ 等佇列系統。
  4. 監控指標:整合 Prometheus + Grafana,透過 prometheus_fastapi_instrumentator 收集 request_latency_seconds, process_cpu_seconds_total 等指標,幫助調校 workers 數量。
  5. 使用 --reload 僅限開發:生產環境千萬不要使用 --reload,會額外產生 file‑watcher 監控,浪費資源。

實際應用場景

場景 為什麼需要多 worker 建議配置
高流量的公開 API(如金融行情、天氣資訊) 每秒可能接收上千個請求,需要快速回應。 workers = (cpu * 2) + 1keep-alive = 5timeout = 30,使用 uvloop + httptools
內部微服務(資料驗證、認證) 請求量較穩定,但對延遲極為敏感。 workers = cputimeout = 10,配合 asyncpg(非同步 PostgreSQL)或 aioredis
機器學習模型推論服務 每次請求會載入大型模型,CPU/GPU 為瓶頸。 workers = cpu(避免過度分叉),將模型載入放在 --preload 並在每個 worker 中共享(使用 torch.multiprocessingtensorflow 的多執行緒模式)。
長輪詢或 WebSocket(即時聊天、遊戲) 需要保持大量長時間連線。 workers = cpukeep-alive 可調高,或使用 Uvicorn 的 --ws 參數,配合 redis Pub/Sub 做訊息分發。
批次資料處理 API(上傳 CSV、產生報表) 請求會觸發 CPU 密集型運算。 考慮將此類任務交給背景工作者(Celery),API 本身只回應任務 ID,保持 workers 數量在 CPU 數量左右。

總結

FastAPI 本身已具備非同步的高效能基礎,而 Gunicorn + Uvicorn workers 則提供了在 多核心 CPU 上水平擴展的完整解決方案。透過正確的 worker 數量preloadkeep‑alivetimeout系統資源限制 等設定,我們可以:

  1. 最大化硬體利用率,讓每個核心都能發揮作用。
  2. 提升服務彈性,在 worker 崩潰時自動重啟,確保 99.9% 的可用性。
  3. 減少阻塞與資源浪費,藉由非同步 I/O 與事件迴圈的結合,讓高併發請求保持低延遲。

在實務上,建議先 在本機或測試環境gunicorn -w 2 -k uvicorn.workers.UvicornWorker 觀察 CPU、記憶體與 I/O 指標,再逐步調整至正式環境的 (cores*2)+1。配合 Prometheus 監控、SystemdDocker Compose 的自動化管理,你的 FastAPI 服務將能在任何規模的部署中保持穩定、快速與可維護。

祝你在打造高效能 API 的路上,玩得開心、跑得更快! 🚀