缓存世界的三座大山:穿透、击穿、雪崩,今天就把它们铲平!

有没有经历过这样的恐怖时刻?618大促前夕,你信心满满,感觉自己的系统坚如磐石。活动一开始,流量像钱塘江大潮一样涌来,然后...监控警报响得跟过年放鞭炮一样,数据库CPU直接飙到100%,页面加载慢得像回到了2G网时代。

恭喜你,大概率是缓存这哥们没顶住,给你上演了一出"缓存三连"的精彩戏码。

别慌,今天咱们就来好好盘一盘缓存世界里最著名的三座大山:缓存穿透、缓存击穿、缓存雪崩。看完这篇,你就能把它们治得服服帖帖,让你的系统在流量洪峰前稳如老狗。


先唠两块钱的:缓存是个啥?

在正式开打之前,咱得先统一一下思想。

缓存(Cache),说白了就是数据库前面的一个"临时小本本"。

  • 核心思想:把那些读多写少、计算还贼耗时的数据,从慢吞吞的数据库(硬盘)里捞出来,放到速度飞快的内存里。
  • 工作流程(经典读场景)
    1. 请求来了,先屁颠屁颠地去缓存里查。
    2. 缓存有(命中):太好了!直接返回数据,又快又省力。
    3. 缓存没有(未命中):唉,只能苦哈哈地去数据库查。
    4. 从数据库查到数据后,塞一份到缓存里,方便下一个兄弟来查。
    5. 最后再把数据返回回去。

你看,用了缓存之后,大部分请求根本不会去打扰数据库大佬,系统性能直接起飞🛫。但这小本本要是没用好,那就不是助力,而是"爆破鬼才"了。


第一座大山:缓存穿透(Cache Penetration)

现象缓存和数据库里,都!没!有!这!个!数!据! 导致请求每次都像穿透了缓存一样,直接怼到数据库上。

举个栗子🌰 : 有个坏蛋,疯狂请求 GET /user/=-1234567。这user_id明显不存在啊!你的缓存里肯定没有,数据库里也不可能有。于是,这个请求每次都会命中数据库。如果这坏蛋用脚本搞个几万次并发请求,你的数据库可能就直接口吐白沫,当场宕机了。

危害:利用根本不存在的数据发起攻击,打崩数据库。

解决方案 :给他堵上

  1. 接口层增加校验:大哥,你先看看请求合不合法吧!比如id<=0的直接拦截返回,别往下走了。
  2. 缓存空对象(Cache Null Object) :就算在数据库没查到,我也在缓存里把这个诡异的key存起来,值就设为null(或者一个特殊标记),并给它一个较短的过期时间 。下次同样的请求过来,缓存里就有东西了,虽然是个null,但也能保护数据库。
    • 缺点 :可能会在缓存里存一堆没用的null键,如果被攻击的key是随机的大量字符串,效果会打折扣。
  1. 布隆过滤器(Bloom Filter)这是对付穿透的大杀器!
    • 它就是一个巨大的二进制位数组(bitmap)和一系列哈希函数。

    • 工作原理 :把所有可能存在的key提前放到布隆过滤器里。当一个请求来了,先用布隆过滤器判断一下这个key是否存在。

      • 如果不存在 :那这个key肯定不存在,直接返回null,完美拦截!
      • 如果存在这个key不一定真的存在(有极小的误判率),那就继续后面的缓存、数据库查询流程。
    • 优点:内存占用极小,效率极高,能从源头彻底堵住恶意攻击。


第二座大山:缓存击穿(Cache Breakdown)

现象 :某个热点key (比如某个爆款商品详情)在缓存过期的一瞬间,有大量的请求同时涌来。此时缓存没了,所有请求都跑到数据库上去,相当于把这个热点数据直接"击穿"了。

注意 :这个key是真实存在的,只是刚好"卡点"过期了。

危害:秒杀等场景下,一个热点key的失效可能导致数据库瞬间压力巨大。

