缓存穿透、缓存击穿、缓存雪崩
目录:
缓存机制
受制于数据库操作速度(磁盘操作)和连接池数量限制,在一些高并发场景,通常会设计缓存(内存操作)来减轻DB压力,进行流量削峰。
本质上来说就是更多地延缓用户请求,将请求拦截在上游,以及层层过滤用户的访问需求,遵从“最后落地到数据库的请求数要尽量少”的原则。
-
穿透:利用不存在的key去攻击mysql数据库
-
雪崩:缓存中的很多key失效,导致数据库负载过重宕机
-
击穿:在正常的访问情况下,如果缓存失效,mysql自我保护,重启缓存的过程
缓存基本流程
graph LR;
start(开始)--> case1{缓存内是否有数据};
do2-->ends(结束);
case1--否-->case2{数据库内是否有数据};
case2--是-->do1[更新缓存数据];
do1-->do2[返回正常数据];
case2--否-->do3[返回空数据];
do3-->ends;
case1--是-->do2;
do1-->ends;
缓存穿透
概念场景
所谓穿透,指的就是流量没有命中缓存直接到达DB时:
- 正常流程:缓存未读取到->到DB查->查到后回写缓存
- 穿透流程:缓存未读取到->到DB查->DB也没查到->缓存内也不能有这个值。
- 这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
这种情况常见于攻击,或者业务BUG。比如重复使用uid=-1
或uid特别大或不存在的数据请求查询。
解决方案
- 创建uid的时候是有约束规范的,对于不在约束范围内的做拦截处理;
- 用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
- 访问key未在DB查询到值,也将空值写进缓存
- 设置较短的缓存过期时间。
缓存击穿
概念场景
热点key,在不停的扛着大并发,大并发集中对这一个点进行访问
在一些特殊的时间点,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案
- 设置热点数据永远不过期。
- 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。
缓存雪崩
概念场景
大量的key设置了相同的过期时间,同时/大批到过期时间,查询数据量巨大,引起数据库压力过大甚至down机
缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
- 缓存数据的过期时间设置随机,不会集中在同一时刻失效。
- 使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
- 设置热点数据永远不过期(或通过观察用户行为,合理设置缓存过期时间)。
- 预热数据:在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
- 限流降级:这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。