Redis缓存穿透怎么解决?总是被恶意请求打穿数据库
我们接口用了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一个空值?设多久?
}
你现在的做法是典型的缓存穿透场景,核心问题在于:不存在的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都别让它打。再给你改一下伪代码,实战可用的:
调试的时候,你可以手动造几个恶意ID压测一下,看看日志里有多少请求被布隆过滤器挡掉了,或者被空值缓存挡掉了。如果发现DB压力还是高,八成是布隆过滤器没加,或者空值没缓存,或者校验没做全。
最后吐槽一句:布隆过滤器这玩意儿其实不难,难的是你得先意识到它能用在这儿。很多人一上来就想“前端配合”,其实根本不用前端管,后端自己就能闭环解决。
再强调一遍:空值缓存+布隆过滤器+ID校验,三者缺一不可,光靠一个等于没防。