05-Redis 的穿透、雪崩、击穿

目录

一 缓存穿透

1.1 缓存穿透概念

1.2 缓存穿透发现

1.3 缓存穿透原因

1.4 缓存穿透解决

二 缓存雪崩

2.1 缓存雪崩概念

2.2 缓存雪崩解决

三 缓存击穿

3.1 缓存击穿概念

3.2 缓存击穿解决


一 缓存穿透

1.1 缓存穿透概念

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层拿不到数据就不写入缓存层。整个过程是:

  1. 缓存层没有命中
  2. 存储层没有命中,不将空结果写入缓存
  3. 返回空结果

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储层的意义。缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕机。

1.2 缓存穿透发现

通常可以在程序中分别统计总调用数、缓存层命中数、存储层命中数,如果发现大量存储层空命中,可能就是出现缓存穿透问题。

1.3 缓存穿透原因

造成缓存穿透的基本原因:

  • 自身业务代码或者数据出现问题。
  • 恶意攻击、爬虫等造成大量命中。

1.4 缓存穿透解决

  • 参数校验

比如对 id 做基础校验,id<=0 的直接拦截。

  • 缓存空对象

缓存空对象有两个问题:

第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题严重),比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除;

第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。比如,过期时间是5分钟,此时存储层添加了这个数据,那这段时间就会出现缓存层和存储层的数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

  • 布隆过滤器拦截

将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

08-BloomFilter解决Redis缓存穿透问题_新猿一马的博客-CSDN博客_bloomfilter 缓存击穿一 什么是 BloomFilter 布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。 布隆过滤器用于检索一个元素是在集合中的状态可以得到下面两种情况:1一定不在集合中;2可能存在集合中。二BloomFilter 原理 布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。2.1 布隆过滤器添加元素 1 将要添加的元素通过 K 个散列函数进行映射,得到一个位数组中的 K 个点; ...https://blog.csdn.net/jack1liu/article/details/107135903

二 缓存雪崩

2.1 缓存雪崩概念

缓存层由于某些原因不能提供服务,所有的请求都达到存储层,存储层的压力暴增,造成存储层宕机的情况。

2.2 缓存雪崩解决

  • 情况一:我们设置缓存时采用了相同的过期时间

解决:

  1. 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
  2. 不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
  3. 做二级缓存,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期。
  • 情况二:缓存服务器宕机

解决:

  1. 保证缓存层服务高可用性。
  2. 依赖隔离组件为后端限流并降级。
  3. 针对线上的流量,进行演练。

三 缓存击穿

3.1 缓存击穿概念

  • 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
  • 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的 SQL、多次 IO、多个依赖等。

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

缓存击穿针对的是某一个热 key ;缓存雪崩针对的是很多 key。

3.2 缓存击穿解决

我们的目标是:尽量少的线程构建缓存(甚至是一个) + 数据一致性 + 较少的潜在危险。

  • 使用互斥锁(mutex key):

这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据即可。

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并回设到缓存了,这时候重试获取缓存值即可  
                      Thread.sleep(50);  
                      get(key);  //重试  
              }  
          } else {  
              return value;        
          }  
 }  
  • 永远不过期

这里的“永远不过期”包含两层意思:

    (1) 从 redis 上看,确实没有设置过期时间,这就保证了,不会出现热点 key 过期问题,也就是“物理”不过期。

    (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在 key 对应的 value 里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期。

两种方案的比较:

  • 互斥锁:方案思路简单,存在一定的隐患,如果构建缓存过程中出现问题或者时间较长,可能会出现死锁或者线程池阻塞风险,但是这种方法能够较好的降低后端存储负载,并在一致性上做的比较好。
  • 永远不过期:这种方案没有设置过期时间,实际上不存在热点 key 产生的一系列危害,会出现数据不一致的情况,同时代码复杂度会增加。

参考文档:http://download.redis.io/redis-stable/redis.conf

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值