首页 > 技术文章 > 缓存穿透与缓存雪崩

tingyugetc 2018-07-27 00:05 原文

缓存穿透与缓存雪崩

缓存穿透

何为缓存穿透

何为缓存穿透,顾名思义就是缓存被穿透了,当请求去查询一个根本不存在的数据时,请求就不会被缓存拦截住而是直接打到了DB中。

在请求并发量较低的情况下出现了缓存穿透不会出现大的问题,DB完全能够承受住相应的并发量。但在大量请求发生时,由于请求的数据根本就不存在,一般缓存也不会去缓存不存在的数据,在这种情况下就出现了请求穿透全部打到DB了上,此时会对最底层的DB造成极大的压力,影响系统稳定性。

造成缓存穿透的原因一般有两种:一种是由于代码bug或者系统数据出现问题,本该缓存到缓存中的数据不见了;另一种就是恶意请求。

缓存穿透解决方案

要解决缓存穿透带来的系统问题,就需要从穿透的根本入手:一种方案是即便请求的数据为空,空数据也会进入缓存;另一种是对请求进行拦截。

缓存空数据

当DB中查询不到数据时,依然将空数据缓存到缓存层中,之后在请求该数据时直接从缓存中去获取,通过这种方法拦截住请求,降低DB压力。

但是缓存空数据可能会出现比较严重的问题:

  1. 空数据进入缓存,意味着可能会出现大量的value为空的key值,需要占用更多的内存空间,如果不及时进行释放的话,会发生资源被无用的key占用;考虑到这个问题比较有效的方案是针对空数据设置一个比较短的过期时间,让其自动过期。
  2. 缓存层和DB层的数据会有一段 时间窗口 的不一致,可能会对业务有一定的影响。例如过期时间设置为1分钟,如果此时DB中添加了这个数据,那么在缓存过期的这段时间内就会出现缓存和DB数据不一致的问题;考虑到这个问题可以利用异步消息或者其他方式去消除缓存中的空数据。

下面给出简单的缓存空数据的代码:

String get(String key) {
	String cacheValue = cache.get(key);
	// 缓存为空
	if (StringUtils.isBlank(cacheValue)) {
		// 从DB中获取
		String storeValue = store.get(key);
		cache.set(key, storeValue);
		if (Objects.isNull(storeValue)) {
			// 如果DB中的数据为空,则设置过期时间为60秒
			cache.expire(key, 60);
		}
		return storeValue;
	} else {
		// 缓存不为空
		return cacheValue;
	}
}

此处还有一个小问题就是当大量请求通过过来时,空缓存尚未建立起来,大量的请求依然会打到DB上,造成DB的压力,防止这个问题可以使用互斥锁,保证对每个key的获取最终只会访问DB一次。

对请求进行拦截

这个方法与上面的缓存空数据的方法完全不同,通过对不可能存在的key进行拦截过滤。这就需要在缓存层之前放置一个拦截器,拦截器来负责允许访问哪些key的请求可以到达缓存层。

实现拦截器的话同样有两种途径:一种是拦截不存在的key,另外一种是放行通过存在的key。对比这两种方法,我们会发现相对于非法key的海量情况,有限量的合法key是可枚举出来的。这种通过将合法key提前保存起来实现一个过滤器的方式就是布隆过滤器

关于布隆过滤器的可以参考Github上的一个开源库:https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter

由于过滤器需要提前的保存合法key,故而这种方法一般适用于数据相对稳定,对实时性要求不高的应用场景中,并且实现方式相对,缓存空间占用少。

两种方案的对比
使用场景 维护成本
缓存空数据 缓存数据命中率不高、数据实时性比较高 代码维护简单、占用缓存空间比较大、数据可能会不一致
过滤器 缓存数据命中率不高、数据实时性不高 代码维护复杂、占用缓存空间较少

缓存雪崩

何为缓存雪崩

缓存雪崩就是当缓存崩掉造成的影响就像雪崩一样严重,这里说的缓存崩掉不仅仅是指缓存服务宕机,也有可能是指缓存服务重启或者缓存中大量数据同时过期等造成的缓存不可用。由于特殊原因造成了缓存不可用,这是所有的请求都会直接打到DB上,对DB造成比较大的压力。

要预防和解决缓存雪崩带来的问题,我们除了提高后端的负载能力,还需要保证缓存服务的可用性以及具备可以及时对请求实施限流降级的能力。

  1. 保证缓存的高可用。为了防止单机的缓存服务的大概率不稳定性,我们可以Codis集群等分布式缓存方案保证缓存的高可用,这样即使个别机器节点宕掉,也不会影响到整个服务的可用性。
  2. 通过中间件对请求进行限流并降级。在整个后端服务中,无论是DB还是缓存抑或是服务器都可被视为同等地位的资源。当出现一个资源不可用时,都会导致请求阻塞在该资源上,造成系统不可用。在系统压力剧增的情况下,适当的有策略的对服务进行降级已保证整个系统不会挂在某一处。同样的,此处也可以采用服务隔离与服务熔断的方案。
总结

此处没有总结,散了散了

推荐阅读