superleeyom / blog

:bookmark: 个人博客仓库,用于记录一些幼稚的想法和脑残的瞬间,欢迎 star、watch,该仓库为个人博客,请不要提 issue ,该仓库后端参考了 @yihong0618 的 gitblog 项目,前端参考了@LoeiFy 的 Mirror 项目,感谢!

Home Page:https://blog.leeyom.top

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关于Redis缓存穿透、缓存雪崩、缓存击穿问题探究

superleeyom opened this issue · comments

缓存穿透

拿一个不存在的 key 去查询数据,如果缓存里面查询不到,就会去数据库里面查询,如果有人恶意拿不存在的 key 疯狂请求,会把数据库压垮,这就是缓存穿透,下面用一段伪代码:

List<String> cacheList = redis.get(key);
if(CollUtil.isEmpty(cacheList)){
	List<String> list = mysql.getList(key);
	if(CollUtil.isNotEmpty(list)){
		redis.set(key,list,3 * 60);
	}
  return list;
}
return cacheList;

通常来说,解决缓存穿透有两种方式:

  • 为不存在的 key 设置空值

    • 伪代码如下:

      List<String> cacheList = redis.get(key);
      if(CollUtil.isEmpty(cacheList)){
      	// 不管有没有在数据库中查询到数据,都给key设置值   
      	List<String> list = mysql.getList(key);
      	redis.set(key,list,3 * 60);
        return list;
      }
      return cacheList;
  • 使用布隆过滤器

缓存雪崩

在某个时间点,大批的 key 出现过期,导致所有的请求全部打到数据库上,把数据库压垮,这种就是缓存雪崩,通常解决缓存雪崩有如下的几种方案:

  • 永不过期:设置 key 永不过期,但是这种会占用服务器挺多内存;
  • 过期时间错开:比如这个 key 设置的过期时间是 5 分钟,那另外一个 key 设置的过期时间则为 7 分钟,把过期时间错开,防止在某个时间点同时失效
  • 多缓存结合:在数据库和 Redis 再加一层缓存,比如 Memcache,这样的话,缓存一旦过期,Memcache 里面还可以顶一会儿;
  • 采购第三方的 Redis 服务:现在很多云平台都有推出 Redis 服务,有单机的,集群的,可以根据自己的使用场景去采购,当然人家也帮你处理好了这些问题,有钱啥问题都能解决。

缓存击穿

当前的某个热点 key 缓存过期,同一时间,有大量的请求同时来访问这个 key,导致所有的请求都打到数据库上去了,把数据库压垮。那通常遇到这种问题的话,一般就是使用排斥锁,当然也有一种粗暴的办法,就是设置永不过期,但是这种粗暴方式,大多数情况下不适用。

关于排斥锁,可以这样理解,第一个请求达到请求 key 发现缓存里面没有,允许它去数据库查询,同时加锁,这样第二个请求,第三个请求…都会被锁阻塞到当前,当第一个请求从数据库查询到数据后,将数据缓存到 Redis 中,然后释放锁,这样第二个,第三个请求...,就直接可以从缓存中拿数据,就不会再打到数据库,这样就减少了数据库的并发压力。

String get(String key) {  
   String value = redis.get(key);  
   if (value  == null) {  
    if (redis.setnx(key_mutex, "1")) {  
        // 给锁设置一个过期时间,防止持有锁的人挂了,导致锁不能释放
        redis.expire(key_mutex, 3 * 60)  
        // 从DB中查询数据并缓存
        value = db.get(key);  
        redis.set(key, value);
      	// 释放锁
        redis.delete(key_mutex);
      	return value;
    } else {  
        //其他线程休息100毫秒后重试  
        Thread.sleep(100);  
        get(key);  
    }  
  }
  return value;
}

其实对于这些热点 key,最好还是有个独立的服务,去定时的刷新缓存,这样的话,很大的程度上可以避免这种问题。