缓存穿透导致接口被恶意刷爆怎么办?

司马春豪 阅读 13

我们线上有个商品详情接口,最近被爬虫疯狂请求不存在的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;
}
我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
技术晓萌
试试这个方法:缓存空值 + 布隆过滤器双保险,别光靠一个。

你现在的代码里,对不存在的 ID 完全没缓存,爬虫一刷不存在的 ID 就直奔数据库,肯定扛不住。先最简单的补救:对查不到的数据也缓存个空值,但过期时间短点,比如 5 分钟。这样恶意刷不存在 ID 的请求最多打一次库,后面都命中空缓存。

修改后的代码:

async function getProduct(id) {
const cacheKey = product:${id};
let data = await redis.get(cacheKey);
if (data !== null) {
// 如果缓存里是空对象或者特殊标记,直接返回 null
if (data === 'NULL') return 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));
} else {
// 关键:对空结果也缓存,防止穿透
await redis.setex(cacheKey, 300, 'NULL'); // 5 分钟过期
}

return data;
}


这样至少能挡住 90% 的无效请求。

至于布隆过滤器没生效,大概率是你没提前把所有合法 ID 都塞进去,或者布隆过滤器本身容量太小、哈希函数设计不合理。布隆过滤器适合提前拦截明显不存在的 ID,但它有误判率,不能完全依赖它防穿透,得配合上面的空值缓存一起用。

如果你数据量特别大(比如百万级商品),布隆过滤器可以放 Redis 前面一层,比如用 Redisson 的布隆过滤器实现:

const redis = new Redis(...);
const rBloom = redis.getBloomFilter('product-id-bloom');
await rBloom.addAsync('123'); // 启动时把所有合法 ID 加进去
// 查询前先判断
const可能 = await rBloom.containsAsync(id);
if (!可能) return null; // 肯定不存在,直接返回
// 否则再走缓存逻辑


但注意:布隆过滤器只适合 ID 已知、变动不频繁的场景。如果商品是动态新增的,得保证布隆过滤器实时更新,否则会误杀新商品。

总结:先上空值缓存,立竿见影;布隆过滤器作为进阶防护,但别指望它 alone 能解决问题。
点赞 3
2026-02-24 14:27