天天看点

Redis-缓存雪崩、穿透、击穿缓存雪崩缓存穿透缓存击穿

缓存雪崩

在同一个时间点上,大量的缓存失效。缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。

解决方案:

    1、数据预热,在即将可能出现大量并发的时候reload,不同的key设置不同的失效时间,尽量把失效时间均匀。

    2、设置永不过期

    3、加锁排队(双重校验锁,缓存击穿解决方案)

    4、做二级缓存,A1缓存失效(失效时间设置短)原始数据,查询B1备用数据缓存(失效时间设置长),缺点可能B1数据会不准确

缓存穿透

确定不存在的key访问缓存(缓存和数据库都没有对应的value),容易被恶意请求,直接访问数据库

解决方案:

1、布隆过滤:把所有可能存在的key(参数),以哈希形式存储在一个足够大bitmap中,当访问的时候进行过滤,把一定不存在的请求拦截掉。

        基本原理及要点:对于原理来说很简单,位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,很明显这个过程并不保证查找的结果是100%正确的。同时也不支持删除一个已经插入的关键字,因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是counting Bloom filter,用一个counter数组代替位数组,就可以支持删除了。添加时增加计数器,删除时减少计数器。

        2、把null值放入缓存中,设置段时间失效(5分钟或者更短),采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存空对象会有两个问题:

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

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

缓存击穿

n同时并发访问同一个value,缓存没有value,n个请求都直接访问数据库。

(在高并发下,多线程同时查询同一个资源,如果缓存中没有这个资源,那么这些线程都会去数据库查找,对数据库造成极大压力,缓存失去存在的意义.打个比方,数据库是人,缓存是防弹衣,子弹是线程,本来防弹衣是防止子弹打到人身上的,但是当防弹衣里面没有防弹的物质时,子弹就会穿过它打到人身上. )

解决方法:

双重检验锁,访问缓存>判断空>加锁>访问缓存>判断空访问数据库

                                                                               > 判断不为空直接返回 

优化方案:使用互斥锁(当锁被人拿到,就拿不到锁)

static Lock reenLock = new ReentrantLock();

public List<String> getData04() throws InterruptedException {
List<String> result = new ArrayList<String>();
// 从缓存读取数据
result = getDataFromCache();
if (result.isEmpty()) {
if (reenLock.tryLock()) {//尝试拿锁
try {
System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
// 从数据库查询数据
result = getDataFromDB();
// 将查询到的数据写入缓存
setDataToCache(result);
} finally {
reenLock.unlock();// 释放锁
}

} else {
result = getDataFromCache();// 先查一下缓存
if (result.isEmpty()) {
System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
Thread.sleep(100);// 小憩一会儿
return getData04();// 重试
}
}
}
return result;
}