缓存之穿透、击穿、雪崩及其解决方法

文章探讨了缓存系统在软件开发中的重要性,详细解释了缓存穿透、缓存击穿和缓存雪崩的概念,并提供了相应的解决方案。缓存穿透是查询不存在的数据,解决方案包括加密key、设置空值缓存等;缓存击穿发生在热点key过期时大量请求到来,可以使用互斥锁或不设过期时间;缓存雪崩是大量缓存同时过期导致数据库压力增大,解决方案包括随机过期时间、高可用集群等。

现代软件开发中,我们必不可少的中间件必定是缓存,也是我们无法避开的一个知识点。今天我们来展开聊一下在开发中对于缓存经常会遇到的三个问题,也是在面试题中经常会被问到的高频知识点!也就是缓存穿透、缓存击穿、缓存雪崩的知识以及其相对应的解决方案!

什么是缓存穿透

缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

假如有人去使用不存在的key对我们进行高频攻击,就会出现我们数据库的压力暴增,从而导致崩溃,这就是缓存穿透现象。

解决方案

1、对url中的key值, id值进行对称加密,使得不轻易的暴露出真实的key值,防止黑客攻击

2、无论数据实际上存不存在,我们都会把这个键存到缓存中(有效期设置的短一些,比如一分钟到三分钟),然后值设置为一个特定值,业务中如果获取到的结果是这个特定值,则返回特定内容或者报错返回。

3、在业务请求时,对数据进行合法性的校验,检查其请求参数是否合理、是否恶意请求、是否含有非法值,对非法请求提前进行拦截。比如我们查询年龄时候,他请求的是负数,则判定为非法请求。

4、进行写数据时,使用布隆过滤器进行打标处理(也就是设置白名单了),当发现缓存中没有对应数据时,可通过查询我们的不漏过滤器来判断数据是否是白名单内数据,如果不在是,则直接返回null或者失败

5、当有异常情况发生时,对方问的对象和数据进行实时监控,然后分析用户的行为是否为恶意请求、攻击或者可恶的爬虫,对其进行特有的用户行为限制,

什么是缓存击穿

缓存击穿:指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求转移到了数据库侧。

解决方案

1、最简单的方式就是不设置热点数据的过期时间,后台操作的时候对缓存进行异步更新操作,比较适用于对缓存一致性要求不严格的场景

2、使用我们的互斥锁(Mutex key),也就是仅仅有一个线程构建缓存,另外的线程等待构建缓存完成后,再从缓存中拿到缓存之后的数据,如果是单机程序,则可以通过lock或者是synchronized来进行处理,如果是分布式环境则需要采用分布式锁来处理。

3、比较猥琐的方式,就是提前进行互斥锁的使用,在value数据的内部设置一个比缓存过期时间更短的过期时间标致,当异步线程发现了这个值快要过期的的时候,立刻对内置的这个时间进行延长,并且重新从数据库进行数据加载,最后在设置到缓存中。

什么是缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

解决方案

1、最有效的解决方案就是,key的过期时间后面随机添加一个数(比如1-10分钟),这样就可以将热点key的过期时间比较均匀的分散开来,避免热点key扎堆过期

2、缓存使用高可用集群模式,这样就减少穿线缓存服务的故障带来的异常

3、不设置热点数据的过期时间,后台操作的时候对缓存进行异步更新操作,比较适用于对缓存一致性要求不严格的场景

4、使用锁或者队列,确保缓存的单线程写操作(这种方案是比较影响高并发量)

5、假如缓存发生雪崩情况,采取服务立刻熔断、限流、降级的措施保障服务的稳定性

6、双key策略,主key设置过期时间,备key不设置过期时间,当主key失效时,直接返回备key值。

总结

本章共讲解了缓存的三种异常情况,上述的三种错误,都是因为原本就应该访问缓存中数据的,但却在缓存中不存在,亦或者是服务出现故障,导致程序流量直接进入到了数据库请求才导致的问题。

好了,今天关于缓存的只是就到这里吧,欢迎大家点击下方卡片,关注我的《coder练习生》

### 缓存穿透雪崩击穿的定义及解决方案 #### 1. 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能数据库因压力过大而崩溃,甚至可能被恶意攻击者利用进行频繁请求攻击[^3]。 **解决方案**: - **布隆过滤器**:使用布隆过滤器提前判断数据是否存在,若布隆过滤器返回不存在,则直接返回空值,避免查询数据库。 - **缓存空对象**:对于查询结果为空的数据,仍然将其写入缓存设置较短的过期时间,防止后续重复查询数据库。 - **限流与熔断**:对高频访问的不存在数据进行限流或熔断处理,减少对数据库的压力。 ```java public Object getData(String key) { // 使用布隆过滤器判断是否存在 if (!bloomFilter.exists(key)) { return null; } Object value = redis.get(key); if (value == null) { // 缓存空对象 value = getDataFromDb(key); if (value == null) { redis.set(key, "null", "PX", 60000); // 缓存空对象,过期时间为60秒 } else { redis.set(key, value, "PX", expire); } } return value; } ``` --- #### 2. 缓存雪崩 缓存雪崩是指缓存中的数据大批量地过期,而查询量巨大,造成数据库压力过大而崩溃。这种情况通常发生在缓存集中过期,大量请求同时到达数据库时[^1]。 **解决方案**: - **设置随机过期时间**:为缓存数据设置带有随机偏移量的过期时间,避免集中过期。 - **预热缓存**:在系统启动或高峰时段前,预先加载热点数据到缓存中。 - **分布式锁**:通过分布式锁机制,确保同一时刻只有一个线程可以加载数据到缓存中,其他线程等待即可。 - **持久化缓存**:利用Redis的RDB或AOF持久化机制,在系统重启后快速恢复缓存数据。 ```java // 设置随机过期时间 long randomOffset = ThreadLocalRandom.current().nextInt(10, 30); // 随机偏移量10~30秒 redis.set(key, value, "PX", baseExpire + randomOffset * 1000); ``` --- #### 3. 缓存击穿 缓存击穿是指某个热点数据在缓存中失效,而此时有大量的并发请求访问该数据,导致所有请求都击穿到数据库,形成瞬时高并发压力,可能导致数据库崩溃[^4]。 **解决方案**: - **分布式锁**:通过分布式锁机制,确保同一时刻只有一个线程可以加载数据到缓存中,其他线程等待即可。 - **双层缓存**:引入本地缓存或二级缓存,减少对远程缓存的依赖。 - **异步更新缓存**:在缓存即将过期时,提前异步刷新缓存,避免过期瞬间的高并发压力。 ```java public Object getDataWithLock(String key) throws InterruptedException { Object value = redis.get(key); if (value == null) { if (lockRedis.set(key, "empty", "PX", lockExpire, "NX")) { try { value = getDataFromDb(key); redis.set(key, value, "PX", expire); } catch (Exception e) { // 异常处理 } finally { lockRedis.delete(key); // 释放锁 } } else { Thread.sleep(50); // 等待50ms后重试 return getDataWithLock(key); } } return value; } ``` --- ### 总结 缓存穿透雪崩击穿缓存系统中常见的问题,合理设计缓存策略可以有效缓解这些问题带来的影响。通过引入布隆过滤器、分布式锁、随机过期时间等技术手段,可以显著提升系统的稳定性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ybb_ymm

你的鼓励会是对我最大的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值