Redis系列 - 缓存雪崩、击穿、穿透、预热、更新

缓存雪崩

产生原因

在某一时刻缓存出现大规模的key失效,导致大量的请求直接打到了数据库上,进而导致数据库压力巨大,在高并发的情况下,瞬间就会将数据库打死宕机,这时如果运维临时重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩。主要有以下两种原因:

  • Redis宕机
  • 缓存的key都在同一时间失效
解决方案
  • 均匀过期:设置不同的过期时间,让缓存失效的时间尽量均匀,避免相同的过期时间导致缓存雪崩,造成大量数据库的访问
  • 分级缓存:第一级缓存失效的基础上,访问二级缓存,每一级缓存的失效时间都不同
  • 热点数据缓存永远不过期
    • 物理不过期,针对热点key不设置过期时间
    • 逻辑过期,把过期时间存在key对应的value里,由后台任务周期性扫描过期时间刷新缓存
  • 保证Redis缓存的高可用,防止Redis宕机导致缓存雪崩的问题。可以使用 主从+ 哨兵,Redis集群来避免 Redis 全盘崩溃的情况。
  • 在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降
  • 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统繁忙”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果
  • 开启Redis持久化机制,尽快恢复缓存数据,一旦重启,就能从磁盘上自动加载数据恢复内存中的数据

缓存击穿

产生原因

缓存击穿是某个热点的key失效,大并发集中对热点key进行请求,就会造成大量请求读缓存没读到数据,从而导致高并发访问数据库,引起数据库压力剧增。这种现象就叫做缓存击穿。

解决方案
  • 在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降
  • 热点数据缓存永远不过期
    • 物理不过期,针对热点key不设置过期时间
    • 逻辑过期,把过期时间存在key对应的value里,由后台任务周期性扫描过期时间刷新缓存

缓存穿透

产生原因

缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。

解决方案
  • 将无效的key存放进Redis中:当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value=”null”,并设置其过期时间较短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义
  • 对用户的请求参数进行合法性检验
  • 使用布隆过滤器:如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力

缓存预热

产生原因

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

解决方案
  • 数据量不大的时候,工程启动的时候进行加载缓存动作
  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新
  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存

缓存更新

产生原因

缓存服务(Redis)和数据服务(数据库)是相互独立且异构的系统,在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据,因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。

缓存本身就是通过牺牲强一致性来提高性能,因此使用缓存提升性能,就会有数据更新的延迟性。这就需要我们在评估需求和设计阶段根据实际场景去做权衡了。

解决方案

缓存更新有四种方式:Cache Aside、Read Through、Write Through、Write Behind Caching。

Cache Aside
  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

  • 命中:应用程序从cache中取数据,取到后返回。

  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

Read Through

在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)。

Write Behind Caching

又称 Write Back,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。

Read More:

Redis 缓存雪崩、击穿、穿透

Redis的缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级

Redis系列 | 缓存穿透、击穿、雪崩、预热、更新、降级