缓存穿透了怎么办?

在如今互联网架构中,几乎每一个互联网项目都会引入缓存系统,好比 Redis、Memcached。来保护下游数据库和提高系统并发量。无论使用哪一种缓存系统都有可能遇到缓存穿透的问题。java

缓存穿透是指在缓存系统中没有查询到数据,而不得不将请求打到数据库上查询的状况。算法

固然缓存系统是不可避免的,少许的缓存穿透对系统也没有损害,不可避免的缘由有如下几点:数据库

  • 缓存系统的容量是有限的,不可能存储系统全部的数据,那么在查询未缓存数据的时候就会发生缓存穿透。
  • 另外一方面就是基于‘二八原则’,咱们一般只会缓存经常使用的那 20% 的热点数据。

正常状况下的缓存穿透是没什么伤害的,可是若是你的系统遭遇攻击,存在大量的缓存穿透的话,那么可能就是一个麻烦了,若是大量的缓存穿透超过了后端服务器的承受能力,那么就有可能形成服务崩溃,这是不可接受的。后端

基于存在这种大量缓存穿透的可能性,因此咱们就须要从根源上解决缓存穿透的问题,解决缓存穿透,目前通常有两种方案:缓存空值和使用布隆过滤器数组

缓存空值

若是咱们系统是遇到攻击的话,那么颇有可能查询的值是伪造的,必然大几率不存在咱们的系统中,这样不管查询多少次,在缓存中一直不存在,这样缓存穿透就一直存在。缓存

在这种状况下,咱们能够在缓存系统中缓存一个空值,防止穿透一直存在,可是由于空值并非准确的业务数据,而且会占用缓存的空间,因此咱们会给这个空值加一个比较短的过时时间,让空值在短期以内可以快速过时淘汰。下面是一段伪代码:服务器

Object nullValue = new Object();
try {
  Object valueFromDB = getFromDB(uid); //从数据库中查询数据
  if (valueFromDB == null) {
    cache.set(uid, nullValue, 10);   //若是从数据库中查询到空值,就把空值写入缓存,设置较短的超时时间
  } else {
    cache.set(uid, valueFromDB, 1000);
  }
} catch(Exception e) {
  cache.set(uid, nullValue, 10);
}
复制代码

虽然这种方法能够解决缓存穿透的问题,可是这种方式也存在弊端,由于在缓存系统中存了大量的空值,浪费缓存的存储空间,若是缓存空间被占满了,还会还会剔除掉一些已经被缓存的用户信息反而会形成缓存命中率的降低。架构

使用布隆过滤器

1970年布隆提出了一种布隆过滤器的算法,用来判断一个元素是否在一个集合中。布隆过滤器底层是一个超级大的 bit 数组,默认值都是 0 ,一个元素经过多个hash函数映射到这个 bit 数组上,而且将 0 改为 1。固然布隆过滤器也不须要咱们实现,在 Google 的 guava 包中有提供布隆过滤器,有兴趣的小伙伴能够研究研究。并发

布隆过滤器存在必定的误判,由于采用hash算法,就必定会存在哈希冲突,这样就可能形成不在数据库中的元素被判断在布隆过滤器中存在,可是不在布隆过滤器中的元素必定不存在数据库中。函数

利用布隆过滤器的这个特色能够解决缓存穿透的问题,在服务启动的时候先把数据的查询条件,例如数据的 ID 映射到布隆过滤器上,固然若是新增数据时,除了写入到数据库中以外,也须要将数据的ID存入到布隆过滤器中

咱们在查询某条数据时,先判断这个查询的 ID 是否存在布隆过滤器中,若是不存在就直接返回空值,而不须要继续查询数据库和缓存,存在布隆过滤器中才继续查询数据库和缓存,这样就解决缓存穿透的问题。

使用布隆过滤器示意图

固然布隆过滤器有缺陷,除了上面咱们讲到过的存在必定的误判,还有一个就是不支持删除

缓存空值和使用布隆过滤器均可以在必定程度上解决缓存穿透的问题,各自有各自的优点,具体如何使用根据特定的场景舍取。

以上就是今天分享的内容,但愿对您的学习或者工做有所帮助,若是您以为文章不错,欢迎点个赞和转发,谢谢。

最后

目前互联网上不少大佬都有缓存穿透相关文章,若有雷同,请多多包涵了。原创不易,码字不易,还但愿你们多多支持。若文中有所错误之处,还望提出,谢谢。

互联网平头哥
相关文章
相关标签/搜索