GeekIBLi

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

2021-07-28

缓存击穿,穿透和雪崩

背景

首先说一下为什么会写这片文章,因为这个对我来说是印象非常深刻的,那是还在实习的时候,当时接了一个任务(其实就是练手的),大致需求是写一个白名单,然后有一个功能对白名单开放。因为是新功能,需要在部分地区试点,如果没有问题才会放开到全国城市运行。就是这么一个小的功能,让当时的我,呵呵。我记得那是第一次使用Redis,根本不知道什么是缓存雪崩啊,缓存击穿啊,穿透呀这些,还有更可怕的缓存一致性问题(TODO 下期再说)。 当时团队十几位大佬review我的代码,哼。这就是为什么印象会深刻一些吧,关键那是我第一个review代码,还是第一次使用redis,整个review就像是十几个大佬在面试我。真是怀疑人生了。。我是废物,别笑。

1
2
3
4
5
6
7
8
9
10
// 大致伪代码
public boolean isPermit(String cityCode){
// 先查询缓存中是否存在
String tmpCity = getCache(cityCode);
if(StringUtil.isNotBlank(tmpCity)){
reture Boolean.TRUE;
}
// 缓存中没有在去查数据库
reture judgeForDb(cityCode);
}

我上面的例子其实就是典型的缓存穿透的问题。因为仅仅是开放了十几个城市来试点功能,所以大部分的查询都是缓存不命中的。

下面引出今天我们的三个关键词:

缓存穿透

要查询的数据,缓存中基本上没有,所以大概率情况下缓存是不命中的,而是去数据库中去查询数据,导致缓存相当于是一个摆设。如果Key不是热点访问还可以,如果是热点Key,而且并发量也会很大的情况下,绝大多数的请求都会打到数据库上,很容易造成数据库宕机。

解决方案

1、布隆过滤器进行校验,bloom filter典型应用场景(用户名是否存在,黑名单机制,单词错误检测)
2、缓存空值方法,这个网上也是说的比较多的,这种方式也是可以的

在此我说一下我的解决方案:

1、校验一个城市是否是城市白名单的城市时,直接查询缓存,存储的数据结构是使用的hash;
2、如果没有查到这个城市,则认为这个城市不在白名单中,因为我是先查的hash,然后在查询hash中的具体城市。如果hash的值都没有查到,那说明缓存失效了;
3、针对缓存失效的情况,可以再查DB来更新缓存,这个时候有人要较真了,会不会出线缓存击穿的情况呀,这个看具体场景,因为我的业务是不需要的,所以这里就直接查库更新缓存了,如果需要的话,那可以上缓存击穿的解决方案,或者看看上面两种解决方案有没有合适的,哈哈哈;
4、因为城市是在后台进行配置的,所以我是在增删改的操作时,保证了缓存数据的一致性的前提下,才选择相信缓存的。这是我当时的解决方案。

缓存击穿

某个热点Key在一段时间内失效了,此时有大量请求瞬时抵达,会严重增加数据库的压力。因为我们的缓存数据一般都是要设置过期时间的,当缓存失效时,会去查询数据库同时更新缓存数据。

解决方案

1、可以加锁,来保证只有第一个请求进来时达到数据库上,然后更新缓存,第二个请求进来是就会命中缓存,当然如果是分布式服务,那就需要使用分布式锁了。
2、合理设置缓存时间,可以将热点Key时间设置长一些,或者根据业务将失效时间设置在业务量比较小的波段,都是可以的。有的解决方案会不设置Key的过期时间,这个,看情况吧,不建议这样。 如果大量的key不设置过期时间,则长期占用内存也是不好的。

缓存雪崩

缓存雪崩顾名思义,就是大量Key在一段时间或者瞬时失效,或者Redis服务重启(所有Key失效,因为是存在内存中),从而导致大量请求打到数据库上,增加数据库压力

解决方案

1、将热点key设置不同波段的过期时间,把过期时间散列开。
2、也可以使用分布式锁来限制高并发的请求,和缓存击穿的解决方案同理。
3、对于Redis重启或宕机的问题,可以考虑集群部署,并保证数据的同步和一致性;

生活远不止眼前的苟且

Tags: Redis