面试Redis——缓存并发 缓存雪崩 缓存穿透

本文主要叙述缓存并发,缓存雪崩,缓存穿透的问题以及解决方案。

缓存并发

什么是缓存并发

场景:在你每天刷抖音,看微信短视频时,都会有一个评论列表,在评论列表中,查询评论的时候,会先去查询Redis缓存,如果有,就立即返回;如果没,就去数据库查询数据,接着更新缓存,返回数据。这时候,如果访问量非常多,有多个C端同时查询评论,Redis缓存又恰好没缓存数据,此时,多个C端就会同时去查询数据库。上述这种现象,就被称为缓存并发。

缓存命中

没命中缓存

话说回来,缓存并发会带来什么危害

  • 让数据库的压力剧增,因为数据库抗不了高并发,流量再大一点,可以直接被打垮,对用户极其不友好,在如今互联网上,App应用的被替代率是很高的,稍有不慎,用户就会被其他App抢走,这对企业来说是灾难性的。我们可以知道,Redis这玩意儿,要么不出问题,一出问题,肯定是大问题。这也就是为什么要强调知其然知其所以然的原因。

既然我们已经知道缓存并发的的严重性,那如何解决

  • 要解决问题前,我们得分析问题为何会产生。絮叨一下,很多人无论是在面试时或者工作时,遇到问题,就立马改,或者说按照百度给的方法就套上去试,这个不行,试那个。这种解决问题的方式是不正确的。一般解决问题的方法论是:遇到报错——定位问题——分析问题——设计解决方案——解决问题。回到缓存并发问题上,我们分析一下“为什么出现缓存并发”。

为什么会缓存并发

  • 原因一:在高并发场景下,机器刚刚启动,且没预热缓存,缓存都为空,又没用分布式锁
  • 原因二:在高并发场景下,机器运行已运行一段时间,但缓存刚好失效,又没用分布式锁
  • 原因三:在高并发场景下,Redis宕机了

以上3种原因都会产生缓存并发,如有遗漏其他原因,欢迎各位同学在留言区补充。

知道原因后,我们可以对照原因来设计解决方案了。

解决方案

  • 针对原因一和原因二,方案 :分布式锁。这个允许一个线程去请求数据库,其他线程挂起,线程查完数据库后,更新缓存,其他线程去访问缓存,返回数据。至于分布式锁如何使用和其底层实现,其实有比较多的细节注意的,但这里不过多讲解,后续会陆续出对应的文章,如果你安耐不住寂寞,可以去谷歌一下。

  • 针对原因三,方案:Redis的高可用。说到高可用,无非就是主从结构+哨兵模式,或者Redis集群。(加链接)

分布式锁和Redis高可用,其实是预防措施,这些应该是事前工作。如果现在缓存并发确实已经发生了,能做的就是把这个模块服务的流量入口缩小,以免因这个模块服务流量过大把MySQL直接打死,从而导致其他模块服务受到影响。如果是Redis宕机了,先把流量入口缩小,然后赶紧重启机器。

当然,最好是事前做好Hystrix的限流、降级、拒绝服务等工作。限流和降级,限流,顾名思义就是限制流量,允许多少流量可以通过,比如每秒最多1000。超过1000后的请求,就走降级,比如返回一些默认值或者友情提示:目前系统繁忙,稍后重试。拒绝服务,则是最后一道防线,如果流量持续一段时间后,仍然很大,就直接拒绝服务,拒绝服务是为了保护Redis直接被流量一波带走,如果Redis服务被打死后,恢复是比较耗费时间的,而且也会因为流量一直很大,刚重启,流量一波又直接带走Redis,完成双杀,Double Kill。拒绝服务,会等流量小后,较快恢复回正常服务状态。拒绝服务是迫不得已之举。

