面试常问:redis缓存击穿/穿透/雪崩
缓存穿透:指缓存服务器中没有要查的数据,数据库中也没有,导致业务系统每次都绕过缓存服务器去查询下游数据库,缓存服务器失去了应有的作用。缓存击穿:指的是某一个经常被访问的key缓存过期的时候,大量请求访问这个key,会瞬间穿透缓存服务器同时访问数据库,导致数据库过载的情况。缓存雪崩:指缓存服务器大量缓存同时过期,或者缓存服务器宕机了,所有的请求直接访问到数据库,造成数据库过载很高,影响性能甚至宕机的
1. 讲个故事
一个人去门店买联想电脑,线下门店没有货了,于是店员给厂家打电话问问有没有货,厂家发现也没货了,这个人就走了。过了一会另一个人也要来买联想电脑,然后店员又打电话问了一次厂家,如此反复。这种情况就是“缓存穿透”。
联想电脑太火了,所有门店都没货了,然而很多顾客都会来门店购买,那么就会有很多门店的很多店员都会给厂家打电话询问有没有货,导致厂家电话被打爆。这种情况就是“缓存击穿”。
不只是联想电脑火,华硕、戴尔也特别火,门店都卖光了,而又有很多人来买电脑,这时候又有很多门店同时打电话咨询厂家,或者因为门店人太多被挤爆了,很多客户自己打电话咨询厂家,厂家电话被打爆。这种情况就是“缓存雪崩”。
2. 一句话总结
2.1 缓存穿透
指缓存服务器中没有要查的数据,数据库中也没有,导致请求每次都绕过缓存服务器去查询下游数据库,缓存服务器失去了应有的作用,数据库访问压力很大。
2.2 缓存击穿
指的是某一个经常被访问的热key缓存过期的那一刻,大量请求访问这个key,会瞬间穿透缓存服务器同时访问数据库,导致数据库访问压力很大,出现过载的情况。
2.3 缓存雪崩
指缓存服务器大量缓存同时过期,或者缓存服务器宕机了,某一时刻所有的请求直接访问到数据库,造成数据库访问压力很大,出现过载,影响性能甚至宕机的情况。
3. 如何解决?
3.1 如何解决缓存穿透?
我们知道这种情况的出现很可能是遭到了恶意攻击,比如发起对id为-1或者很大的数据的访问,这样的数据明显是不会存在到数据库中的,所以也不会返回结果,存储到缓存数据库。这种情况有两种解决方案:
- 如果缓存中没有的数据,在数据库中也没有查到,那么我们可以返回value值为null,存储在缓存数据库,设置一个比较短的有效时间,例如30s。
- 使用布隆过滤器,过滤掉对id <= 0 或者id很大的访问。
3.2 如何解决缓存击穿?
分清缓存击穿和缓存穿透的区别,缓存穿透多数情况下数据库中是没有这条数据的,而缓存击穿时,数据库是有这条数据的,只是在缓存中过期了,但因为这条数据是热key,并发访问量非常大,所以会导致数据库过载击穿。
我们有如下三种解决方案:
-
设置热点key永不过期。
这里并不是给这个数据的存活时间设置为永久,而是将过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的重建。
-
使用互斥锁。
在单体项目中是这样的原理:数据的缓存过期后,只有首个访问的线程可以访问数据库,其他线程等待这个线程查询到数据,更新缓存后即可直接从缓存中获取数据。
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。在高并发情况下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的,同样会导致用户等待超时,这是个治标不治本的方法。所以在高并发场景下,尽可能不使用加锁的方式。
以下是伪代码:
public static String getData(String key) throws InterruptedException {
String result = getDataFromRedis(key);
if (result == null) {
if (lockObj.getLock()) {
result = getDataFromMysql(key);
if (result != null) {
redis.set(key, result);
}
lockObj.unLock();
}
} else {
Thread.sleep(100);
result = getDataFromRedis(key);
}
return result;
}
- 定时刷新。
这个和第一条大同小异,我们可以在后台写一个定时任务,假如这条数据的存活时间为10分钟,我们可以每9分钟执行一次定时任务,将数据库中查到的数据更新到缓存中,刷新存活时间。
3.3 如何解决缓存雪崩?
缓存雪崩就是缓存击穿的加强版,所以也可以通过加互斥锁的方式解决。同样,也不适用在高并发的场景下。除了这个,还有以下两种解决方案。
-
设置过期时间为随机值。
我们给缓存设置一个随机的过期时间,使缓存的失效时间可以均匀分布,避免缓存在某一时间大量过期导致缓存雪崩。 -
设置一个缓存标记更新缓存。
缓存标记,记录缓存数据是否过期,如果过期会触发另外的线程在后台去更新实际key的缓存。
真正的缓存数据过期时间比缓存标记多一倍,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后才会返回新数据。
更多推荐
所有评论(0)