直接用 functools.lru_cache 做本地缓存,Redis 缓存必须用专用客户端+显式键管理;key 要稳定(如 md5(func.__name__ + str(sorted(kwargs.items())))),必设 ex,捕获连接异常并降级,FastAPI 中应封装为 async Depends 而非装饰器,防穿透/击穿需空值缓存与 NX 写入,更新应主动 delete 而非依赖 TTL。

Redis装饰器怎么写才不踩坑
直接上结论:别自己手写通用 @cache 装饰器,用 functools.lru_cache 做本地缓存,Redis 缓存必须走专用客户端 + 显式键管理。自己拼接 key、忽略过期时间、不处理异常,90% 的缓存失效和雪崩都从这儿开始。
常见错误是把 Redis 当成“带持久化的 lru_cache”——比如用 json.dumps(args) 当 key,结果浮点数精度、字典顺序、None/NaN 处理全乱套;或者没设 ex 参数,缓存永久驻留,数据一更新就永远看不到新结果。
- key 必须稳定可预测:推荐用
hashlib.md5((func.__name__ + str(sorted(kwargs.items()))).encode()).hexdigest()[:16],不依赖repr()或json.dumps() - 必须显式传
ex=60(秒),别依赖 Redis 默认;高并发场景建议加nx=True防重复写入 - 装饰器里 catch
redis.ConnectionError和redis.TimeoutError,失败时直接 fallback 到原函数,别让缓存故障拖垮接口
FastAPI 里怎么给 API 接口加 Redis 缓存
FastAPI 的 Depends 和异步支持让缓存逻辑容易错位。最稳妥的方式不是装饰路由函数,而是封装成依赖项(Depends),在路径操作函数里显式调用。
原因很简单:路径函数参数含 Request、BackgroundTasks 或依赖了其他 Depends 时,装饰器拿不到完整上下文,且无法 await Redis 操作。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 定义一个 async 依赖函数,例如
async def get_cached_user(user_id: int, redis: Redis = Depends(get_redis)) - 在依赖里先
await redis.get(f"user:{user_id}"),命中则json.loads()返回;未命中则查 DB +await redis.setex(f"user:{user_id}", 300, json.dumps(...)) - 避免在装饰器里做 await ——
async def cache_decorator(...)是无效的,Python 不允许 async 装饰器包装 sync 函数
缓存穿透和击穿怎么防
高频接口一旦遇到大量查不存在的 ID(比如恶意刷 /user/999999999),Redis 没命中、DB 也查不到,流量全打到数据库,就是缓存穿透。击穿则是热点 key 过期瞬间,大量请求同时涌向 DB。
这两个问题不能靠“加个 try-except”解决,得从策略层堵住。
- 穿透:查 DB 返回 None 时,往 Redis 写个空值(如
"null")+ 短过期(ex=60),注意业务层要识别并跳过这个假值 - 击穿:用
SET key value EX 300 NX命令(即redis.set(..., nx=True)),只让第一个请求回源,其余等待重试或短暂降级 - 别用布隆过滤器——小项目没必要,维护成本高,且误判率在用户 ID 类场景下反而可能漏掉合法请求
缓存更新时机为什么不能只靠超时
依赖 TTL 自动过期是最省事的做法,但也是最危险的。DB 数据改了,Redis 还要等 5 分钟才刷新,前端看到的就是脏数据;更糟的是,如果接口响应体结构变了(比如字段重命名),旧缓存 JSON 反序列化会直接报 KeyError 或类型错误,而你根本收不到告警。
真正可控的方式,是在数据变更的明确节点主动删缓存,哪怕多写两行 redis.delete("user:123")。
- 用户资料更新后,立刻
await redis.delete(f"user:{user_id}"),而不是等它过期 - 删比更新更安全:避免序列化逻辑不一致、网络失败导致新旧缓存共存
- 批量操作(如后台导出)后,用
redis.scan(match="user:*")+delete清范围,但注意别在主线程扫全库,要用scan分页
缓存不是开关,是需要和业务逻辑对齐的活状态。写错一行 delete,比写十行 setex 更难排查。

















