PHP缓存穿透怎么防止

wen PHP项目 20

本文目录导读:

PHP缓存穿透怎么防止

  1. 缓存空对象(最常用)
  2. 布隆过滤器(Bloom Filter,最优方案)
  3. 参数校验与限流
  4. 互斥锁(Mutex Key)
  5. 最佳实践建议

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)。
  • 代码示例

    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 重建,配合其他方案使用

最佳实践建议

  1. 组合使用:最推荐的方式是 “参数校验 + 布隆过滤器 + 缓存空对象”
  2. 兜底策略:即使布隆过滤器说存在、缓存也没命中,在查询数据库时也要加一个熔断/限流机制,防止数据库被打满。
  3. 监控报警:监控缓存命中率和数据库 QPS,如果缓存命中率突然骤降(通常意味着穿透),立即报警。

一句话总结:对于大多数 PHP 项目,缓存空对象 是快速有效的方案;如果是高并发且 key 比较规律的场景,布隆过滤器才是根治穿透的利器。

抱歉,评论功能暂时关闭!