阿里面试:缓存击穿、缓存穿透、缓存雪崩 3大问题,如何彻底解决?
文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
阿里面试:缓存击穿、缓存穿透、缓存雪崩 3大问题,如何彻底解决?
尼恩特别说明: 尼恩的文章,都会在 《技术自由圈》 公号 发布, 并且维护最新版本。 如果发现图片 不可见, 请去 《技术自由圈》 公号 查找
在40岁老架构师 尼恩的100+读者交流群 中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
- 什么是缓存击穿、缓存穿透、缓存雪崩?
- 缓存击穿、缓存穿透、缓存雪崩 碰到过吗?如何 彻底解决?
最近有小伙伴在面试 阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V171版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,回复:领电子书
这里,把这个答案共享出来,帮大家内力猛增,让面试官爱到 “不能自已、口水直流”。
CacheAside 模式 —— 使用Redis 缓存常用模式
CacheAside 模式, 是使用Redis 缓存常用模式。 一般情况下,CacheAside 的查询过程如下:
- 先查询 Redis,如果Redis 查询成功,直接返回
- 如果Redis 查询不存在,去查询 DB;
- 如果 DB 查询成功,数据回写 Redis,返回
- 如果 DB 查询不存在,直接返回。
使用Redis 缓存的3大核心问题
使用Redis作为缓存时,常见的三大核心问题包括:
-
缓存击穿(Cache Breakdown):
缓存击穿是指一个备受欢迎的缓存数据突然失效或宕机,导致重建缓存时,由于是热点Key,会有大量的线程来查和重建缓存,导致大量数据请求直接到达数据库,这种我们称为缓存击穿。
-
缓存穿透(Cache Penetration):
缓存穿透是指查询的Key压根不存在,所以每次都未命中缓存,直接到数据库,这我们称为缓存穿透。
-
缓存雪崩(Cache Avalanche):
缓存雪崩是指在同一时间段,大量缓存数据同时过期或Redis服务宕机,导致大量请求同时到达数据库,从而给数据库带来非常大的压力。
使用Redis 缓存的3大核心问题,关乎到 系统的稳定性和性能。
所以 缓存击穿、缓存穿透、缓存雪崩 是 高并发的重点问题, 也是面试的重点问题。
尽管如此, 在尼恩 的 技术自由圈 社群中, 还是有很多小伙伴, 傻傻分不清。 面试回答不好。
这里 ,尼恩给大家做一下系统化、体系化的梳理,帮助大家 毒打面试官,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
第一大核心问题: 缓存击穿
一个并发访问量比较大的key(也就是常说的 HotKey)在某个时间过期,导致所有的请求直接打在DB上。
具体来是,就是说某个 key 非常热点(HotKey),访问非常频繁,处于集中式高并发访问的情况,
当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
结果是:
请求会直接访问数据库,并回设到缓存中,高并发访问数据库会导致数据库崩溃。
缓存击穿的原因
缓存击穿的概念就是单个hotkey过期, 所有请求直接打到db上,
缓存击穿 方案1: 加DCL锁更新 缓存
加锁 DCL(双检索) 更新缓存。
加分布式锁(DCL)更新缓存,是一种常见的解决缓存击穿的策略。以下是这个方案的大致步骤:
- 加锁检查缓存(第一次检查):当用户请求数据时,首先检查缓存中是否存在该数据。
- 加锁:如果缓存中没有数据,那么走DB。但是,在尝试从数据库查询数据之前,使用本地锁(或者分布式锁)来确保只有一个请求能够执行数据库查询操作。
- 数据库查询:如果成功获取到锁,那么第二次检查缓存,如果确实缓存中没有数据, 执行数据库查询操作,获取最新的数据。
- 更新缓存:将查询到的数据写入缓存,并设置一个合理的过期时间。
- 释放锁:完成缓存更新后,释放分布式锁,以便其他请求可以继续执行。
- 返回数据:将查询到的数据返回给用户。
- 处理其他请求:对于在等待锁释放期间到达的请求,它们可以直接从缓存中获取数据,而不需要再次查询数据库。
//伪代码
public object GetProductListNew() {
int cacheTime = 30;
String cacheKey ="product_list";
String ockKey = cacheKey;
String cacheValue = CacheHelper.get(cacheKey);//第一次检查
if (cacheValue != null) {
return cacheValue!
}else {
synchronized(lockKey) cacheValue = CacheHelper.get(cacheKey); //第二次检查
if (cacheValue != null){
return cacheValue;
}else {
//这里一般是sql查询数据
cacheValue = GetProductListFromDB();
CacheHelper.Add(cacheKey,cacheValue, cacheTime);
}
}
return cacheValue;
}
}
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。
假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。
同样会导致用户等待超时,这是个治标不治本的方法!
注意:在高并发场景下尽可能不用!
缓存击穿 方案2:热点数据,永不过期
热点数据,永不过期 的策略如下:
(1)预先设置热门数据:
在redis高峰访问时期,提前设置热门数据到缓存中,或适当延长缓存中key过期时间。
(2)对于热点key设置,永不过期。
要值得注意的是,这里说到的永不过期并不是将热点数据存在时间设置为无限制。而是将过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的重建。
其实,这个方案不太可行: 因为这个解决不了一个核心问题, 哪些数据是 hotkey?
缓存击穿 方案3: 多级缓存 + 热点探测 + 数据预加载
对于有条件的团队, 如果并发量确实比较高 , 下面这个才是比较有效的方案。
多级缓存 + 热点探测 + 数据预加载 的方案比较复杂, 具体请参见尼恩架构团队的文章:
第二大核心问题: 缓存穿透
缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样。
缓存穿透的原因:
当 某个key在 数据库没有数据时, 正常情况下,缓存也无数据可存.
所以对于这种value 不存在的 key, 缓存 形同虚设, 每次都查DB 。
为什么?
因为 数据库无数据,当然也不会有对应key 的数据写入缓存, 所以,缓存中也不会有数据。
既然缓 存中也不会有数据,因此每次请求都会去查询数据库,这种情况就出现缓存穿透。
红色的线条,就是缓存穿透的场景。
当查询的 Key 在缓存和 DB 中都不存在时,就会出现这种情况。
缓存穿透的恶果:
比如有个接口需要查询商品信息。
如果有恶意用户随意使用一个 不存在的商品 ID 发起请求,瞬间并发量很高, DB 会直接挂掉。
缓存穿透方案1:缓存空值
既然缓存 穿透的原因是:因为数据库无数据,缓存中也不会有数据。
当我们从数据库中查询到空值时,可以向缓存中 回写一个空值。
当然,为了避免缓存被长时间占用,需要给这个空值加一个比较短的过期时间,例如 3~5 分钟。
缓存空值的问题:
当大量无效请求穿透过来时,缓存内就会有 大量的空值缓存。
如果缓存空间被占满了,还会因剔除掉一些已经被缓存的用户信息,反而会造成缓存命中率的下降,所以这个方案,需要评估缓存容量。
有什么办法提升 缓存空值的 空间浪费问题呢 ? 可以考虑使用布隆过滤器。
缓存穿透方案2:布隆过滤器
针对这个问题,加一层布隆过滤器。
布隆过滤器的原理是在你存入数据的时候,会通过散列函数将它映射为一个位数组中的K个点,同时把他们置为1。
这样当用户再次来查询A,而A在布隆过滤器值为0,直接返回,就不会产生击穿请求打到DB了。
显然,使用布隆过滤器之后会有一个问题就是误判。
因为,布隆过滤器本身是一个数组,可能会有多个值落到同一个位置, 理论上来说只要我们的数组长度够长,误判的概率就会越低。
但是,仍然有可能会误判。
不过,这里有转机。
因为 布隆过滤器 是 “ 存在“ 才有 误判, “不 存在“ 没有 误判。
也就是说: 如果 布隆过滤器 判定不存在, 那就是真的不存在。
所以, 布隆过滤器 用在这个场景,是可以的。 如果 布隆过滤器 判定不存在, 就去DB中查询,不会再发生缓存 穿透。
布隆过滤器 的问题:
存在数据一致性问题, 解决方案参考下图:
第三大核心问题:缓存雪崩
某⼀时刻发⽣⼤规模的缓存失效的情况,例如缓存服务宕机、大量key在同一时间过期,这样的后果就是⼤量的请求进来直接打到DB上,db无响应,最后可能导致整个系统的崩溃,称为雪崩。
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,
但是缓存机器意外发生了:
- 缓存全盘宕机,缓存挂了,
- 大量key在同一时间过期
此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后db无响应,最后导致整个系统的崩溃。
总之,雪崩和击穿、热key的问题不太一样的是,雪崩是指大规模的缓存都过期失效了。
缓存雪崩的两大原因
总结一下,缓存雪崩的两大原因:
- 缓存全盘宕机,缓存挂了,
- 大量key在同一时间过期
缓存雪崩的3大解决方案:
缓存雪崩是三大缓存问题里最严重的一种,我们来看看怎么预防和处理。
- 提高缓存可用性
- 集群部署:通过集群来提升缓存的可用性,可以利用Redis本身的Redis Cluster或者第三方集群方案如Codis等。
- 多级缓存:设置多级缓存,设置一级缓存本地 guava 缓存,第一级缓存失效的基础上再访问二级缓存 redis,每一级缓存的失效时间都不同。
- 过期时间
- 设置不同的过期时间:过期时间随机值。为了避免大量的缓存在同一时间过期,可以把不同的 key 过期时间随机生成,避免过期时间太过集中。
- 热点数据永不过期。
- 熔断降级
-
限流: 如果redis宕机,可以限流,避免同时刻大量请求打崩DB
-
熔断:当缓存服务器宕机或超时响应时,为了防止整个系统出现雪崩,可以使用hystrix 类似的熔断,暂时停止业务服务访问db, 或者其他被依赖的服务,避免 MySQL 被打死。
-
降级:当出现大量缓存失效,而且处在高并发高负荷的情况下,在业务系统内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback(退路)错误处理信息。
设置不同 过期时间 的参考实现:
避免缓存设置相近的有效期,为有效期增加随机值(1-5分钟)使失效时间均匀分布。
这样每一个缓存的过期时间的重复率就会降低,很难引发集体失效的事件。
缓存添加随机时间示例:
// 缓存原本的失效时间
int exTime = 10 * 60;
// 随机数生成类
Random random = new Random();
// 缓存设置
jedis.setex(cacheKey, exTime + random.nextInt(1000) , value);
说在最后:有问题找老架构取经
Redis 是 面试的核心 知识, 涉及到的核心面试题,主要包括:
阿里面试: 缓存击穿、缓存穿透、缓存雪崩 3大问题,如何彻底解决? (本题)
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
遇到上面的面试题,可以按照尼恩的深度 梳理,进行 深度回答,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。
很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。前段时间,刚指导一个27岁 被裁小伙,拿到了一个年薪45W的JD +PDD offer,逆天改命。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。
技术自由的实现路径:
实现你的 架构自由:
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
… 更多架构文章,正在添加中
实现你的 响应式 自由:
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
实现你的 spring cloud 自由:
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
实现你的 linux 自由:
实现你的 网络 自由:
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
实现你的 分布式锁 自由:
实现你的 王者组件 自由:
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》