当前位置:首页 > 问答 > 正文

Redis优化 缓存安全 深入探讨Redis缓存穿透问题的解决方案,redis缓存穿透题

Redis优化 | 缓存安全:深入探讨Redis缓存穿透问题的解决方案

场景引入:当你的缓存“形同虚设”

想象一下,你的电商平台刚刚上线了一波促销活动,用户疯狂涌入,服务器压力骤增,你信心满满,因为早已部署了Redis缓存,理论上能扛住高并发,监控面板突然飙红——数据库CPU 100%,响应时间飙升,大量请求超时……

“明明有缓存,怎么数据库还是被打爆了?”

排查后发现,大量请求在查询一个根本不存在的商品ID,导致缓存未命中,请求直接穿透到数据库,这就是典型的缓存穿透问题。


什么是缓存穿透?

缓存穿透是指查询一个数据库中根本不存在的数据,导致每次请求都无法命中缓存,直接访问数据库,如果恶意攻击者构造大量无效请求,数据库可能瞬间被压垮。

缓存穿透 vs 缓存击穿 vs 缓存雪崩

  • 缓存穿透:查询不存在的数据,缓存和数据库都没有。
  • 缓存击穿:某个热点Key突然失效,大量请求直接打到数据库。
  • 缓存雪崩:大量Key同时失效,数据库压力激增。

我们重点解决缓存穿透问题。


缓存穿透的常见原因

  1. 恶意攻击:黑客故意构造大量无效Key进行请求。
  2. 业务逻辑缺陷:比如用户输入非法ID,系统未做校验。
  3. 数据自然淘汰:某些冷门数据被LRU策略清理,但仍有请求访问。

5种解决方案,彻底解决缓存穿透

缓存空对象(Null Caching)

思路:即使数据库查不到,也在Redis中缓存一个空值(如"NULL"),并设置较短的过期时间(如30秒)。

代码示例(Java + Spring Boot)

public Product getProduct(String id) {
    // 1. 先查缓存
    String cacheKey = "product:" + id;
    Product product = redisTemplate.opsForValue().get(cacheKey);
    // 2. 缓存命中,直接返回
    if (product != null) {
        return "NULL".equals(product) ? null : product; // 处理空缓存
    }
    // 3. 查数据库
    product = db.query("SELECT * FROM products WHERE id = ?", id);
    // 4. 数据库无数据,缓存空值
    if (product == null) {
        redisTemplate.opsForValue().set(cacheKey, "NULL", 30, TimeUnit.SECONDS);
    } else {
        redisTemplate.opsForValue().set(cacheKey, product, 1, TimeUnit.HOURS);
    }
    return product;
}

优点:简单直接,能有效拦截重复无效请求。
缺点

Redis优化 缓存安全 深入探讨Redis缓存穿透问题的解决方案,redis缓存穿透题

  • 占用Redis内存(如果恶意Key极多,可能浪费空间)。
  • 短期不一致(如果后来数据库新增了数据,但缓存仍是空值)。

布隆过滤器(Bloom Filter)

思路:在Redis前加一层布隆过滤器,快速判断Key是否可能存在,如果不存在直接拦截请求。

实现步骤

  1. 初始化时,将所有有效Key加载到布隆过滤器。
  2. 查询时,先检查布隆过滤器,若不存在则直接返回。

代码示例(Redisson布隆过滤器)

RBloomFilter<String> bloomFilter = redisson.getBloomFilter("product_bloom");
bloomFilter.tryInit(1000000L, 0.01); // 预期数据量100万,误判率1%
// 预热阶段:加载所有有效ID
List<String> allIds = db.queryAllProductIds();
allIds.forEach(bloomFilter::add);
// 查询阶段
public Product getProduct(String id) {
    if (!bloomFilter.contains(id)) {
        return null; // 直接拦截
    }
    // 后续正常查缓存、数据库...
}

优点:内存占用极低,适合海量数据。
缺点

  • 有一定的误判率(可配置,通常1%)。
  • 不支持删除(需结合其他方案,如Counting Bloom Filter)。

接口层校验

思路:在业务层增加参数校验,拦截非法请求。

示例

Redis优化 缓存安全 深入探讨Redis缓存穿透问题的解决方案,redis缓存穿透题

  • 商品ID必须是数字,且长度固定(如UUID或自增ID)。
  • 用户权限校验,防止越权访问。
public Product getProduct(String id) {
    // 校验ID格式(假设ID必须是6位数字)
    if (!id.matches("\\d{6}")) {
        throw new IllegalArgumentException("Invalid product ID");
    }
    // 后续逻辑...
}

适用场景:适合有明显规则的业务数据。


限流 & 熔断

思路:对异常请求进行限流,防止数据库被击穿。

实现方式

  • Redis + Lua限流脚本:限制同一IP/UserID的请求频率。
  • Sentinel/Hystrix熔断:当异常请求超过阈值,直接熔断。

示例(Redis限流)

-- KEYS[1] = 限流Key(如"rate_limit:user123")
-- ARGV[1] = 时间窗口(秒)
-- ARGV[2] = 最大请求数
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local current = redis.call("INCR", key)
if current == 1 then
    redis.call("EXPIRE", key, window)
end
if current > limit then
    return 0 -- 限流
else
    return 1 -- 放行
end

缓存预热 + 热点数据保护

思路:提前加载可能被频繁访问的数据,避免冷启动问题。

实现方式

Redis优化 缓存安全 深入探讨Redis缓存穿透问题的解决方案,redis缓存穿透题

  • 活动开始前,手动预热热门商品数据。
  • 监控热点Key,自动延长过期时间。

如何选择最佳方案?

方案 适用场景 优点 缺点
缓存空对象 数据量小,变化不频繁 简单易实现 内存占用
布隆过滤器 海量数据,防恶意攻击 内存效率高 有误判率
接口校验 业务规则明确 零成本 仅适合特定场景
限流熔断 高并发场景 保护数据库 可能误伤正常请求
缓存预热 活动/大促场景 避免冷启动 需提前规划

推荐组合方案

  1. 布隆过滤器 + 缓存空对象(兼顾拦截与缓存)。
  2. 接口校验 + 限流(双重防护)。

缓存穿透看似简单,但如果不加防范,可能让精心设计的缓存层形同虚设。关键在于“拦截无效请求”和“保护数据库”,具体方案需结合业务特点选择。

如果你的系统正在面临类似问题,不妨试试布隆过滤器+空缓存的组合,既能高效拦截恶意请求,又能避免数据库过载。

“缓存安全无小事,防微杜渐才能稳如磐石。” 🚀

发表评论