Redis缓存问题及解决方案

查询流程:

在这里插入图片描述

【问题1】——穿透、击穿及雪崩

问题描述
线程T1在查DB并更新redis缓存的过程中(还未成功将结果放入缓存),有大量并发请求对该Key请求,导致有大量的线程去走查DB并更新redis缓存的流程,不仅对DB造成巨大压力,同时还会产生多次不必要的redis缓存更新操作(网络开销)

问题原因

  1. 没有对某些可能的热点数据进行预热
  2. 缓存过期导致【缓存击穿】 或者【缓存雪崩】
  3. 对同一个不存在的key进行大量请求导致【缓存穿透】

解决方案

  1. 对于某些可能的热点数据在项目启动的过程中进行预热处理,刷到redis中
  2. 见下文【缓存击穿】和【缓存雪崩】解决方案
  3. 见下文【缓存穿透解决方案】
【问题2】——数据延迟、一致性

对于更新操作,更新完DB后需要将缓存中的数据也进行刷新,常见的方式有以下几种

问题描述

  1. 先删缓存、再更新DB、再放缓存
    • 删除缓存后,放入新缓存前可能导致【问题1】,即发生【缓存击穿】。但这种情况仅再超高并发且DB操作较费时的情况下有可能发生,
    • 线程1还未更新DB前,线程2访问缓存未命中,去DB拿到旧数据,此时线程1DB更新完成并将新数据放入了缓存中,而线程2这时候将旧数据又放入了缓存中替换了新数据,导致缓存中一直为旧数据
  2. 先删缓存、再更新DB、不放缓存,下次查询再放缓存
    • 如果key为热点key,可能导致【问题1】,即缓存击穿
  3. 先更新DB、再更新缓存
    • 缓存更新前的查询拿到的都是旧数据
    • 如果对同一个记录有连续的更新操作,再更新缓存时可能有如1中所示的顺序混乱的情况导致数据不一致

解决方案

  1. 详见下文缓存击穿解决方案
  2. 详见下文数据一致性解决方案

解决方案

缓存穿透

描述

  1. 大量请求某一个不存的key,导致发生【问题1】
  2. 大量请求不存在的随机key,导致发生【问题1】

解决

  1. 空值缓存
    简单的解决方案,对于某个不存在的key,同样设置到缓存中,值为特定值,过期时间设置的较短,一般为一分钟以内。这种方法仅适用少量key的穿透
  2. 布隆过滤器
    提前对可能的查询条件做布隆过滤器,对所有的请求先过一遍布隆过滤器,过滤掉非法请求
  3. 其他过滤器
    如用户鉴权等对非法请求过滤
缓存击穿

描述

某热点Key失效时,有大量请求进来请求该Key。从而大量请求DB

解决

  1. 使用锁
    当请求缓存未命中时,对后面的请求DB并更新缓存的行为进行加锁,只允许一个线程去做该操作。其他线程在发现被锁住的时候阻塞一会然后重新走查询缓存的流程。
  2. 逻辑过期
    缓存本身不设置过期时间,将过期时间写入到value中,每次查询时判断value是否过期,如果过期,再去更新缓存。但该方案要考虑由于没有过期时间导致LRU替换key的情况,这时候可能导致【缓存击穿】
  3. 提前更新
    类似逻辑过期,但在value中的过期时间要比真正的过期时间要小一些(如统一少三秒),当发现value中的过期时间已经过期时,对这个value过期时间进行修正(再续命两秒)然后走逻辑过期的逻辑;
缓存雪崩

描述

大量key同时过期,导致大量请求进行DB查询,如果某些key时热点key,则这些key都有可能发生【问题1】

解决

key的过期时间要有一定的上下波动性,避免完全一致,分散压力

数据一致性/并发竞争

描述
由于db更新和缓存更新顺序紊乱导致数据不一致的问题

解决

  1. 将对某数据的db更新和缓存更新操作路由到一个JVM队列中,由一个线程去处理。即将这个操作进行串行化处理。同时队列中如果存在多个缓存更新的任务可以进行合并去重,不需要多次更新。这种解决方案会带来很大性能开销
  2. 根据版本号进行更新,db操作的数据附带本次操作的版本号(如时间戳等),这个版本号也会存在于缓存的value中,更新缓存时先判断现在缓存中value版本,如果已存在的版本大于目前要更新的版本,则不去进行更新操作。
发布了98 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Mutou_ren/article/details/103843223