解决方案 :让请求排队 ,或者永不过期

  1. 互斥锁(Mutex Lock) :只让一个请求去干活!
    • 当发现缓存失效时,不是所有人都能去查数据库。先去抢一把分布式锁
    • 抢到锁的请求,负责去数据库加载数据,并回填到缓存中。
    • 没抢到锁的请求呢?要么休眠一下稍等片刻,要么直接返回个"稍后再试"的提示,等缓存有了再来读。
    • 优点:能极大地减轻数据库压力。
    • 缺点:有点麻烦,引入了锁的逻辑,性能上会有细微损耗。
  2. 逻辑过期(Logical Expiration) / "永不过期"
    • 咱们不设置物理过期时间(TTL),而是在缓存value里藏一个逻辑过期时间。
    • 请求来时,发现数据逻辑上过期了,就像上面一样,抢个锁去后台异步更新缓存。
    • 在更新期间,其他请求仍然可以先返回旧的缓存数据,虽然有点"脏",但不会造成卡顿。
    • 优点:用户体验丝滑,不会出现卡顿等待。
    • 缺点:实现复杂,需要保证数据的最终一致性。

小结:击穿是"点"的问题,是针对某一个热点key的。


第三座大山:缓存雪崩(Cache Avalanche)

现象大量的缓存key在同一时间过期 ,或者Redis缓存服务直接挂了。导致瞬间所有请求都砸向数据库,数据库压力山大,直接崩溃,引起整个系统崩溃,就像雪崩一样,连锁反应,一发不可收拾。

危害:比击穿更恐怖,是"面"的问题,可能导致整个系统瘫痪。

解决方案错开过期时间 + 高可用

  1. 给过期时间加随机值 :这是预防雪崩最简单有效的办法。比如原本统一设置1小时过期,现在改成 1小时 + [0-300秒] 的随机时间。这样就能保证key不会在同一时间点集体去世,而是均匀地过期。
  2. 构建高可用的缓存集群:比如Redis的哨兵(Sentinel)模式或者集群(Cluster)模式。就算一台Redis节点挂了,也能自动进行主从切换,保证缓存服务整体可用,不至于全盘崩溃。
  3. 服务降级和熔断 :借助Hystrix、Sentinel等工具。当检测到数据库快要被拖垮时,对请求进行熔断,直接返回一个预设的默认值(比如"系统繁忙,请稍后再试")或者兜底数据,保护数据库不死,保住大部分用户的基本可用性。

总结一下

问题类型 问题描述 核心解决方案
穿透 查不存在的数据,缓存和DB都没有 :参数校验、缓存空对象、布隆过滤器
击穿 热点key过期瞬间,被高并发访问 :互斥锁、逻辑过期
雪崩 大量key同时过期缓存服务宕机 :过期时间随机、集群高可用、服务熔断降级

好了,各位后端大佬,缓存世界的三座大山已经给大家铲平了!下次面试或者设计系统时,再问到这些问题,你就可以嘴角上扬,微微一笑,绝对不抽,然后从容地说出你的解决方案。

大家还有什么应对高并发的骚操作?或者在实践中踩过哪些坑?欢迎在评论区一起交流摸鱼(学习)心得!


希望这篇博客对你有帮助!如果觉得不错,点个赞再走吧~

相关推荐
用户4099322502122 小时前
需求驱动测试:你的代码真的在按需行事吗?
后端·ai编程·trae
双向333 小时前
前后端接口调试提效:Postman + Mock Server 的工作流
后端
许苑向上3 小时前
Spring Boot 的注解是如何生效的
java·spring boot·后端
Apifox3 小时前
如何让 Apifox 发布的在线文档具备更好的调试体验?
前端·后端·测试
tangweiguo030519873 小时前
Django REST Framework 构建安卓应用后端API:从开发到部署的完整实战指南
服务器·后端·python·django
会豪3 小时前
工业仿真(simulation)-- 自定义物流路线(5)
后端
爱读源码的大都督3 小时前
挑战一下,用Java手写Transformer,先手写QKV,能成功吗?
java·后端·程序员
华仔啊3 小时前
面试官灵魂拷问:count(1)、count(*)、count(列)到底差在哪?MySQL 性能翻车现场
java·后端