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中状态管理的基础方法。根据项目需求选择合适的方案,即可高效解决数据共享和性能优化问题。

小夜