redis 缓存穿透、击穿、雪崩
缓存穿透
缓存穿透指的是,当我们访问某个缓存 key 时,若此 key 不存在于缓存中,则会去查DB数据库,但也没有数据,而后续请求都直接打到DB数据库上。在流量大时,可能DB数据库就挂掉了。缓存和数据库都查询不到这条数据
的现象我们称为缓存穿透
。要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
解决办法
- 给缓存手动设置一个默认值,不去查询DB数据库。
- 把查询DB数据的结果都放入缓存。即使查询返回的为空,仍然将这个空结果存入到缓存中,但是它的过期时间非常的短,如30秒,最长不会超过5分钟。这样可以防止攻击者反复暴力攻击。
- 使用布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个 Key 是否在 DB 中存在,不存在你 return 就好了,存在你就去查了 DB 刷新 KV 再 return。
这样处理的原因是,即使当前查询的 key 为空字符串,或者空数组,非null值,结果也会被缓存起来。后续请求进来发现已经有缓存了,就直接走缓存。
缓存击穿
在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好缓存时间到期,失效了,就会导致大量的请求都打到DB数据库上面去。这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。缓存中没有数据值,但数据库中有此值(一般是缓存时间到期),这种现象我们称为缓存击穿。
解决办法
- 设置热点数据永远不过期。
- 使用互斥锁。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去查询数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX 或者 Memcache 的 ADD)去 set 一个 mutex key,当操作返回成功时,再进行查询数据库的操作并回设缓存;否则,就重试整个 get 缓存的方法。其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。
若系统的并发很高,当缓存过期时,则大量的请求会穿透缓存,同时到DB中查询,那我们可以设置缓存当缓存过期时,只去DB中请求一次并缓存吗?可以,我们可以使用 redis 的setnx(),意思是(set Not Exists),如果$key不存在则设置,存在则不进行任何操作。设置成功设置返回1,说明当前的请求获得了当前的操作权限,设置失败返回0,说明此资源已经被其他请求获得
给存入缓存的数据增加一个过期时间字段暂时:
public function getArticlesLock($key) { $time = time(); $expire = 10 * 2; $lockKey = 'lock:k'; $data = Cache::get($key); if (!is_null($data)) { //缓存未过期 if ($data['expire'] > time()){ return $data['data']; } //加锁失败说明已经有请求执行加锁,返回之前的缓存数据 if (!Redis::setnx($lockKey,1)) { return $data['data']; } } sleep(3); $datat = $this->searchDB(); $data = [ 'data' => $datat, 'expire' => $time + $expire - 10 ]; $r = Cache::put($key, $data, $expire); //解锁 Redis::del($lockKey); return $data['data']; }
缓存雪崩
某时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB数据库上面,DB数据库压力瞬间增大,结果就是DB数据库支撑不住而崩溃。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决办法
- 设置热点数据永远不过期。
- 设置不同的失效时间。为了避免这些热点的数据集中失效,那么我们在设置缓存过期时间的时候,我们让他们失效的时间错开。比如在一个基础的时间上加上或者减去一个范围内的随机值。
- 缓存的高可用性。缓存层设计成高可用,防止缓存大面积故障。即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如 Redis Sentinel 和 Redis Cluster 都实现了高可用。
- 缓存备份和快速预热。比如:Redis持久化机制,尽快恢复缓存集群。一旦重启,就能从磁盘上自动加载数据恢复内存中的数据。
缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
- 直接写个缓存刷新页面,上线时手工操作下;
- 数据量不大,可以在项目启动的时候自动进行加载;
目的就是在系统上线前,将数据加载到缓存中。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?