缓存击穿、穿透、雪崩分析

缓存击穿、穿透、雪崩是在使用缓存的时候经常需要考虑的三个问题。我们先了解下概念:

首先这三种情况都是缓存没有命中(缓存中没有相应的值)。

缓存击穿:缓存击穿表示数据库中也没有相应的值。

缓存穿透:数据库中有相应的值,需要重建缓存。

缓存雪崩: 大量的缓存失效,导致大量的key发生缓存穿透。

缓存击穿

分析

我们先考虑下缓存击穿会导致什么样的问题,缓存击穿的时候请求会落在数据库之上。通常情况下这并没有问题,不过想想一下这个情况, 如果传入的参数为-1(当然参数校验是一种解决办法,这里就是举个数据库不存在数据的例子),那么数据库中是不存在值的,而这个请求如果可以不抵达数据库,那就可以优化数据库的压力。甚至于如果有恶意攻击,也可以利用这个漏洞伪造大量不存在的key对数据库进行攻击。

解决方案

缓存空值

我们可以将空值也进行缓存,伪代码如下:

public Article findArticleById(Long articleId) {
		Object object = redisTemplate.opsForValue().get(String.valueOf(articleId));
    if (object != null) { // 缓存命中
	  		return Article(object);
    }
    // 查询数据库
    Article article = ArticleMapper.selectByPrimaryKey(articleId);
    if (article != null) { // 数据存在加入缓存
    		redisTemplate.opsForValue().set(String.valueOf(articleId), article, 60, TimeUnit.MINUTES);  
    } else { // 数据不存在,缓存空值
      	redisTemplate.opsForValue().set(String.valueOf(articleId), null, 60, TimeUnit.SECONDS);
    }
}

考虑几个问题:

  1. 空值缓存的过期时间如何选择?
    因为空值会耗费redis的key,所以对于控制的缓存时间,需要比较短,例子中设置了60s。具体可以根据业务需求设置。

  2. 空值缓存会导致数据库缓存不一致问题吗?
    如果数据库新建数据之后有删除缓存key的操作,那是不会有不一致情况的。如果没有,那么不一致的情况最多也就是空值缓存过期时间。

  3. 如果是恶意攻击,伪造大量不存在的key怎么办?
    首先业务上尽量要对key进行合法性判定。但是依旧有很多不存在的key穿过缓存抵达数据库的话,那这种方案依旧无法防范恶意攻击,需要使用布隆过滤器方案。

Bloom Filter

在缓存前使用Bloom Filter来挡掉不存在的key,直接将请求返回。

Bloom Filter的详细分析,参考:布隆过滤器

缓存穿透

通常情况下缓存穿透没有什么问题,新建缓存就可以了。依旧考虑大并发的情况下,如果某个key一直扛着大并发,比如某个爆款。这时如果缓存失效,那么重建缓存的过程之中大量的请求直接打到数据库上。

参考网络上的文章,基本使用互斥锁(可以用redis setnx),让线程等待缓存新建。

但实际情况,能达到这种量级的应用就不多。另外既然已经是爆款了,缓存就不应当失效,甚至爆款对redis也造成过大压力,需要按照一定的机制直接建立堆缓存。

大道至简。

缓存雪崩

缓存雪崩是指在某一个时间段,缓存集中过期失效。

产生的原因之一是在载入缓存的时候设置的过期时间一致,导致缓存中大量的键集中过期。这种情况下我们可以考虑对缓存时间设置一个随机值来分散缓存过期时间。同时考虑到热门与冷门的缓存(多级缓存)。

伪代码如下:

Article article = ArticleMapper.selectByPrimaryKey(articleId);
if (article != null) { // 数据存在加入缓存
		long time = 0;
		if (isHot(article)) {
      	// 热门数据缓存时间长
    		time = 3600 + random(600);  
    } else {
    		time = 600 + random(600);
    }
		redisTemplate.opsForValue().set(String.valueOf(articleId), article, time, TimeUnit.SECONDS);  
} else { // 数据不存在,缓存空值
		redisTemplate.opsForValue().set(String.valueOf(articleId), null, 60, TimeUnit.SECONDS);
}

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。

所以需要对缓存进行高可用设计,来防止缓存服务器宕机。

posted @ 2020-08-04 14:30  江舟  阅读(288)  评论(0)    收藏  举报