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

有没有经历过这样的恐怖时刻?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同时过期缓存服务宕机 :过期时间随机、集群高可用、服务熔断降级

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

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


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

相关推荐
q***71857 小时前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
大象席地抽烟7 小时前
使用 Ollama 本地模型与 Spring AI Alibaba
后端
程序员小假7 小时前
SQL 语句左连接右连接内连接如何使用,区别是什么?
java·后端
小坏讲微服务7 小时前
Spring Cloud Alibaba Gateway 集成 Redis 限流的完整配置
数据库·redis·分布式·后端·spring cloud·架构·gateway
hoiii1877 小时前
挂载配置文件以Docker启动Redis服务
redis·docker·eureka
方圆想当图灵7 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(下)
分布式·后端·github
方圆想当图灵8 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(上)
分布式·后端·github
小羊失眠啦.8 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust
C++chaofan8 小时前
基于session实现短信登录
java·spring boot·redis·mybatis·拦截器·session
q***33378 小时前
Redis简介、常用命令及优化
数据库·redis·缓存