ThinkPHP防缓存穿透需组合空值缓存与布隆过滤器:空值缓存用字符串占位符(如'__MISS__')+随机过期时间,显式判断;布隆过滤器需RedisBloom模块支持,前置拦截非法ID;二者互补,缺一不可。
ThinkPHP 怎么加空值缓存防穿透
空值缓存不是“缓存 null”,而是缓存一个有明确语义、可识别、带短过期时间的占位符。ThinkPHP 默认的
和
对
值不友好(比如文件驱动会写入空文件,Redis 驱动可能返回
与未命中混淆),必须主动控制值和过期逻辑。
用字符串标记空结果,比如
或
,避免直接传
过期时间建议设为
秒之间,并加 ±10–20% 随机抖动(如
),防瞬间集体刷新
读取后必须显式判断:
,不能依赖松散比较(
会把
、
、
全判成空)
写操作(如新增用户)后,记得用
主动删掉对应 key,否则空值缓存会阻塞新数据生效
ThinkPHP 能不能直接用 RedisBloom 做布隆过滤器
不能开箱即用,但可以集成。ThinkPHP 本身不内置布隆过滤器,需借助 Redis 模块
(服务端加载)+ PHP 客户端扩展(如
或原生
类调用原始命令)。
初始化阶段要把所有合法 ID 的哈希值批量写入 Bloom 过滤器,例如用
,不能等请求来了再加
查询前先执行
,返回
就直接拒绝,不走
注意误判率:典型配置下约 0.1%,即“说存在但其实没有”,不影响正确性;但“说不存在”一定是真不存在——所以它只适合做前置拦截,不能替代空值缓存
如果用的是 ThinkPHP 自带的
配置,得确认底层 Redis 实例已启用
模块,否则
命令会报
为什么空值缓存 + 布隆过滤器要一起上
单靠空值缓存,扛不住恶意构造的随机 ID(比如
),每个新 ID 都会写一条空缓存,Redis 内存涨得快,KEY 数量爆炸;单靠布隆过滤器,初始化后无法自动感知新插入的 ID(比如后台刚注册一个用户),会导致“真实存在却被拦截”。两者是互补兜底关系。
布隆过滤器负责“减负”:筛掉 99% 以上的非法请求,不让它们碰 Redis 和 DB
空值缓存负责“兜底”:对漏过的、或新增后尚未同步进布隆过滤器的 ID,提供短暂保护
布隆过滤器更新成本高(全量重建 or 批量增量),空值缓存清理成本低(单 key 删除),配合使用才可持续
线上必须监控
的 false-negative 率(应接近 0)和空缓存命中率(长期 > 80% 说明布隆过滤器没起效)
ThinkPHP 缓存穿透防护容易踩的三个坑
很多团队加了空值缓存还是被攻垮,问题往往不出在逻辑,而在细节实现和环境适配。
PHP 8.5.5
PHP 8.5.5 是 PHP 8.5 分支的维护更新版本。该版本延续了“小步快跑”的迭代逻辑,通过深度错误修复、底层性能微调以及安全加固,旨在为开发者提供一个更健壮、更高效的运行环境。该版本严格遵守语义化版本规范,不包含破坏性变更。
下载
立即学习
“
PHP免费学习笔记(深入)
”;
—— 这行代码在文件驱动下实际写入的是空字符串,在 Redis 驱动下可能被转成
,导致后续无法区分“缓存未命中”和“缓存空值”,必须统一用非空字符串标记
用
时没重写回调返回值:如果数据库查不到,回调里直接
,框架会把它当有效值缓存,且不清除旧缓存,造成脏状态
布隆过滤器 key 名硬编码但没加命名空间,比如多个环境共用
,测试库刷进去的数据污染了生产过滤器,结果大量真实用户被拒
布隆过滤器不是加个扩展就完事,它和业务生命周期强绑定;空值缓存也不是 set 一下就高枕无忧,它的值、类型、清理时机,每一处都得抠准。否则防护层自己就成了故障点。
Cache::get()Cache::set()nullfalse'__MISS__''NULL'null30–300rand(270, 330)if ($data === '__MISS__') { return null; }==0''falseCache::rm('user:123')redisbloompredis/predisRedisBF.ADD user_bf 123BF.EXISTS user_bf 9999990Cache::get()cache('redis')redisbloomBF.EXISTSERR unknown commanduser:18273645901BF.EXISTSCache::set('user:999', null, 60)falseCache::remember()return nulluser_bf