当初大头菜初入互联网行业时,乳臭未干,什么也不懂,用Redis前,只知道Redis很厉害,很适合做缓存,关于Redis的注意事项,除了知道在生产环境不能用keys *命令外,其他一概不懂。当时的评论项目,因为流量特别大,并发贼高,查询评论接口就出现了缓存并发问题,幸好提前做了默认值回复和限流+降级措施。下面是用分布式锁解决缓存并发的关键代码:

  @Autowired
  private DistributionLock locker;
  
 //没缓存,查数据库,获取评论
  if (comment == null) {
      //加分布式锁,只允许一个线程去回源
      if(locker.trylock(Constants.QUERYCOMMENT+moduleType+resourceId)){
          try {
              comment = getDataFromRedis(moduleType, resourceId);
              if(comment == null){
                  //缓存没数据,去数据库查
                  comment = getDataFromMongoDB(moduleType, resourceId);
              }
          }finally {
              locker.unlock(Constants.QUERYCOMMENT+moduleType+resourceId);
          }
      }

分布式锁

接下来,继续讲缓存穿透

缓存穿透

什么是缓存穿透

场景:查询评论的时候,如果直接查询id=-1的数据,那么在缓存中,没命中,又去数据库中查找,又没命中。上述这种情况,被称为缓存穿透。用通俗易懂的话来概括:就是查找一个一定不存在数据库的数据,就叫缓存穿透。

那缓存穿透有什么坏处呢

  • 如果流量大时,会直接打趴服务,造成服务不可用。

按照方法论的套路继续走

为什么会出现缓存穿透

  • 原因一,非正常请求,比如:黑客攻击,专门构造一些特殊格式的数据来请求,给系统造成巨大压力。

  • 原因二,正常请求,用户输错了数据。

解决方案

  • 方案一:缓存空对象:在请求时,先访问缓存,查不到数据,再去数据库查询,数据库也查不到对应的数据,返回null给客户端。且异步更新缓存(key,“null”),并加入短暂的过期时间。
    缓存空对象

  • 方案二:方案一其实有一个明显的缺点,就是如果请求的数据一定不存在时,那么这个时候,缓存就缓存一大堆无用的(key1,“null”),(key2,“null”)。浪费内存。这个时候可以结合数据校验和布隆过滤器。
    布隆过滤器

说到数据校验,这个事儿是特别重要的。尤其是在一些直接涉及到钱的服务中,数据校验是巨重要的。如果不进行数据校验,大公司的老板就少一台宾利。小公司,就可能直接破产了。下面是大头菜的同学所在公司的案例
案例1
案例2

不管如何,培养良好的开发习惯,能让你受益终身。

缓存雪崩

什么是缓存雪崩

场景:在评论列表中,如果有一批评论成为了热点评论,但不幸,此时这批条评论,在Redis缓存中,都失效了,由于没命中缓存,加上大量请求,都去数据库查询评论,从而给数据库造成极大压力,甚至崩溃。这种情况,被称为缓存雪崩。
缓存雪崩

缓存雪崩的坏处

给数据库极大压力,甚至打垮数据库,从而造成系统不能正常提供服务。

为什么会缓存雪崩

  • 原因一,redis宕机,相当于多个key同一时刻失效。

  • 原因二,redis没宕机,多个key正常到时就失效。

总结:如何你面试的时候,遇到这个问题,最好分情况回答,就是redis是否宕机。给面试官留下脑子清晰的印象。

解决方案

  • 方案一,解决原因一,既然宕机了,那就想到高可用,redis集群,哨兵模式,故障转移和故障恢复,同时还应该做好监控和报警。如果没法自动完成故障转移,那就人工干预。

  • 方案二,过期时间=失效时间+随机时间,解决原因二。

  • 方案三,永不过期,既然你是因为过期时间到了导致的雪崩,那就干脆让你不过期就完事了。有人会问,那缓存一致性怎么保证?后台主动更新:就是通过mysql更新的时候,让mq监听Binlog,回调更新缓存。使其缓存和数据库数据保持一致。

  • 方案四,可以从应用架构角度出发,通过限流,降级,熔断手段来降低影响,除此之外来避免多级缓存来避免这种灾难。如果你使用的微服务架构是SpringCloud,那你可以直接使用Hystrix,来实现限流,降级,熔断,修改一下配置文件即可。

补充

缓存击穿,就是只有一个key过期的缓存雪崩。

总结

全篇下来,几乎都是按照遇到报错——定位问题——分析问题——设计解决方案——解决问题方法论来写的。文中介绍了缓存并发,缓存穿透,缓存雪崩的定义,危险,原因,解决方案。大头菜希望你读完这篇文章后,不仅能学会知识,更能掌握定位问题——分析问题——解决问题的方法论。知识点,这些以后可能会忘,但方法掌握好后,方能见招拆招。

非常感谢你能看到这里,如果觉得文章写得不错 求关注 求点赞 求分享 (对我非常非常有用)。
如果你觉得文章有待提高,我十分期待你对我的建议,求留言。
如果你希望看到什么内容,我十分期待你的留言。
各位的捧场和支持,是我创作的最大动力!

### 关于 MySQLRedis 缓存相关的面试题整理 #### 什么是缓存穿透缓存穿透是指查询一个不存在的数据,由于数据库中也没有对应的记录,因此不会将其写入缓存。这将导致每次请求都会直接访问数据库[^1]。 #### 如何解决缓存穿透问题? 解决方案之一是 **缓存空数据**。当查询到一个不存在的数据时,在缓存中存储一条空值记录,并设置较短的有效期。这样可以减少后续对该键的重复查询压力[^3]。然而,这种方法可能会引发新的问题——如果该数据后来被创建,则需要及时清除缓存中的空值记录以保持一致性。 另一种方法是采用 **布隆过滤器 (Bloom Filter)** 技术。它可以在内存中高效判断某条数据是否存在,从而避免不必要的缓存和数据库查询操作。 #### 缓存雪崩是什么?如何应对? 缓存雪崩指的是大量缓存在同一时刻集中失效,造成短时间内对数据库的巨大冲击。为了避免这种情况发生,可以通过以下方式处理: - 设置随机化的过期时间; - 使用分布式锁机制限制并发更新频率; - 构建多级缓存架构减轻单点负载压力[^2]。 #### 数据库与缓存之间的一致性维护有哪些策略? 为了保证两者间同步准确性,通常采取如下措施: - 双向更新模式:每当修改数据库内容的同时刷新关联缓存项; - 主动删除法:仅在成功插入新纪录至DB后再移除相应旧版Cache Entry; - 定时拉取差异对比并修正偏差部分。 ```python import redis from threading import Lock # 初始化Redis连接池实例化对象r以及全局互斥量lock用于控制资竞争情况下的安全访问行为。 pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True) r = redis.Redis(connection_pool=pool) def get_with_lock(key): lock_key = f"{key}:lock" with Lock(): if not r.exists(lock_key): try: # 加上分布式的独占标志位防止其他客户端干扰当前事务流程执行过程. r.setnx(lock_key,"true") value = fetch_from_db_and_cache_it_if_needed(key) finally: r.delete(lock_key) return value def fetch_from_db_and_cache_it_if_needed(key): cached_value = r.get(key) if cached_value is None: db_result = query_database_for_the_given_key(key) if db_result != 'not_found': store_in_redis(db_result,key) return db_result or "default_empty_string_representation" ``` 上述代码片段展示了利用Python编程语言结合第三方模块`redis-py`实现带锁定功能的安全获取指定KEY所映射VALUE的方法逻辑结构图解说明文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值