缓存穿透、缓存击穿、缓存雪崩

缓存机制

受制于数据库操作速度(磁盘操作)和连接池数量限制,在一些高并发场景,通常会设计缓存(内存操作)来减轻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特别大或不存在的数据请求查询。

解决方案

  1. 创建uid的时候是有约束规范的,对于不在约束范围内的做拦截处理;
  2. 用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤;
  3. 访问key未在DB查询到值,也将空值写进缓存
  4. 设置较短的缓存过期时间。

缓存击穿

概念场景

热点key,在不停的扛着大并发,大并发集中对这一个点进行访问

在一些特殊的时间点,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案

  1. 设置热点数据永远不过期。
  2. 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。

缓存雪崩

概念场景

大量的key设置了相同的过期时间,同时/大批到过期时间,查询数据量巨大,引起数据库压力过大甚至down机

缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方案

  1. 缓存数据的过期时间设置随机,不会集中在同一时刻失效。
  2. 使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
  3. 设置热点数据永远不过期(或通过观察用户行为,合理设置缓存过期时间)。
  4. 预热数据:在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  5. 限流降级:这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

参考


分享: