Redis(五) 缓存穿透 缓存击穿 缓存雪崩及其解决方案

本文详细介绍了缓存穿透、缓存击穿和缓存雪崩的解决方案,包括接口检验、缓存空值、布隆过滤器、加锁策略等,并探讨了布隆过滤器的工作原理及其在数据检索和去重中的应用。同时,提供了Java实现布隆过滤器的示例,并提到了Redis和Guava的布隆过滤器实现。
摘要由CSDN通过智能技术生成

一 缓存穿透

缓存不起作用,每次请求都直接访问数据库,称为缓存穿透。
场景:大量请求的key不在缓存中。或者甚至大量请求的key不在数据库中,因此也无法写入缓存,下一次请求仍然会直接访问数据库。流量大时数据库可能会宕机。出现这种情况往往是遭到了非法攻击。

解决方案

1 接口检验:可以在最外层先做一层校验:用户鉴权、数据合法性校验等。
2 缓存空值:简单粗暴的方法。当访问缓存和DB都没有查询到值时,可以将请求的key和空值写进缓存,但是设置较短的过期时间,该时间需要根据产品特性来设置。
3 布隆过滤器:使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。

二 缓存击穿

在缓存中的热点key过期的一瞬间,有大量访问这个key的请求打进来,缓存不起作用,直接到达数据库,称为缓存击穿。

解决方案

1 热点数据不过期:简单粗暴。

2 自动更新:redis支持查询某个key剩余有效时间,所以我们只需要设定一个时间差,比如3分钟,请求的时候查询有效时间,如果小于设定值(3分钟),那么刷新这个key的有效时间,刷新这个操作可以使用异步实现。
缺点:如果在key失效的前三分钟内,没有任何请求,还是会存在缓存击穿的问题,所以这种方式不是特别推荐。

3 定时刷新
一 写一个定时任务查询快要过期的key,更新数据,刷新有效时间。
缺点:比较消耗服务器性能。
二 利用延迟队列,将key存入缓存的同时发送一个延迟队列(按指定时间消费),时间小于缓存中key的过期时间,到了指定时间,消费者刷新key的有效时间再发送一个延迟队列,以此循环。
缺点:如果中间件宕机,还是会存在缓存击穿的问题。

4 加互斥锁(推荐):在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就进入阻塞,等到第一个线程将数据写入缓存后,其他线程从缓存中查询。具体操作:为数据库查询操作(添加缓存)加锁,所有线程都要阻塞等待第一个线程从数据库查询数据并存到缓存,即便是其他线程经过双重检验,可以再次从缓存中取数据,也要等待 锁 的释放。
它不需要额外的服务器开销(定时任务),也不需要额外的资源消耗(中间件),只是让线程串行。
首先:本地锁-JVM锁,比如synchronized锁/ReentrantReadWriteLock读写锁(尽管使用双重检验锁)。
本地锁在分布式场景下的问题:本地锁只能锁住当前服务的jvm进程,在分布式场景下,没法锁住所有服务的jvm进程,在某个微服务部署了多个服务时,可能会查询多次(次数不确定,与第一个请求将数据添加到缓存的时间有关)。
然后:Redis 分布式锁,加锁时要按 key 维度去加锁,占坑式抢锁,如果设置成功就执行逻辑,否则自旋等待锁的释放。

三 缓存雪崩

当缓存中有大面积的key在同一时间过期时,对于这些key的请求在这一瞬间都会直接到达数据库,造成雪崩。

解决方案

1 过期时间打散:设计随机过期时间,给原来过期时间加上一个随机值,使每一个key的过期时间分散开来,不会在同一时间全部失效。
2 热点数据不过期:将一些常用的数据设置成为永久有效。
3 加互斥锁:同缓存击穿。

四 布隆过滤器

使用场景:检索元素是否存在大集合中、去重。
注意:当布隆过滤器判断结果为存在时,有一定的误判概率。
结构:位数组+哈希函数

布隆过滤器的特点是:
判断不存在的key,一定不存在判断存在的key,有可能不存在,误判的概率和哈希函数的个数以及位数组的长度有关。

优点是空间利用率和查询效率较高。
缺点是返回结果存在误判,存放的数据不易删除。

降低这种误判率的思路:
增加位数组的长度,增加哈希函数的个数,进而降低哈希冲突概率。

写入过程

将给定元素经过hash计算写入到位数组中。
假设有一个位数组和三个hash函数,当我们要写入值时,过程如下:
key1经过三个hash函数计算得到位数组的下标索引为:0,2,5;然后,位数组对应的位置置1.
key2经过三个hash函数计算得到位数组的下标索引为:1,3,6;然后,位数组对应的位置置1.
key3经过三个hash函数计算得到位数组的下标索引为:0,4,7;然后,位数组对应的位置置1.
完成了数据的写入。

查询过程

判断给定元素是否在集合中。
比如,查询key4是否在集合中,首先hash函数计算key4对应位数组的下标为7,8,9,然后检查是否都为1。
本例中,下标不全为1,因此,布隆过滤器判定key4 一定不在集合中
再比如,查询key5是否在集合中,首先hash函数计算key5对应位数组的下标为1,3,5,然后检查是否都为1。
本例中,由于存入key1 key2时设置对应下标为1 3 5的比特位全为1,因此,布隆过滤器判定key5 在集合中
但是显然key5并不在集合中。因此,布隆过滤器有一定的误判概率,判断存在的key,有可能不存在。

布隆过滤器实现

参考:布隆过滤器 - 有代码示例
1 java手动实现:
对于java而言,需要一个位数组、几个哈希函数、add方法、contains方法。
位数组可以使用BitSet,自定义Hash类,实现hash方法,定义Hash数组。

2 利用Google开源的Guava自带的布隆过滤器:
mvn中添加依赖,创建BloomFilter时可以设置存储元素的最大数量以及容忍误判的概率。
调用put方法、mightContains方法
缺点是只能单机使用,容量扩展也比较困难。

3 redis中的布隆过滤器:
dockerhub中搜rebloom, redislabs/rebloom是redis的布隆过滤器的module
创建布隆过滤器 BF.RESERVE,可以设置容错率、过滤器容量、容量扩展系数。
调用BF.ADD(BF.MADD)、 BF.EXISTS(BF.MEXISTS) , M代表multi.

参考:
带你搞明白什么是缓存穿透、缓存击穿、缓存雪崩
缓存穿透、缓存击穿、缓存雪崩解决方案 - 有代码示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值