缓存现在的项目都会用到,可能啥都不想就先上个缓存吧,简单方便,但是缓存在没有命中或者集体失效的情况下就会引起缓存穿透和缓存雪崩的问题,下面就简单说下缓存穿透和雪崩的预防的一些常用方法

缓存穿透

是指查询一个一定不存在的数据,这个时候缓存无法命中,于是直接去请求数据库,查询不到数据不写入缓存,最终导致每次查询这个不存在的数据时候都回去查询数据库。最终的结果就是如果请求量很大会导致数据库挂掉。

缓存穿透解决办法

1. 缓存空值

这种方法简单粗暴,不管有没有查询到数据,都将结果缓存了,这样下次查询缓存就可以命中了。但是需要注意空值的缓存最好设置一个不长的过期时间,例如 2 分钟之类的。

这个方法有一些注意点:

  • 数据的不一致现象:如果数据已经存在了,但是由于缓存没有过期,无法更新最新的数据,会导致这段时间缓存数据和存储数据不一致。当然可以用一些其他的方式避免这样的问题,例如利用消息系统清除缓存等…
  • 占用空间:其实这也不算啥问题,如果你的内存很多的话,但是就怕故意攻击导致大量缓存无用的键,占用大量内存。这样的问题在设置了缓存有效期的情况下,也不算很严重的问题,可以将缓存时间设置短一点…

2. 布隆过滤

布隆过滤可以确定一个键不存在,也就可直接丢掉这样的不合法请求,这样就可以大大减少缓存无法命中的情况

所以将可能查询的参数hash到足够大的bitmap中存储,在控制器中进行校验,一个一定不存在的数据会被拦截掉,减少对存储系统的查询压力

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

缓存雪崩

缓存集中在一段时间内失效,导致查询全部落到数据库,造成了缓存雪崩

缓存雪崩解决办法

对于这种情况,都需要针对业务和用户行为分析,尽量让缓存失效时间均匀分布,避免同一时间大量缓存失效

1. 加锁排队限流

限流算法:

  • 计数
  • 滑动窗口
  • 令牌桶
  • 漏桶

在缓存失效后可以通过加锁或者队列控制读数据库或者写缓存的线程数量,例如对某个key只允许一个线程查询数据和写缓存。当然大部分时候可能需要一个分布式锁,真的挺麻烦的,感觉在高并发下好像没啥卵用…

2. 缓存预热

可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

3. 双缓存策略

多花费点空间,保存两份缓存,一份有过期时间,一份没有过期时间,在缓存过期的时候直接查询没有过期时间的缓存。发送更新缓存请求,同时更新两个缓存。

4. 缓存不过期

这里的“永远不过期”包含两层意思:

  • 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期
  • 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建(这里如何保证只有一个线程去构建呢?),也就是“逻辑”过期

其实就是需要代码逻辑维护缓存的过期,不知道这样做的性能如何,我猜应该差不多吧,毕竟缓存时间你不维护也是Redis给你维护的…

不足之处就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受

总结

想了下, 感觉缓存最好都是异步更新,这样不会阻塞请求,或者减少阻塞的时间。

对于防止缓存雪崩的更新策略:

  • 双缓存策略:在第一份缓存过期后,简单加个锁去操作,例如用Redissetnx,加锁成功去发送更新缓存的消息,加锁失败的就直接去取未过期的缓存即可,之后更新缓存服务去读取队列消息去更新缓存,这样的一个异步的锁也不会阻塞服务,同时避免缓存击穿

  • 缓存不过期策略:这种情况就是设置缓存数据中的过期时间要过期的时候,加锁(分布式锁还是程序语言自带的锁都行,这个锁应该会阻塞一下请求),然后发送一个消息到消息队列,再更新下缓存的过期时间,避免后面的请求重复发送缓存更新的消息到消息队列。再用单独的服务更新缓存,然后对于一段时间内重复的消息,直接忽略。

总的来说就是不求没有重复的消息,尽量减少重复的消息即可,反正单个程多更新几次缓存对数据库影响也不大。上面都是避免和防止缓存穿透和雪崩的方法,但是当服务器宕机或者已经发生了,这些方法就不怎么管用了,所以尽量保证服务器高可用以及做好限流和服务降级之类的了