Redis缓存穿透怎么解决?总是被恶意请求打穿数据库

♫利芹 阅读 20

我们接口用了Redis做缓存,但最近发现有些不存在的ID被疯狂请求,比如/api/user?id=99999999,这种请求直接穿透到数据库,导致DB压力暴增。

我试过缓存空值,但担心内存被占满;也想过用布隆过滤器,但不知道前端这边怎么配合。有没有更稳妥的方案?

目前后端是Node.js,伪代码大概是这样:

const user = await redis.get(<code>user:${id}</code>);
if (user) {
  return JSON.parse(user);
} else {
  const dbUser = await db.query('SELECT * FROM users WHERE id = ?', [id]);
  if (dbUser) {
    redis.setex(<code>user:${id}</code>, 3600, JSON.stringify(dbUser));
    return dbUser;
  }
  // 这里要不要setex一个空值?设多久?
}
我来解答 赞 4 收藏
二维码
手机扫码查看
1 条解答
司马冰杰
这个问题我太熟悉了,之前就踩过这个坑,恶意请求打穿DB确实挺头疼的。

你现在的做法是典型的缓存穿透场景,核心问题在于:不存在的ID请求,Redis里查不到,DB里也查不到,但每次请求都得走DB一层,相当于给DB开了个无底洞。

先说结论:缓存空值 + 布隆过滤器 + ID合法性校验,三件套组合用才稳妥,别光靠一个。

先看你伪代码里那个问题:空值要不要setex?要,但得设短点,别设太久。比如设300秒(5分钟)就行,别设一小时。因为恶意ID通常是随机生成的,过一会儿换一个ID继续打,你缓存太久反而占内存。缓存空值的关键是让这些“无效请求”在Redis里走一圈就返回,别打到DB。

但光缓存空值也有问题:如果攻击者能生成大量不同ID(比如1亿个),那你缓存的空值也得占1亿个key,内存照样扛不住。所以得加一层布隆过滤器。

布隆过滤器不是前端配合的问题,是后端自己就能加的。你可以在Redis前再套一层布隆过滤器,把所有合法存在的ID提前放进去(比如从DB里批量加载,或者每次DB查到数据时同步加进去)。请求来的时候,先问布隆过滤器:“这个ID可能存在吗?”如果回答“不存在”,直接返回,不用查Redis,也不用查DB。

Node.js里可以用 bloom-filters 这个库,或者直接用 RedisBloom 模块(Redis 6+ 支持),后者更稳,不用自己维护布隆过滤器状态。

另外,别忘了前置校验:比如ID是不是数字?是不是在合理范围内?像 id=99999999 这种,如果你的用户表ID是自增的,最大就几万,那直接在接口层拦截掉就行,连DB都别让它打。

再给你改一下伪代码,实战可用的:

// 假设你用了 RedisBloom,或者自己封装了布隆过滤器
const bloomFilter = new BloomFilter(); // 初始化布隆过滤器(可选:启动时预加载合法ID)

async function getUserById(id) {
// 前置校验:ID是不是数字?是不是超范围?
if (typeof id !== 'number' || id <= 0 || id > 10000000) {
return null;
}

// 布隆过滤器兜底:如果肯定不存在,直接返回
if (!bloomFilter.mightExist(id)) {
return null;
}

// 查缓存
let user = await redis.get(user:${id});
if (user) {
return JSON.parse(user);
}

// 缓存没命中,查DB
const dbUser = await db.query('SELECT * FROM users WHERE id = ?', [id]);
if (dbUser) {
// 命中了,写入缓存 + 更新布隆过滤器(可选,如果布隆过滤器支持增量更新)
redis.setex(user:${id}, 3600, JSON.stringify(dbUser));
bloomFilter.add(id); // 布隆过滤器里加上这个合法ID
return dbUser;
}

// DB也没查到,说明这个ID确实不存在,缓存空值(短时间)
redis.setex(user:${id}, 300, JSON.stringify(null));
return null;
}


调试的时候,你可以手动造几个恶意ID压测一下,看看日志里有多少请求被布隆过滤器挡掉了,或者被空值缓存挡掉了。如果发现DB压力还是高,八成是布隆过滤器没加,或者空值没缓存,或者校验没做全。

最后吐槽一句:布隆过滤器这玩意儿其实不难,难的是你得先意识到它能用在这儿。很多人一上来就想“前端配合”,其实根本不用前端管,后端自己就能闭环解决。

再强调一遍:空值缓存+布隆过滤器+ID校验,三者缺一不可,光靠一个等于没防。
点赞
2026-02-25 12:24