缓存穿透导致接口频繁被刷,该怎么防?

萌新.雨婷 阅读 67

我们有个商品详情页,用户输入不存在的ID时,请求会直接打到数据库,现在被人用脚本疯狂刷无效ID,数据库快扛不住了。我试过加一层内存缓存,但空值没存,好像还是会被穿透。

这是我的React组件里调用接口的逻辑:

useEffect(() => {
  const fetchProduct = async () => {
    const cached = localStorage.getItem(<code>product_${id}</code>);
    if (cached) return setData(JSON.parse(cached));
    
    const res = await fetch(<code>/api/product/${id}</code>);
    const data = await res.json();
    // 如果data是空对象或null,这里就没缓存
    if (data?.id) {
      localStorage.setItem(<code>product_${id}</code>, JSON.stringify(data));
    }
    setData(data);
  };
  fetchProduct();
}, [id]);

是不是应该把空结果也缓存一段时间?但又怕占太多内存,有没有更优雅的做法?

我来解答 赞 10 收藏
二维码
手机扫码查看
2 条解答
Mc.慧利
Mc.慧利 Lv1
你说得对,确实应该把空结果也缓存起来,但需要一些策略来避免内存占用过大。具体来说可以这样处理:

首先在前端代码里,当获取到空数据时也要进行缓存,不过要设置一个较短的过期时间。比如说5分钟,这样能有效防止频繁请求无效ID。

修改后的代码大概长这样:

useEffect(() => {
const fetchProduct = async () => {
const cached = localStorage.getItem(product_${id});
if (cached) return setData(JSON.parse(cached));

try {
const res = await fetch(/api/product/${id});
const data = await res.json();

// 这里做了修改
if (data?.id) {
localStorage.setItem(product_${id}, JSON.stringify(data));
} else {
// 缓存空结果,但加个过期标记
localStorage.setItem(product_${id}, JSON.stringify({ expired: Date.now() + 300000 }));
}

setData(data);
} catch(e) {
console.error('fetch error', e);
}
};
fetchProduct();
}, [id]);


在后端也需要做些改进。建议加一层布隆过滤器,先判断这个ID是不是肯定不存在。布隆过滤器的优点是空间效率高,而且查询速度特别快。如果布隆过滤器说“可能有”,再去查数据库;如果直接说“绝对没有”,就直接返回404。

后端伪代码大概是这样:

const bloomFilter = new BloomFilter();

app.get('/api/product/:id', async (req, res) => {
const id = req.params.id;

// 先用布隆过滤器快速判断
if (!bloomFilter.mightContain(id)) {
return res.status(404).json({ message: 'Not Found' });
}

const product = await db.getProductById(id);
if (product) {
return res.json(product);
} else {
// 更新布隆过滤器
bloomFilter.add(id);
return res.status(404).json({ message: 'Not Found' });
}
});


这样做能从根本上解决缓存穿透问题。前端缓存短期无效结果,减少重复请求;后端用布隆过滤器快速拦截明显无效请求。两层防护下,数据库的压力应该能得到明显缓解。虽然这增加了点开发复杂度,但为了系统稳定性还是值得的。毕竟谁也不想半夜被报警电话吵醒吧。
点赞
2026-03-26 11:03
UP主~春芳
你这个情况典型的缓存穿透问题。我建议几个关键改进点:

首先必须把空结果也缓存起来,不然攻击者用随机ID就能一直穿透到DB。给空结果设置短一点的TTL,比如5分钟,这样既能缓解攻击又不会占用太多内存。

前端可以这样改:
// 缓存空结果时用特殊标记
const cached = localStorage.getItem(product_${id});
if (cached) {
if (cached === 'NULL') return setData(null); // 空结果处理
return setData(JSON.parse(cached));
}

const res = await fetch(/api/product/${id});
const data = await res.json();

if (data?.id) {
localStorage.setItem(product_${id}, JSON.stringify(data));
} else {
// 空结果缓存5分钟
localStorage.setItem(product_${id}, 'NULL', { expires: 300 });
}
setData(data);


后端更关键,必须做这些防护:
1. 加布隆过滤器,快速判断ID是否有效
2. 对频繁请求的IP做限流
3. 商品ID要做格式校验,防止随机字符串攻击

安全提醒:前端缓存只是辅助,主要防护要放在后端。攻击者完全可以绕过前端直接调API,所以后端必须实现布隆过滤和限流。

另外吐槽下,你们这个产品详情页居然不做ID有效性校验就直接查库,这简直是给黑客开绿灯啊...(来自一个凌晨三点还在处理类似问题的苦逼开发)
点赞 2
2026-03-05 19:00