帮你解读什么是Redis缓存穿透、缓存击穿和缓存雪崩(包含解决方案)


前言
web

做为一种非关系型数据库,redis也老是免不了有各类各样的问题,这篇文章主要是针对其中三个问题进行讲解:缓存穿透、缓存击穿和缓存雪崩,并给出一些解决方案。面试

1、缓存穿透

一、概念redis

缓存穿透是指查询一个数据库必定不存在的数据。正常的使用缓存流程大体是,数据查询先进行缓存查询,若是key不存在或者key已通过期,再对数据库进行查询,并把查询到的对象,放进缓存。若是数据库查询对象为空,则不放进缓存。算法

这里须要注意缓存击穿的区别,缓存击穿,缓存击穿是指缓存中没有但数据库中有的数据,而且某一个key很是热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间(通常是缓存时间到期),持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。数据库

为了不缓存穿透其实有不少种解决方案。下面介绍几种后端

二、解决方案数组

(1)布隆过滤器缓存

布隆过滤器是一个bit向量或者bit,若是咱们要映射一个值到布隆过滤器中,咱们使用多个不一样的哈希函数生成多个哈希值,并将每一个生成的哈希值指向的bit位设置为1,以下baidu一词设置了三个位置为1。微信

原理:对一个key进行k个hash算法获取k个值,在比特数组中将这k个值散列后设定为1,而后查的时候若是特定的这几个位置都为1,那么布隆过滤器判断该key存在。并发

“tencent”一词,对应的状况

能够看到,不一样的词对应的bit位置可能相同,当词不少的状况时,可能大部分bit位置都是1,这时查询taobao可能对应的位置都为1,只能说明taobao一词可能存在,不是必定存在的,这时1就被覆盖了,这就是布隆过滤器的误判若是它说不存在那确定不存在,若是它说存在,那数据有可能实际不存在。

Redis的bitmap只支持2^32大小,对应到内存也就是512MB,误判率万分之一,能够放下2亿左右的数据,性能高,空间占用率及小,省去了大量无效的数据库链接。

所以咱们能够经过布隆过滤器,将Redis缓存穿透控制在一个可容范围内。

使用布隆过滤器:
导入依赖

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>

代码:

public class Test {

private static int size = 1000000;//预计要插入多少数据

private static double fpp = 0.01;//指望的误判率

private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

public static void main(String[] args) {
//插入数据
for (int i = 0; i < 1000000; i++) {
bloomFilter.put(i);
}
int count = 0;
for (int i = 1000000; i < 2000000; i++) {
if (bloomFilter.mightContain(i)) {
count++;
System.out.println(i + "误判了");
}
}
System.out.println("总共的误判数:" + count);
}
}

应用:

@Cacheable(value="key1")
public String get(String key) {
String value = redis.get(key);
// redis中不存在该缓存
if (value == null) {
//布隆过滤器也没有,直接返回
if(!bloomfilter.mightContain(key)){
return null;
}else{
//布隆过滤器中能查到,不表明必定有,查出来放入redis,一样也能够避免缓存穿透
value = db.get(key);
redis.set(key, value);
}
}
return value;
}

(2)、缓存空对象
当存储层不命中后,即便返回的空对象也将其缓存起来,同时会设置一个过时时间,以后再访问这个数据将会从缓存中获取,保护了后端数据源。

可是这种方法会存在两个问题

 ● 若是空值可以被缓存起来,这就意味着缓存须要更多的空间存储更多的键,由于这当中可能会有不少的空值的键;

 ● 即便对空值设置了过时时间,仍是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于须要保持一致性的业务会有影响。

2、缓存雪崩

(1)、概念
缓存雪崩是指缓存中大批量数据到过时时间,而查询数据量巨大,引发数据库压力过大甚至down机。和缓存击穿不一样的是,缓存击穿指并发查同一条数据,缓存雪崩是不一样数据都过时了,不少数据都查不到从而查数据库。

产生雪崩的缘由之一,假如立刻就要到双十一零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过时了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

博主在作电商项目的时候,通常有三种方法:
(1)采起不一样分类商品,缓存不一样周期。在同一分类中的商品,加上一个随机因子。这样能尽量分散缓存过时时间,并且,热门类目的商品缓存时间长一些,冷门类目的商品缓存时间短一些,也能节省缓存服务的资源。
(2)若是缓存数据库是分布式部署,将 热点数据均匀分布在不一样的缓存数据库中。
(3)设置热点数据永远不过时。

(4) 使用加锁限流的方式


3、缓存击穿

(1)概念
缓存击穿,是指缓存中没有但数据库中有的数据,而且某一个key很是热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间(通常是缓存时间到期),持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

(2)解决方案

 ● 设置热点数据永远不过时。

 ● 使用互斥锁(mutex key)
  业界比较经常使用的作法,是使用mutex。简单地来讲,就是在缓存失效的时候(判断拿出来的值为空),不是当即去load db,而是先使用缓存工具的某些带成功操做返回值的操做(好比Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操做返回成功时,再进行load db的操做并回设缓存;不然,就重试整个get缓存的方法。

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,能够利用它来实现锁的效果。

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;
}
}
本文来自个人博客: https://blog.csdn.net/qq_37989738

推荐阅读

一、文末赠书 | 面试官居然又问我volatile

二、史上最全的Jackson框架使用教程

三、逐行解读HashMap源码【终章】

四、原创 | 万万没想到,HashMap默认容量的选择,居然背后有这么多思考!?

扫描二维码

获取更多精彩

Java技术大联盟

有收获,就在看 

本文分享自微信公众号 - Java技术大联盟(jingdakunye520)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索