写BUG的派大星

Patrick Star

  • 首页
  • 归档

  • 搜索
设计模式 Gis Kafka Druid 微信小程序 Java 开源项目源码 物体识别 机器学习 Mybatis 微服务 Feign OpenVPN CSS Streamsets CDH SpringCloud SpringBoot maven 分布式 Shell Tree Linux js WebSocket 多线程 集群 Hadoop 大数据 JDK ElasticSearch MySQL 数据库 Redis Http Nginx

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

发表于 2020-03-03 | 分类于 数据库 | 0 | 阅读次数 1023

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

缓存击穿

情形描述

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

使用互斥锁(mutex key)

在缓存失效的时间点(拿到的值为空),不立即从数据库中取数据,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。

SETNX 是 “Set If Not Exists”的缩写,只有不存在的时候才会进行设置,可以利用它来实现锁的效果。(2.6.1之前未实现setnx的过期期间)


2.6.1之前

String get(String key) {  
   String value = redis.get(key);   
   if (value  == null) {  
    if (redis.setnx(key_mutex, "1")) {  
        // 3 min timeout to avoid mutex holder crash  
        redis.expire(key_mutex, 3 * 60)  
        value = db.get(key);  
        redis.set(key, value);  
        redis.delete(key_mutex);  
    } else {  
        //其他线程休息50毫秒后重试  
        Thread.sleep(50);  
        get(key);  
    }  
  }  
}

2.6.1之后

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
		  if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                      get(key);  //重试
              }
          } else {
              return value;      
          }
 }

“提前”使用互斥锁

在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。

设置永不过期

在物理层面:将热点key的有效期设置为不过期。在逻辑上呢,将过期时间放在key对应的value中,如果发现即将过期,通过后台的线程进行缓存的重新构建。

这种方法的有点是性能比较好,缺点就是在构建缓存的过程中,其他线程会访问到旧数据。这个大部分情况下是可以忍受的。

资源保护

采用netflix的hystrix,可以做资源的隔离保护主线程池,如果把这个应用到缓存的构建也未尝不可。

方法总结

解决方案优点缺点
简单分布式互斥锁(mutex key)1. 思路简单 2. 保证一致性1. 代码复杂度增大 2. 存在死锁的风险 3. 存在线程池阻塞的风险
“提前”使用互斥锁1. 保证一致性同上
不过期1. 异步构建缓存,不会阻塞线程池1. 不保证一致性。 2. 代码复杂度增大(每个value都要维护一个timekey)。 3. 占用一定的内存空间(每个value都要维护一个timekey)。
资源隔离组件hystrix1. hystrix技术成熟,有效保证后端。 2. hystrix监控强大。1. 部分访问存在降级策略。

缓存雪崩

情形

有大面积的缓存在同一时间失效,请求全部被转发到数据库中,导致这一瞬间数据库的压力陡增,造成雪崩。

解决方案

将缓存失效时间分散

如将缓存的有效期设置为一个2-5分钟的随机数。这样就会让缓存的失效时间错开,不会存在同一时间里缓存大面积失效的问题。

加锁或队列

将用户的请求进行加锁排队来分散数据库的压力,但是本质上并没有提高系统的吞吐量,而且可能造成阻塞,导致用户等待超时。而如果是在分布式环境下,还要解决分布式锁的问题,所以在高并发场景下很少使用。

二级缓存或者双缓存

A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

缓存穿透

情形

大量的请求都查询一个不存在的数据,如id=-1等等,由于数据库中本就没有数据,自然不存在缓存命中这么一说,如此一来,所有的流量都进入到了数据库中,如果被利用这一点使用一个不存在的key进行频繁的攻击,可能会导致数据库挂掉等风险。

解决方案

将空数据写入缓存

即使查询结果为空,也将其写入缓存中,但是过期时间很短。用这种方法可以避免同一时间数据库的负载过大。

采用布隆过滤器

将所有可能存在的数据放在一个bitmap中,使用这个bitmap对不存在的数据进行拦截。

  • 本文作者: Patrick
  • 本文链接: https://www.write1bug.cn/archives/缓存击穿缓存穿透和缓存雪崩
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 设计模式 # Gis # Kafka # Druid # 微信小程序 # Java # 开源项目源码 # 物体识别 # 机器学习 # Mybatis # 微服务 # Feign # OpenVPN # CSS # Streamsets # CDH # SpringCloud # SpringBoot # maven # 分布式 # Shell # Tree # Linux # js # WebSocket # 多线程 # 集群 # Hadoop # 大数据 # JDK # ElasticSearch # MySQL # 数据库 # Redis # Http # Nginx
关于红黑树
CentOS8搭建分布式Hadoop3.2.1环境(hdfs+yarn)
  • 文章目录
  • 站点概览
Patrick

Patrick

不是在改BUG,就是在写BUG。

52 日志
9 分类
36 标签
RSS
E-mail
Creative Commons
© 2018 — 2023 Patrick
人生如逆旅|我亦是行人
鲁ICP备18043140号-1