Redis缓存穿透时为什么要用空值缓存?怎么正确实现?

来源:AI大模型作者:上海GEO公司头衔:草根站长
导读:本期聚焦于小伙伴创作的《Redis缓存穿透时为什么要用空值缓存?怎么正确实现?》,敬请观看详情。当恶意请求不断查询数据库里根本不存在的用户ID,Redis层毫无命中,流量直接打到MySQL,连接池瞬间被打满。空值缓存的思路是把查不到的结果也写进Redis,设置较短过期时间,让后续同样的非法key直接命中空对象返回。但如果不加过期时间或没防住随机key轰炸,内存会被垃圾数据挤爆。实践中要结合布隆过滤器做第一道拦截,空值缓存做兜底,过期时间控制在三到五分钟,并对key格式做校验,才能既挡住穿透又不过度消耗内存。

缓存穿透是指查询一个数据库中根本不存在的数据,由于缓存不命中,请求每次都会落到数据库上。攻击者可利用不存在的ID高频访问,使数据库压力剧增。空值缓存是一种直接有效的应对手段:当查询数据库返回空结果时,仍将空值写入Redis并设置较短的过期时间,使后续相同请求直接命中缓存中的空对象,不再访问数据库。

Redis缓存穿透时为什么要用空值缓存?怎么正确实现?

空值缓存的基本实现原理

在常规的缓存读取逻辑中,我们先从Redis查询key,若存在则直接返回;若不存在则查数据库,数据库有值则回写缓存,无值则不作处理。这种写法在遇上不存在的key时,每一次请求都会穿透到数据库。空值缓存改变了这一行为:当数据库查询结果为空,我们依然向Redis写入一个代表空的标记,例如字符串NULL或空对象{},并赋予一个比正常数据更短的过期时间。

之所以要设置短过期时间,是因为空值本身并不是业务有效数据。若永久保留,Redis中会逐渐堆积大量无意义的key,既浪费内存,又可能掩盖后续该key对应数据被真实写入的情况。一般建议空值缓存的TTL设置为三到五分钟,足以挡住短时间内重复出现的穿透请求,又不会长期占用空间。

下面是一段Java结合Spring Data Redis的示例,展示了在用户查询场景中如何落地空值缓存:

public User getUser(Long id) {
    // 先从Redis查询
    String cacheKey = "user:" + id;
    String cached = redisTemplate.opsForValue().get(cacheKey);
    if (cached != null) {
        // 命中空值标记直接返回null
        if ("NULL".equals(cached)) {
            return null;
        }
        return JSON.parseObject(cached, User.class);
    }
    // 查数据库
    User user = userMapper.selectById(id);
    if (user == null) {
        // 写入空值标记,过期时间设为3分钟
        redisTemplate.opsForValue().set(cacheKey, "NULL", 3, TimeUnit.MINUTES);
        return null;
    }
    // 正常数据写缓存,过期时间30分钟
    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
    return user;
}

空值缓存的局限与随机key攻击

空值缓存能够解决固定不存在key的重复穿透问题,但面对随机生成的无效key仍显无力。攻击者若每次都使用不同的UUID或自增到极大数据范围的ID,那么每一次请求的key在Redis中都不存在,数据库依然会被打到。此时若对每个随机key都写空值,Redis内存会被迅速撑爆,形成另一种形式的资源耗尽攻击。

因此,空值缓存通常不应作为独立方案使用。它更适合作为布隆过滤器或接口参数校验之后的兜底手段。布隆过滤器可以在请求进入Redis之前,快速判断key是否可能存在于集合中;若判断一定不存在,直接拒绝请求。只有布隆过滤器认为可能存在、但数据库实际查不到的情况,才由空值缓存接管,这样写入Redis的空key数量会被控制在极小范围内。

此外,在代码层面对key的格式与范围做基础校验也非常重要。例如用户ID应是正整数且在合理区间,不符合规则的请求在Controller层就返回错误,连Redis都不用查。以下是简单的参数校验示例:

public User getUserApi(@RequestParam Long id) {
    if (id == null || id <= 0 || id > 100000000L) {
        throw new IllegalArgumentException("非法用户ID");
    }
    return getUser(id);
}

空值缓存与布隆过滤器的组合方案

在生产环境中,推荐采用布隆过滤器加空值缓存的双层结构。布隆过滤器使用位数组记录已知有效key的哈希特征,查询时若返回不存在则直接拦截;若返回存在,再走Redis与数据库逻辑。由于布隆过滤器有误判率,可能把不存在的key误判为存在,这时空值缓存就能补上最后一环,避免误判key穿透到数据库。

具体落地时,可借助Redisson提供的RBloomFilter实现分布式布隆过滤器,在用户注册或数据写入时同步加入过滤器。查询链路上先问布隆过滤器,再查Redis空值,最后查数据库并回写。这样即便有少量漏网之鱼,空值缓存也能将数据库保护住,同时空key的写入量因为前置过滤而非常有限。

要注意布隆过滤器本身不存储具体值,不能用来判断空值,它只负责存在性预判。空值缓存负责的是存在性预判通过后、真实查询却落空的情形。二者职责清晰,组合后系统既能抵抗固定穿透,也能缓解随机key冲击,对Redis内存的占用也处于可控范围。以下为组合调用的简化逻辑:

public User safeGetUser(Long id) {
    if (id == null || id <= 0) {
        return null;
    }
    // 布隆过滤器预判
    if (!bloomFilter.contains(id)) {
        return null;
    }
    return getUser(id);
}

通过上述组合,空值缓存不再是孤军奋战,而是作为缓存防御体系中的补充环节,在保障数据库稳定性的同时,也维持了Redis资源使用的合理性。实际配置时,空值TTL、布隆过滤器容量与误判率都需根据业务数据量细致调整。

Redis缓存穿透空值缓存修改时间:2026-08-15 21:42:13

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。