FastAPI狀態管理:簡單實現全局變量與緩存

在Web應用開發中,狀態管理是一個常見的需求——比如多個請求需要共享臨時數據(如計數器、用戶會話),或緩存頻繁訪問的結果以提升性能。FastAPI作爲現代Python異步框架,提供了多種實現狀態管理的方式,其中全局變量和緩存是最基礎也最常用的手段。本文將從簡單到複雜,一步步講解如何在FastAPI中實現這兩種狀態管理方式。

一、全局變量:簡單的狀態共享

全局變量是最簡單的狀態管理方式,直接在代碼中定義一個變量,多個路由函數可以共享和修改它。但需注意:FastAPI支持異步,且可能運行在多進程環境中,全局變量的使用需謹慎處理線程安全多進程隔離問題。

1. 單進程下的全局變量

在單進程部署(如默認的Uvicorn單進程啓動)中,全局變量可以直接使用。以下是一個計數器示例:

from fastapi import FastAPI
import asyncio

app = FastAPI()

# 定義全局變量:用於存儲計數器
count = 0
# 定義異步鎖(解決多請求同時修改的競態條件)
lock = asyncio.Lock()

@app.get("/counter")
async def get_counter():
    global count
    async with lock:  # 獲取鎖,確保同一時間只有一個請求修改count
        count += 1
        return {"current_count": count}

解釋
- count是全局變量,每次請求時計數器自增1。
- asyncio.Lock()是異步鎖,確保同一時間只有一個請求能修改count,避免多請求同時讀寫導致的數據錯誤(例如兩個請求同時讀取count=1,都執行count += 1,最終結果應爲3但可能錯誤變爲2)。
- 此時,每次訪問/counter都會看到計數器遞增,如第一次1,第二次2,依此類推。

2. 全局變量的侷限性

全局變量僅適合單進程場景,存在以下問題:
- 多進程隔離:若用Uvicorn --workers N啓動多個進程,每個進程的內存空間獨立,全局變量無法跨進程共享(例如N=2時,每個進程的計數器獨立計數)。
- 內存依賴:全局變量數據存儲在內存中,服務重啓後數據丟失,且不支持持久化。
- 線程安全風險:若未加鎖,異步環境下多個請求可能同時修改全局變量,導致數據錯誤。

二、緩存:更高效的狀態管理

當需要處理高頻訪問的數據(如用戶信息、配置參數)或跨用戶共享數據時,全局變量效率較低,此時應使用緩存。緩存是臨時存儲數據的“中間層”,避免重複計算或數據庫查詢,常見實現包括內存緩存、專業緩存庫(如cachetools)和分佈式緩存(如Redis)。

1. 內存緩存:用字典模擬緩存

最簡單的緩存方式是直接用Python字典存儲鍵值對,適用於小規模數據和開發環境:

from fastapi import FastAPI
import time

app = FastAPI()

# 模擬數據庫查詢(實際項目中可替換爲真實數據庫操作)
def get_user_from_db(user_id: int):
    time.sleep(1)  # 模擬1秒查詢延遲
    return {"user_id": user_id, "name": f"User_{user_id}"}

# 定義內存緩存字典
user_cache = {}

@app.get("/user/{user_id}")
async def get_user(user_id: int):
    # 1. 先查緩存
    if user_id in user_cache:
        return {"source": "cache", "data": user_cache[user_id]}

    # 2. 緩存未命中,從數據庫查詢並緩存
    user_data = get_user_from_db(user_id)
    user_cache[user_id] = user_data  # 存入緩存
    return {"source": "database", "data": user_data}

效果
- 首次請求/user/1時,需等待1秒(模擬數據庫查詢),並緩存結果;
- 第二次請求/user/1時,直接從緩存讀取,無需等待,性能提升顯著。

2. 用cachetools庫自動管理緩存

cachetools是Python專門的緩存庫,提供多種緩存策略(如LRUTTL),避免手動管理字典的複雜度:

