空值缓存必须带ttl且禁用none作值;因redis客户端将none转为字符串"none",导致语义混淆、误判命中及内存泄漏,应统一用"null"标识并配合短ttl。

直接结论:空值缓存必须带 TTL,且不能用 None 作缓存值;否则无法区分“未命中”和“查到空”,缓存穿透照旧。
为什么 redis.set("key", None) 是错的
Redis 的 set 命令会把 None 自动转成字符串 "None"(Python 客户端行为),而后续 get("key") 返回的是 b"None" —— 这既不是逻辑上的空,也不是数据库里真实的缺失状态,反而造成语义混淆。更严重的是,如果没设 TTL,这个假空值会永久占着内存,还可能被误判为有效缓存。
常见错误现象:
- 前端反复请求
/shops/999999,日志显示“缓存命中”,但返回的却是{"id": "None", ...} - 监控发现缓存命中率虚高,但数据库 QPS 仍在飙升
redis.setex() 写空值时必须用明确标记字符串
推荐统一用 "null"(小写、无引号、纯字符串)作为空值标识,配合短 TTL(如 300 秒)。这样既能被代码清晰识别,又不会污染业务数据结构。
实操建议:
- 写空值:用
await redis.setex("shop:999999", 300, "null"),不是None、""或"NULL" - 读缓存后判断:先检查
cached_data == "null",再决定是否抛HTTPException(404) - 避免 JSON 反序列化空值:不要对
"null"调用json.loads(),会报JSONDecodeError
依赖注入里做空值缓存要防 await 错位
FastAPI 的依赖函数里如果混用同步 Redis 客户端(如 redis.Redis)和 async def,会导致事件循环阻塞。必须用 redis.asyncio.Redis,且所有操作都显式 await。
容易踩的坑:
- 在
Depends函数里写redis.get(...)(没 await)→ 程序不报错但永远拿不到值 - 把缓存逻辑写在装饰器里 → 无法 await 异步 Redis 操作,实际变成同步阻塞
- 多个路径函数共用同一个空值 key,但没统一 TTL 策略 → 某些接口空值过期太快,另一些又太久
布隆过滤器不是银弹,得和空值缓存配合用
布隆过滤器能快速拦截 99% 的非法 key,但它有误判率(false positive),且不解决“合法 key 对应数据已被删”的场景。所以生产环境通常组合使用:
- 先查布隆过滤器:
if key not in bloom_filter: return {"error": "not found"} - 再查 Redis:
cached = await redis.get(key),若为"null"直接返回 404 - 若未命中,查 DB;查不到则写
"null"+ TTL;查到则正常写入
真正容易被忽略的是:布隆过滤器本身需要定期重建(比如凌晨跑任务重刷全量合法 ID),否则数据变更后它就失效了。空值缓存可以兜底,但布隆过滤器一旦 stale,空值写入量会陡增。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!











