缓存穿透问题的成因分析与三种实战解决方案

爱丹 Dev 优化 阅读 2,099
赞 25 收藏
二维码
手机扫码查看
反馈

缓存穿透的那些事儿,我踩过的坑全在这了

最近又遇到缓存穿透的问题,折腾了好几天才搞定。这种问题每次遇到都让人头大,但解决完后回头看看,其实也就那么回事儿。今天就把我的实战经验分享出来,希望能帮到你们。

缓存穿透问题的成因分析与三种实战解决方案

我的写法,亲测靠谱

先说说我现在的处理方式吧。简单来说就是“布隆过滤器+缓存空值”。代码实现如下:

const bloomFilter = new BloomFilter({ size: 1000000, hashes: 5 });

// 初始化布隆过滤器
async function initBloomFilter() {
    const keys = await fetch('https://jztheme.com/api/valid-keys').then(res => res.json());
    keys.forEach(key => bloomFilter.add(key));
}

// 查询逻辑
async function getData(key) {
    if (!bloomFilter.test(key)) {
        return null; // 不在布隆过滤器中,直接返回
    }

    const cache = getFromCache(key);
    if (cache !== null) {
        return cache;
    }

    const data = await fetch(https://jztheme.com/api/data?key=${key}).then(res => res.json());

    if (!data) {
        setToCache(key, 'EMPTY'); // 缓存空值
        return null;
    }

    setToCache(key, data);
    return data;
}

这段代码的核心思想是:用布隆过滤器快速判断key是否存在,避免大量无效请求打到数据库。同时对查询结果为空的情况也进行缓存,防止恶意攻击者反复请求不存在的数据。

这几种错误写法,别再踩坑了

讲完最佳实践,再说说那些容易出问题的写法。这些问题我都踩过,真的是血泪教训。

错误一:直接查数据库

async function badGetData(key) {
    const cache = getFromCache(key);
    if (cache !== null) {
        return cache;
    }
    return await fetch(https://jztheme.com/api/data?key=${key}).then(res => res.json());
}

这种写法看起来没问题,但如果有人故意请求大量不存在的key,就会导致每个请求都打到数据库,造成严重性能问题。我就曾经因为这个差点被运维同事打死。

错误二:只缓存有值的结果

async function anotherBadGetData(key) {
    const cache = getFromCache(key);
    if (cache !== null) {
        return cache;
    }

    const data = await fetch(https://jztheme.com/api/data?key=${key}).then(res => res.json());
    if (data) {
        setToCache(key, data); // 只缓存非空数据
    }
    return data;
}

这种写法也很常见,问题是如果某个key确实不存在,每次请求都会去查数据库。尤其是在高并发场景下,会导致数据库压力暴增。我在一个电商项目里就吃过这个亏,双11当天差点崩了。

实际项目中的坑

  • 布隆过滤器需要定期更新。我一般会设置一个定时任务,每小时更新一次。记得要处理好更新过程中的数据一致性问题。
  • 缓存空值的时间不宜过长。我通常设置为5分钟,太长会影响正常业务,太短又起不到防护作用。
  • 要注意分布式环境下的缓存一致性。如果你的服务是多实例部署,最好使用集中式的缓存方案,比如Redis。

另外还有个细节要提醒:布隆过滤器的大小和哈希函数数量需要根据实际情况调整。刚开始我配得太大,内存占用太高;后来调小了,又发现误判率上升。最后是通过压测找到的平衡点。

一些补充说明

虽然上面的方案已经比较完善了,但还是有几个地方可以优化:

  1. 可以考虑对接风控系统,对异常访问行为进行限制
  2. 对于特别重要的接口,可以增加限流措施
  3. 定期分析日志,找出频繁访问的无效key,针对性地做优化

不过说实话,这些优化都是锦上添花,基础的布隆过滤器+空值缓存已经能解决90%的问题了。毕竟咱们做开发的,讲究的就是性价比,过度优化反而得不偿失。

以上是我总结的最佳实践,有更好的方案欢迎评论区交流

缓存穿透这个问题说难不难,说简单也不简单。关键是得理解它的本质:避免大量无效请求打到数据库。我的这套方案虽然不是最完美的,但在实际项目中验证过很多次了,稳定性还不错。

如果你有更好的解决方案,或者对这个话题有什么看法,欢迎在评论区留言讨论。前端路漫漫,大家一起进步!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论