核心思想:为什么用缓存?
首先,我们要明白为什么要在数据库(DB)前面加一个缓存(比如 Redis)。
-
数据库:数据持久化在硬盘上,读写速度慢。
-
缓存:数据放在内存里,读写速度极快。
所以我们把经常被访问的"热点数据"放在缓存里,这样绝大部分请求可以直接从缓存中读取数据,直接返回,大大减轻数据库的压力,提升系统性能。
正常的访问流程是:
读请求 -> 访问缓存 -> 缓存有数据(命中)-> 直接返回
读请求 -> 访问缓存 -> 缓存无数据(未命中)-> 访问数据库 -> 将数据写入缓存 -> 返回
当这个流程出现异常时,就产生了我们下面要说的三个问题。
1. 缓存穿透
🎯 什么是缓存穿透?
缓存穿透 是指查询一个数据库中根本不存在的数据。由于缓存中也不会有这个数据,所以每次请求都会穿过缓存,直接去查询数据库。如果有人恶意地大量发起这种请求,就会导致数据库压力过大甚至崩溃。
简单比喻 :
你去图书馆(缓存)借一本《如何成为亿万富翁》。图书馆没有,你就去总书库(数据库)找,总书库也没有。第二天你又来借同一本书,流程又重复一遍... 如果有成千上万的人每天都来借这本根本不存在的书,总书库的管理员就累垮了。
❓ 为什么会发生?
-
恶意攻击:黑客故意请求大量不存在的数据 ID(如 -1, 0, 或非常大的数字)。
-
业务逻辑错误:用户输入了不合法的参数或ID。
🛡️ 解决方案
-
缓存空对象(null caching)
-
做法 :当数据库也查不到数据时,我们仍然将这个"空结果"(比如
null)进行缓存,并设置一个较短的过期时间(例如 5分钟)。 -
效果:下次再请求这个不存在的数据时,缓存中就有这个"空值"了,可以直接返回,而不用访问数据库。
-
缺点:可能会占用额外的缓存空间;如果恶意请求的 key 是海量且不重复的,此方案效果会打折扣。
-
-
布隆过滤器(Bloom Filter)
-
做法 :在缓存之前,加一个布隆过滤器。布隆过滤器可以高效地判断一个元素"一定不存在"或"可能存在"于某个集合中。系统启动时,将所有可能存在的 key(比如所有商品ID)初始化到布隆过滤器中。
-
流程:请求来了,先让布隆过滤器判断。
-
如果布隆过滤器说"一定不存在",则直接返回空,拒绝访问数据库。
-
如果布隆过滤器说"可能存在",才允许去查询缓存和数据库。
-
-
效果:可以从根本上解决恶意的不存在 key 攻击。
-
缺点:需要额外的组件和空间;实现稍微复杂;有极低的误判率("可能存在"意味着它也可能真的不存在,但概率极低)。
-
总结:缓存穿透是"查无此数",解决方案的核心是【在缓存层挡住无效请求】。
2. 缓存击穿
🎯 什么是缓存击穿?
缓存击穿 是指一个热点 key (访问量非常高的数据)在缓存中过期的那一刻,突然有大量的请求涌向这个 key。此时缓存失效,所有这些请求都会直接打到数据库上,瞬间可能压垮数据库。
简单比喻 :
一家超级网红奶茶店(热点key)每天限量供应。平时大家都是在"预约券"(缓存)上排队。下午3点整,今天的"预约券"突然失效了(缓存过期)。此时所有在门口排队的人(大量请求)瞬间全部涌向柜台(数据库),柜台瞬间被挤爆。
❓ 为什么会发生?
-
某个 key 是热点数据,并发访问量非常大。
-
这个 key 在缓存中过期了。
🛡️ 解决方案
-
设置热点数据永不过期
-
做法:对于一些极热点数据,可以设置其永不过期。然后通过后台任务或消息队列,在数据更新时主动去刷新缓存。
-
效果:从根本上避免了因过期而导致的击穿问题。
-
缺点:需要额外的逻辑来维护数据一致性;如果数据永远不淘汰,可能会占用内存。
-
-
互斥锁(Mutex Lock)
-
做法 :当发现缓存失效时,不是所有线程都去查数据库,而是先尝试去获取一个分布式锁(比如用 Redis 的
SETNX命令)。-
只有一个线程(比如线程A)能成功获取到锁。
-
线程A去数据库查询数据,并重建缓存。
-
其他线程等待,然后重试从缓存中获取数据。
-
-
效果:将高并发的数据库请求串行化,保证只有第一个请求会去访问数据库,其他请求等待并复用缓存结果。
-
缺点:如果获取锁失败,线程需要等待或重试,性能会有一定损耗;系统复杂性增加。
-
总结:缓存击穿是"热点数据过期",解决方案的核心是【防止瞬间大量请求落到数据库上】。
3. 缓存雪崩
🎯 什么是缓存雪崩?
缓存雪崩 是指在同一时间,大量的缓存 key 同时失效 ,或者 Redis 缓存服务直接宕机。导致所有请求都无法从缓存中获取数据,全部转向数据库,引起数据库瞬时压力过大而崩溃。
缓存雪崩是缓存击穿的"升级版",击穿是一个点,雪崩是一片。
简单比喻 :
双十一零点,所有商品的"购物车优惠券"(缓存)都设置在了同一时间过期。零点一过,海量用户发现优惠券没了,同时点击"重新加载"或"查询最新优惠"(大量请求),瞬间把商城的结算系统(数据库)冲垮了。
❓ 为什么会发生?
-
大量 key 设置了相同的过期时间(TTL)。
-
Redis 集群宕机。
🛡️ 解决方案
-
设置不同的过期时间
-
做法 :给缓存数据的过期时间加上一个随机值。比如,基础过期时间是 1 小时,然后再加上一个 1-10 分钟的随机数。这样就能保证大量 key 不会在同一时间点失效。
-
效果:将大量 key 的失效时间分散开,避免集体失效。
-
-
构建高可用的缓存集群
-
做法:通过 Redis 的哨兵(Sentinel)模式或集群(Cluster)模式,实现缓存服务的高可用。即使个别节点宕机,整个集群依然可以提供服务。
-
效果:防止因缓存服务宕机而导致的雪崩。
-
-
服务降级和熔断
-
做法:在应用层,当发现数据库压力过大时,对于非核心业务的数据请求,直接返回预定义的默认值(降级),或者暂时停止服务(熔断),保护数据库不被拖垮。
-
效果:"弃车保帅",保证核心业务的可用性。
-
-
缓存预热
-
做法:在系统上线或重启前,先将部分高频数据加载到缓存中。
-
效果:避免系统刚启动时,大量请求直接落到冷启动的数据库上。
-
总结:缓存雪崩是"大规模缓存失效",解决方案的核心是【分散风险,保证缓存服务高可用】。