缓存雪崩
缓存雪崩就是我们在设置缓存的时候,都会给它设置一个超时时间,为什么一定要设置超时时间呢,因为现阶段分布式缓存里面,不管你用任何解决方案都是很难做到跟数据库保持强一致的,就是操作分布式缓存跟操作数据库,这两个操作,很难保证它的原子性,所以我们就会设置一个超时时间,去做一个兜底,避免缓存里面的数据,跟数据库长期保持不一致。
很多系统在第一次部署的时候,需要去做一个缓存预热,就是把大量的key的超时时间设置为同样的一个过期时间,那如果刚好到了这个过期的时间点,大量的请求就直接都打数据库了,那这个时候数据库就非常有可能被打挂,这就是缓存雪崩的场景。所以刚才提到一个关键点就是他们缓存失效的时间是同一个时间点。

解决方案:我们在设置缓存失效的时候,把他的时间做一个离散的处理,代码层面的处理呢,就是一个固定的时间,再加一个随机数去保证他的过期时间尽量分散在不同的时间范围。
缓存穿透
缓存穿透的问题,主要是用在抵抗一些灰产的时候,这个是尤其需要去考虑的。例如我们之前在线上,当时我们经常会拿一些商品出来搞促销,我们请求头里面会带一个活动ID的字段,但这个字段的话它是不连续的,所以感觉像薅羊毛的这些程序员,就不停地调我们的接口,通过这种商品加活动ID,然后自己拼装了很多参数不停的来攻击,不停的来查询我们的系统,看一下这个商品还有哪些什么其他的活动。显而易见他拼凑出来很多数据我们在缓存跟数据库里面都是不存在的,所以说他查询缓存不存在,就直接去查数据库,导致我们的数据库的压力非常大,这就是缓存穿透的场景。
在业界也有比较成熟的解决方案,第一种方案呢就是缓存一个null值,但是呢这里也有另外一个问题,就是大量的null值可能会把这个内存空间给撑爆。

第二种方案就是用布隆过滤器 ,关于布隆过滤器的介绍使用,在我另一篇文章里《通俗易懂讲布隆过滤器》,这里不再做赘述。
那么其实布隆过滤器也有一个比较大的问题,就是怎么去删除一个数据,因为布隆过滤器,它是类似于一个简单的单向hash,所以你没办法因为数据库里面删了某一个数据,就去把布隆过滤器里面的数据给删掉,因为完全有可能另外一个case它也是占用了同样的哈希位,所以关于布隆过滤器的删除也是一个难点。
还有怎么去保证布隆过滤器的操作跟你数据库操作的一个原子性。在写入跟删除的过程当中,如果期间有一次部署,那就可能导致部分数据,布隆过滤器跟数据库的数据不一致,如果说是单纯的缓存,我们还能通过设置一个超时时间去做一个兜底,那布隆过滤器,我们一般是没办法去设超时时间的,所以布隆过滤器跟数据库之间操作的一个原则性,这个就很难去保证。
缓存击穿
缓存击穿这一般出现在某一个热key的场景,就是这个key失效了之后,大量的请求都去请求数据库导致数据库的压力特别大。像我之前在线上就确实遇到过一个慢SQL,它平时是不会去执行的,因为都是有缓存的,那某一天在业务高峰期,突然这个key失效了,它本身又是一个热key,然后大量的请求查不到缓存,都去请求数据库了,然后这个MySQL就把系统给拖死了,当时是差点导致整个系统的雪崩。
解决方案本质上就是,我们把DB的执行当成一个临界资源,那这中间肯定就要去加一个锁,就是采用分布式锁 ,就是缓存失效了之后,所有请求先去抢一个分布式锁,抢成功了再去请求数据库,这样就能够保证整个集群,它只有一个请求去打到数据库。
另外值得一提的就是,这里既然加了锁,那其他没抢到锁的这些请求怎么办。这里有两种处理方案,第一种就是拿不到锁就阻塞等待在这里,等待一段时间我们再去取缓存,但是在生产环境,我们一般是不会这样去用的,因为这里存在一个非常大的风险,就是我们系统既然用到了缓存,那这个请求量应该是比较大的,那你要阻塞在这里,那阻塞多久呢,那阻塞的这个时间会不会大量请求夯在这里,把整个RPC的连接池给打爆,针对这种情况我们更多的是采取返回一个默认值的方式,而不是让它直接阻塞等待在这里。
那我们怎么去取这个默认值呢,这其实也有一个比较巧妙的解决方案,就是你在设置这个key value的时候,你在value里面加一个字段,这个字段field就是逻辑过期的时间,代码每次查询到缓存之后,先去判断一下这个时间,如果这个时间已经过期了,再去抢这个分布式锁,然后再去查询数据库,把这个缓存给更新掉。
那如果拿不到分布式锁呢,就直接返回这个逻辑上过期的这个数据,所以这个默认值可以这样去设计。你会发现这其实也是一种弱一致的解决方案,同时也比较巧妙的解决了这个缓存击穿的问题。

欢迎收看,点赞,关注,一键三连~