缓存穿透是大量请求携带不存在的key绕过缓存直击数据库,导致db崩溃;cache.get_or_set无法防御,因并发请求会同时执行db_query且空结果不缓存;需布隆过滤器前置校验+空值缓存双保险。

缓存穿透不是“缓存没配好”,而是大量请求携带根本不存在的 key(比如 id=-1、product_id="abc")绕过缓存直击数据库——它会在你毫无察觉时把 DB 拖垮。
为什么 cache.get_or_set() 挡不住缓存穿透
很多人以为用 cache.get_or_set(key, lambda: db_query(), timeout=60) 就万事大吉,其实不然:
- 这个 lambda 在缓存未命中时会被**所有并发请求同时执行**,相当于把数据库查询从串行变成并行压测
- 如果
db_query()返回None或空结果,Django 默认不写入缓存,下一次相同无效请求又来一遍 - 没有前置校验,恶意构造的非法 key(如超长字符串、负数 ID)直接进查询链路
必须加空值缓存 + 布隆过滤器双保险
单靠一种手段容易被绕过。真实生产环境要分层拦截:
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
-
布隆过滤器(Bloom Filter)放最前:在请求进入缓存逻辑前快速判断 key 是否“可能”存在。用
mmh3哈希 + Redis 的setbit/getbit实现,误判率可控(通常 -
空值缓存放第二道:当 DB 确认查无此数据,仍调用
cache.set(key, None, timeout=60),避免同一无效 key 反复冲击 -
参数强校验放最外层:对 ID 类字段做
if not isinstance(id, int) or id 拦截;对字符串 ID 做正则或长度限制,比如 <code>if not re.match(r'^[a-f0-9]{32}$', product_id)
Redis 集群下布隆过滤器容易失效的坑
如果你用的是 Redis 集群(非单机),布隆过滤器的 setbit 和 getbit 可能路由到错误节点,导致误判:
- 集群模式下,
key会被哈希到特定 slot,但布隆过滤器操作的是位图,而位图命令在 Redis 集群中**不支持跨 slot 执行** - 解决办法是强制让布隆过滤器 key 落在同一 slot:用
{bloom:user}这种带花括号的 key 格式,Redis 会只对花括号内部分哈希 - 或者干脆不用集群存布隆过滤器,改用单节点 Redis 实例专用于布隆过滤器,避免路由问题
别忽略 Django 信号清理带来的穿透风险
用 @receiver(post_save, sender=User) 清理缓存时,如果只删 user_123,但没同步删 user_profile_123 或 user_permissions_123,下次请求这些 key 就会因 miss 而穿透——尤其当它们原本就为空值缓存时,清理不全等于留了后门。
真正麻烦的不是技术实现,而是空值缓存和布隆过滤器的生命周期管理:布隆过滤器不会自动同步 DB 新增数据,需要后台任务定期重建;空值缓存的 timeout 太短扛不住扫描攻击,太长又拖慢合法数据上线速度——这个平衡点得结合业务 QPS 和数据变更频率手动调,没法一劳永逸。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










