缓存穿透导致接口被恶意刷爆怎么办?
我们线上有个商品详情接口,最近被爬虫疯狂请求不存在的ID,直接打穿缓存压垮数据库了。试过加布隆过滤器但没生效,是不是哪里写错了?
这是我现在用的缓存逻辑:
async function getProduct(id) {
const cacheKey = <code>product:${id}</code>;
let data = await redis.get(cacheKey);
if (data !== null) return JSON.parse(data);
// 缓存未命中,查数据库
data = await db.query('SELECT * FROM products WHERE id = ?', [id]);
if (data) {
await redis.setex(cacheKey, 3600, JSON.stringify(data));
}
// 问题:如果id根本不存在,这里不会缓存空值,下次还会查库
return data;
}
你现在的代码里,对不存在的 ID 完全没缓存,爬虫一刷不存在的 ID 就直奔数据库,肯定扛不住。先最简单的补救:对查不到的数据也缓存个空值,但过期时间短点,比如 5 分钟。这样恶意刷不存在 ID 的请求最多打一次库,后面都命中空缓存。
修改后的代码:
这样至少能挡住 90% 的无效请求。
至于布隆过滤器没生效,大概率是你没提前把所有合法 ID 都塞进去,或者布隆过滤器本身容量太小、哈希函数设计不合理。布隆过滤器适合提前拦截明显不存在的 ID,但它有误判率,不能完全依赖它防穿透,得配合上面的空值缓存一起用。
如果你数据量特别大(比如百万级商品),布隆过滤器可以放 Redis 前面一层,比如用 Redisson 的布隆过滤器实现:
但注意:布隆过滤器只适合 ID 已知、变动不频繁的场景。如果商品是动态新增的,得保证布隆过滤器实时更新,否则会误杀新商品。
总结:先上空值缓存,立竿见影;布隆过滤器作为进阶防护,但别指望它 alone 能解决问题。