from fastapi import FastAPI
from cachetools import LRUCache, TTLCache
from typing import Optional

app = FastAPI()

# 方式1:LRU緩存(最近最少使用,最多存儲10條數據)
user_cache_lru = LRUCache(maxsize=10)

# 方式2:TTL緩存(自動過期,10秒內未被訪問的數據自動清除)
user_cache_ttl = TTLCache(maxsize=10, ttl=10)

# 模擬用戶查詢(與前例相同)
def get_user_from_db(user_id: int):
    time.sleep(1)
    return {"user_id": user_id, "name": f"User_{user_id}"}

@app.get("/user/lru/{user_id}")
async def get_user_lru(user_id: int):
    # 檢查LRU緩存
    if user_id in user_cache_lru:
        return {"source": "lru_cache", "data": user_cache_lru[user_id]}
    user_data = get_user_from_db(user_id)
    user_cache_lru[user_id] = user_data
    return {"source": "database", "data": user_data}

@app.get("/user/ttl/{user_id}")
async def get_user_ttl(user_id: int):
    # 檢查TTL緩存(10秒過期)
    if user_id in user_cache_ttl:
        return {"source": "ttl_cache", "data": user_cache_ttl[user_id]}
    user_data = get_user_from_db(user_id)
    user_cache_ttl[user_id] = user_data
    return {"source": "database", "data": user_data}

關鍵特性
- LRUCache(maxsize=10):最多緩存10條數據,當新數據超過容量時,自動刪除最久未使用的鍵。
- TTLCache(maxsize=10, ttl=10):緩存鍵值對10秒後自動過期,避免數據永久佔用內存。

3. 分佈式緩存:Redis緩存(進階)

若需跨服務器共享緩存(如多實例部署)或持久化數據,需使用專業緩存服務(如Redis)。以下是FastAPI集成Redis的示例:

from fastapi import FastAPI
import redis.asyncio as redis
import json

app = FastAPI()

# 連接Redis(需提前安裝redis:pip install redis)
redis_client = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)

@app.get("/user/redis/{user_id}")
async def get_user_redis(user_id: int):
    # 1. 嘗試從Redis緩存讀取
    cached_data = await redis_client.get(f"user:{user_id}")
    if cached_data:
        return {"source": "redis_cache", "data": json.loads(cached_data)}

    # 2. 緩存未命中,從數據庫查詢
    user_data = get_user_from_db(user_id)
    # 3. 存入Redis,設置10分鐘過期
    await redis_client.setex(
        f"user:{user_id}",  # 鍵:user:1
        600,                # 過期時間:600秒(10分鐘)
        json.dumps(user_data)  # 值:JSON序列化
    )
    return {"source": "database", "data": user_data}

優勢
- Redis支持分佈式部署,所有服務實例共享同一緩存;
- 可通過setex設置過期時間,避免緩存數據永久佔用空間;
- 支持多種數據結構(字符串、哈希、列表等),滿足複雜緩存需求。

三、總結:全局變量 vs 緩存

場景 全局變量 緩存(內存/Redis)
適用場景 單進程臨時共享、簡單計數器 高頻訪問數據、跨用戶共享、分佈式場景
優勢 代碼簡單,無需額外依賴 性能高、支持過期和持久化
侷限性 多進程隔離、內存依賴、線程安全風險 需額外安裝緩存庫/服務(如Redis)

四、實踐建議

  • 開發環境:簡單場景用全局變量(加鎖),高頻數據用cachetools緩存;
  • 生產環境:多進程/分佈式部署必須用緩存(如Redis),避免全局變量跨進程問題;
  • 數據安全:緩存敏感數據需加密,避免Redis直接存儲明文密碼等信息。

通過本文的示例,你已掌握FastAPI中狀態管理的基礎方法。根據項目需求選擇合適的方案,即可高效解決數據共享和性能優化問題。

小夜