缓存穿透导致接口频繁被刷,该怎么防?
我们有个商品详情页,用户输入不存在的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]);
是不是应该把空结果也缓存一段时间?但又怕占太多内存,有没有更优雅的做法?
首先在前端代码里,当获取到空数据时也要进行缓存,不过要设置一个较短的过期时间。比如说5分钟,这样能有效防止频繁请求无效ID。
修改后的代码大概长这样:
在后端也需要做些改进。建议加一层布隆过滤器,先判断这个ID是不是肯定不存在。布隆过滤器的优点是空间效率高,而且查询速度特别快。如果布隆过滤器说“可能有”,再去查数据库;如果直接说“绝对没有”,就直接返回404。
后端伪代码大概是这样:
这样做能从根本上解决缓存穿透问题。前端缓存短期无效结果,减少重复请求;后端用布隆过滤器快速拦截明显无效请求。两层防护下,数据库的压力应该能得到明显缓解。虽然这增加了点开发复杂度,但为了系统稳定性还是值得的。毕竟谁也不想半夜被报警电话吵醒吧。
首先必须把空结果也缓存起来,不然攻击者用随机ID就能一直穿透到DB。给空结果设置短一点的TTL,比如5分钟,这样既能缓解攻击又不会占用太多内存。
前端可以这样改:
后端更关键,必须做这些防护:
1. 加布隆过滤器,快速判断ID是否有效
2. 对频繁请求的IP做限流
3. 商品ID要做格式校验,防止随机字符串攻击
安全提醒:前端缓存只是辅助,主要防护要放在后端。攻击者完全可以绕过前端直接调API,所以后端必须实现布隆过滤和限流。
另外吐槽下,你们这个产品详情页居然不做ID有效性校验就直接查库,这简直是给黑客开绿灯啊...(来自一个凌晨三点还在处理类似问题的苦逼开发)