redis入门系列(八)

212 阅读5分钟

常见面试问题

  • 什么是缓存穿透?解决方案?
  • 什么是缓存击穿?解决方案?
  • 什么是缓存雪崩?解决方案?

缓存穿透

缓存穿透其实就是访问一个缓存不存在的key,请求会穿透到数据库,数据库也不存在,例如请求id为-1的数据,这种无效的请求如果大量访问会导致系统压力剧增。

解决方案

1、接口层进行校验,过滤掉无效的数据。

2、缓存空值,当数据库和缓存都查询不到的情况,我们可以给这个key缓存一个空值,并同时设置一个较短的过期时间,让空值能在较短的时间内过期掉。

弊端:缓存系统中存了大量的空值,浪费缓存的存储空间,如果缓存空间被占满了,还会还会剔除掉一些已经被缓存的用户信息反而会造成缓存命中率的下降。

3、使用布隆过滤器:将数据库中所有的查询条件,放入布隆过滤器中,当一个查询请求过来时,先经过布隆过滤器进行查,如果判断请求查询值存在,则继续查;如果判断请求查询不存在,直接丢弃。

布隆过滤器

布隆过滤器它的作用是它能迅速判断一个元素是否在集合中,特点是高效插入和查询,相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

原理:通常我们判断一个元素是否存在,我们可能使用map这个结构,它可以在O(1)的时间复杂度内返回结果,效率非常高。但是HashMap的实现也有缺点,例如存储容量占比高,考虑到负载因子的存在,通常空间是不能被用满的,而一旦你的值很多例如上亿的时候,那 HashMap 占据的内存大小就变得很可观了。布隆过滤器是一个 bit 向量或者说 bit 数组。

当插入一个元素时,我们使用多个不同的哈希函数生成多个哈希值,并对每个生成的哈希值指向的 bit 位置 1,例如针对值 “hello” 和三个不同的哈希函数分别生成了哈希值 1、3、6。

此时我们再存入一个“world”,它通过三个hash函数生成的哈希值分别为2、3、5

如果我们要查询一个值,例如查询“haha”,它通过三个hash函数生成的哈希值分别为2、3、4,这个时候我们发现4对应的bit位为0,那么我可以确定“haha”一定不存在集合里面,但是我们查询“hello”是否存在时,我们不能说它一定存在,那这个是不能确定的,因为有可能存在哈希冲突。很显然,过小的布隆过滤器很快所有的bit位均为1,那么查询任何值都会返回“可能存在”,就起不到过滤的目的了。布隆过滤器的长度会直接影响误报率,布隆过滤器越长其误报率越小;另外,哈希函数的个数也需要权衡,个数越多则布隆过滤器bit位置位1的速度越快,且布隆过滤器的效率越低;但是如果太少的话,那我们的误报率会变高。

应用场景:

1、推荐去重

2、网页爬虫对URL的去重,避免爬取相同的URL地址。

3、反垃圾邮件,从数十亿个垃圾邮件列表中判断某邮箱是否垃圾邮箱。

4、缓存穿透,将所有可能存在的数据缓存放到布隆过滤器中。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间巨增。

解决方案

1、设置热点的数据永不过期

2、定时更新缓存,当缓存快要过期的时候,定时将数据库数据刷新到缓存。

3、互斥锁,互斥锁简单来说就是在Redis中根据key获得的value值为空时,先锁上,然后从数据库加载,加载完毕,释放锁。若其他线程也在请求该key时,发现获取锁失败,则睡眠一段时间(比如100ms)后重试。

缓存雪崩

Redis中缓存的数据大面积同时失效,或者Redis宕机,从而会导致大量请求直接到数据库,压垮数据库。

解决方案

1、设置过期时间均匀分布,我们可以在设置有效期时增加随机值。

2、数据预热,对于即将来临的大量请求,我们可以提前将数据提前缓存在Redis中,并设置不同的过期时间。

3、限流降级,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

4、保证Redis服务高可用,通过Redis的哨兵模式和集群模式,为防止Redis集群单节点故障,可以通过这两种模式实现高可用。