#FastAPI与Redis集成完全指南
📂 所属阶段:第三阶段 — 数据持久化(数据库篇)
🔗 相关章节:FastAPI SQLAlchemy 2.0实战 · FastAPI异步编程深度解析
#1. 为什么 Web 服务需要 Redis?
传统架构:
请求 → FastAPI → 数据库查询 → 返回
↓
单点瓶颈,高延迟
Redis优化架构:
请求 → FastAPI → Redis缓存(命中)→ 直接返回
↓ ↓
数据库查询 ← 未命中 ← 缓存未命中
↓
Redis写入 ← 更新缓存核心价值总结:
- 🚀 热点数据缓存:内存查询比数据库快10-100倍
- 🔑 分布式Session:多实例部署下共享登录状态
- 📨 轻量消息队列:替代RabbitMQ/Kafka处理简单异步任务
- 🏆 实时排行榜/签到:利用Sorted Set/BitMap原生数据结构
#2. 基础配置与连接管理
#2.1 依赖安装
pip install redis[hiredis] pydantic-settings python-dotenv✅ hiredis是C语言实现的解析器,显著提升大体积数据的处理速度
#2.2 配置与连接
# config.py
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
redis_url: str = "redis://localhost:6379/0"
redis_max_connections: int = 20
redis_socket_keepalive: bool = True
class Config:
env_file = ".env"
@lru_cache()
def get_settings():
return Settings()
# redis_client.py
import redis.asyncio as redis
from config import get_settings
settings = get_settings()
class RedisManager:
_client: redis.Redis | None = None
@classmethod
async def init(cls):
"""初始化连接池"""
if not cls._client:
cls._client = redis.from_url(
settings.redis_url,
max_connections=settings.redis_max_connections,
socket_keepalive=settings.redis_socket_keepalive,
decode_responses=True, # 自动解码bytes为str
encoding="utf-8"
)
await cls._client.ping()
print("✅ Redis连接成功")
@classmethod
def get_client(cls) -> redis.Redis:
if not cls._client:
raise RuntimeError("Redis未初始化")
return cls._client
# main.py生命周期绑定
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
await RedisManager.init()
yield
await RedisManager.get_client().close()
app = FastAPI(lifespan=lifespan)#3. 高性能缓存策略
#3.1 通用异步缓存装饰器
# cache/decorators.py
import json
import hashlib
from functools import wraps
from redis.asyncio import Redis
from redis_client import RedisManager
def cached(
key_prefix: str,
ttl: int = 300, # 默认5分钟
):
"""
支持参数自动哈希的缓存装饰器
用法:
@cached("user:profile:{user_id}", ttl=600)
async def get_user_profile(user_id: int): ...
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
client: Redis = RedisManager.get_client()
# 构造带参数哈希的缓存键
args_str = str(sorted(kwargs.items())) if kwargs else str(args)
args_hash = hashlib.md5(args_str.encode()).hexdigest()[:8]
cache_key = f"{key_prefix}:{args_hash}"
# 尝试获取缓存
cached_val = await client.get(cache_key)
if cached_val:
return json.loads(cached_val)
# 未命中,执行原函数
result = await func(*args, **kwargs)
# 存入缓存(自动处理简单复杂对象的序列化)
await client.setex(
cache_key,
ttl,
json.dumps(result, default=str)
)
return result
return wrapper
return decorator
async def invalidate_cache(pattern: str):
"""清除匹配的缓存键(生产建议用SCAN替代KEYS)"""
client = RedisManager.get_client()
keys = await client.keys(pattern)
if keys:
await client.delete(*keys)#3.2 缓存实战:用户信息+热门文章
# services/user_service.py
from cache.decorators import cached, invalidate_cache
from redis_client import RedisManager
# 缓存用户资料15分钟
@cached("user:profile:{user_id}", ttl=900)
async def get_user_profile(user_id: int):
# 模拟数据库查询
return {"id": user_id, "name": f"用户{user_id}", "email": f"{user_id}@example.com"}
# 更新后清除缓存
async def update_user_profile(user_id: int, data: dict):
# 模拟数据库更新
print(f"更新用户{user_id}:{data}")
await invalidate_cache(f"user:profile:{user_id}*") # 清除所有相关键
# 缓存热门文章1小时,带随机抖动防雪崩
@cached("posts:hot", ttl=3600 + (id % 100) for id in range(1000))
async def get_hot_posts():
# 模拟数据库聚合查询
return [{"id": i, "title": f"热门文章{i}"} for i in range(10)]#4. 分布式Session管理
#4.1 Session管理器
# session/manager.py
import uuid
import json
from datetime import timedelta
from redis.asyncio import Redis
from redis_client import RedisManager
class RedisSessionManager:
def __init__(self, prefix: str = "session:", expire: int = 86400*7):
self.client: Redis = RedisManager.get_client()
self.prefix = prefix
self.expire = expire
async def create(self, user_id: int, **extra) -> str:
"""创建新Session,返回session_id"""
session_id = str(uuid.uuid4())
key = self.prefix + session_id
data = {"user_id": user_id, **extra}
await self.client.setex(key, self.expire, json.dumps(data))
return session_id
async def get(self, session_id: str) -> dict | None:
"""获取并刷新Session"""
key = self.prefix + session_id
data = await self.client.get(key)
if data:
await self.client.expire(key, self.expire) # 续期
return json.loads(data)
return None
async def destroy(self, session_id: str) -> bool:
"""登出时销毁Session"""
key = self.prefix + session_id
return await self.client.delete(key) > 0
session_manager = RedisSessionManager()#4.2 Session认证依赖
from fastapi import Request, Cookie, HTTPException, Depends
from session.manager import session_manager
async def get_current_user(
request: Request,
session_id: str = Cookie(None)
):
"""从Cookie或Header获取Session,返回当前用户"""
# 也支持Header携带(X-Session-ID)
header_id = request.headers.get("X-Session-ID")
current_id = header_id or session_id
if not current_id:
raise HTTPException(401, "请先登录")
session = await session_manager.get(current_id)
if not session:
raise HTTPException(401, "Session已过期,请重新登录")
return session
# 使用示例
@app.get("/profile")
async def my_profile(user: dict = Depends(get_current_user)):
return {"profile": await get_user_profile(user["user_id"])}
@app.post("/logout")
async def logout(session_id: str = Cookie(None)):
if session_id:
await session_manager.destroy(session_id)
return {"msg": "登出成功"}#5. 轻量异步消息队列
#5.1 List实现即时队列
# queue/list_queue.py
import json
from redis_client import RedisManager
LIST_QUEUE = "fastapi:tasks"
async def enqueue_task(task_type: str, **task_data):
"""入队即时任务"""
client = RedisManager.get_client()
await client.rpush(LIST_QUEUE, json.dumps({"type": task_type, "data": task_data}))
async def process_list_queue():
"""后台无限循环处理队列(单独进程/容器运行)"""
client = RedisManager.get_client()
while True:
# 阻塞式弹出,无任务等待0秒(永久)
result = await client.blpop(LIST_QUEUE, timeout=0)
if result:
_, task_json = result
task = json.loads(task_json)
print(f"处理任务:{task}")
# 根据type分发处理
# if task["type"] == "send_welcome": await send_email(task["data"])#5.2 ZSet实现延迟队列
# queue/delay_queue.py
import json
import time
from redis_client import RedisManager
DELAY_QUEUE = "fastapi:delay_tasks"
TEMP_QUEUE = "fastapi:delay_temp"
async def enqueue_delay_task(task_type: str, delay_seconds: int, **task_data):
"""入队延迟任务(例如10秒后发送验证码)"""
client = RedisManager.get_client()
score = time.time() + delay_seconds
await client.zadd(DELAY_QUEUE, {json.dumps({"type": task_type, "data": task_data}): score})
async def process_delay_queue():
"""后台轮询处理延迟任务(建议1秒轮询一次)"""
client = RedisManager.get_client()
while True:
now = time.time()
# 获取所有到期的任务
tasks = await client.zrangebyscore(DELAY_QUEUE, 0, now, withscores=False)
if tasks:
# 用事务/管道原子操作:删除原任务 + 入队即时处理
pipe = client.pipeline()
pipe.zrem(DELAY_QUEUE, *tasks)
for task in tasks:
pipe.rpush(LIST_QUEUE, task)
await pipe.execute()
time.sleep(1)#6. 避坑指南
#6.1 常见陷阱
| 陷阱 | 错误代码 | 修复方案 |
|---|---|---|
| 缓存穿透(大量查不存在的ID) | if not user: return None | 缓存空值,设短过期(如60秒) |
| 缓存雪崩(大量缓存同时过期) | @cached(ttl=3600) | 加随机抖动ttl=3600+random.randint(0,300) |
| 内存泄漏 | await client.set("permanent", data) | 必须设过期时间 |
| 序列化错误 | json.dumps({"time": datetime.now()}) | 用default=str或手动转ISO格式 |
#6.2 生产环境建议
- 连接池配置:根据服务器CPU核心数调整
max_connections(一般为2*CPU核心数) - 禁用KEYS命令:生产环境用
SCAN替代,或者设置Redis配置rename-command KEYS "" - 持久化策略:缓存类应用可选
RDB,Session类可选RDB+AOF混合 - 监控告警:关注
used_memory_human、instantaneous_ops_per_sec、keyspace_hits(命中率>90%为佳)
#7. 总结
Redis是FastAPI性能优化的核心利器,本文覆盖了:
- ✅ 基础连接与生命周期管理
- ✅ 高性能缓存装饰器与实战
- ✅ 分布式Session与认证
- ✅ 即时/延迟轻量消息队列
- ✅ 常见避坑与生产建议
后续可以继续探索:
- Redis Stream实现可靠事件
- Redis BitMap实现签到系统
- Redis Sorted Set实现实时排行榜
- Redis Lua脚本实现原子操作

