本文目录导读:

PHP缓存穿透是指当请求的数据在缓存和数据库中都不存在时,每次请求都会直接打到数据库,导致数据库压力剧增,甚至崩溃,尤其在恶意攻击或高并发场景下,影响非常严重。
以下是几种主流的防止缓存穿透的方案,按推荐程度排序:
缓存空对象(最常用)
这是最直接、最简单的防御手段,当查询数据库发现数据不存在时,仍然将该 key 写入缓存,但 value 设置为一个特殊标记(如 null、、false),并设置一个较短的过期时间(30-60 秒)。
-
优点:实现简单,对现有代码改动小。
-
缺点:会占用额外的缓存空间;如果恶意攻击使用大量不同的 key,缓存中会存满空对象。
-
代码示例(Memcached/Redis):
function getData($key) { $cache = getRedis(); $data = $cache->get($key); // 1. 如果缓存命中(即使是空值),直接返回 if ($data !== false) { return $data; } // 2. 缓存未命中,查数据库 $data = queryDatabase($key); // 3. 如果数据库也没有数据 if (is_null($data)) { // 设置一个空值的缓存,过期时间短一些(60 秒) $cache->setex($key, 60, 'NULL_VALUE'); return null; } // 4. 数据库有数据,设置缓存(正常过期时间) $cache->setex($key, 3600, $data); return $data; }
布隆过滤器(Bloom Filter,最优方案)
这是防止大量不同 key 穿透的最佳方案,在请求到达缓存之前,先通过布隆过滤器判断 key 是否可能存在。
-
原理:布隆过滤器是一个很长的二进制向量和一系列随机映射函数,它可以判断一个元素“一定不存在”或“可能存在”。
-
优点:内存占用极低,查询速度极快(O(k)),能有效拦截 99% 的无效 key。
-
缺点:有一定的误判率(可能会把不存在的 key 误判为存在),但绝不会漏掉存在的 key,实现稍复杂,需要提前预热。
-
使用场景:适用于 key 集合相对固定或可预见的场景(如用户 ID、商品 ID)。
-
PHP 实现:
- 使用 Redis 自带的
Bloom Filter模块(推荐,性能最好)。 - 使用 PHP 扩展(如
bloom-filter)。
- 使用 Redis 自带的
-
代码示例:
function getDataWithBloom($key) { $redis = getRedis(); $bloomKey = 'bloom_filter:products'; // 布隆过滤器的 key // 1. 首先检查布隆过滤器(关键步骤) // 假设你需要安装支持布隆过滤器的 Redis 模块 if(!$redis->bfExists($bloomKey, $key)) { // 布隆过滤器说绝对不存在,直接返回 null,避免查询数据库 return null; } // 2. 如果布隆过滤器说可能存在,再去查缓存 $data = $redis->get($key); if ($data !== false) { return $data; } // 3. 缓存未命中,查数据库 $data = queryDatabase($key); // 4. 处理数据库结果 if (is_null($data)) { // 即使数据库没有,由于布隆过滤器误判,这里可以不用设置缓存空对象 return null; } // 5. 将真实数据写入缓存 $redis->setex($key, 3600, $data); return $data; }
参数校验与限流
在业务逻辑的入口处进行拦截。
- 严格的参数校验:对于明显不合法的 key(ID 为负数、字符串长度异常、包含特殊字符等),直接拒绝请求,这是第一道防线。
- 接口限流:针对同一 IP、同一用户、同一 API 的访问频率进行限制,对于高频攻击请求,直接返回 503 或验证码,防止恶意穿透。
互斥锁(Mutex Key)
对于高并发场景,当缓存失效时,为了防止大量请求同时去查询数据库,可以使用互斥锁来控制只让一个线程去数据库加载数据。
- 结合空对象使用:当发现是空对象且缓存已过期,加锁后重新查询数据库,防止多个请求同时打到数据库。
| 方案 | 复杂度 | 内存占用 | 效果 | 适用场景 |
|---|---|---|---|---|
| 缓存空对象 | 低 | 高 | 中等 | 穿透 key 数量少时 |
| 布隆过滤器 | 高 | 极低 | 极高 | 穿透 key 数量多、高并发、key 相对固定 |
| 参数校验 | 低 | 无 | 一般 | 所有系统的基础防线 |
| 互斥锁 | 中 | 无 | 高 | 热点 key 重建,配合其他方案使用 |
最佳实践建议
- 组合使用:最推荐的方式是 “参数校验 + 布隆过滤器 + 缓存空对象”。
- 兜底策略:即使布隆过滤器说存在、缓存也没命中,在查询数据库时也要加一个熔断/限流机制,防止数据库被打满。
- 监控报警:监控缓存命中率和数据库 QPS,如果缓存命中率突然骤降(通常意味着穿透),立即报警。
一句话总结:对于大多数 PHP 项目,缓存空对象 是快速有效的方案;如果是高并发且 key 比较规律的场景,布隆过滤器才是根治穿透的利器。