一、先简单介绍下缓存穿透、缓存雪崩、缓存击穿及一些解决思路
1、缓存
缓存(cache),原始意义是指访问速度比一般随机存取存储器(RAM)快的一种高速存储器,通常它不像系统主存那样使用DRAM技术,而使用昂贵但较快速的SRAM技术。缓存的设置是所有现代计算机系统发挥高性能的重要因素之一。——百度百科
我理解的缓存就是能使应用更快更方便地获取数据的一种技术。
2、缓存穿透
缓存穿透指的是:同一时刻,大量的并发请求数据库中不存在的信息,他既不会命中缓存,也不会命中数据库,但是他会查找数据库。
比如说:用户查询的id是数据库不存在的,所以这个时候这些并发请求都不会命中缓存,将直接访问数据库,同时数据库中也没有查到该数据,也没法放入缓存,这样如果同一时刻大量的并发请求到达数据库,数据库承受不住这么高的并发,就会导致数据库直接挂了,这就是缓存穿透。
一些解决方法:
(1)缓存空值:
当某个值在缓存中查不到,在数据库中也查不到时,也需要将该值缓存起来,只不过值是空的。这样后面的请求,再拿相同的值发起请求时,就能从缓存中获取空数据,直接返回了,而无需再去查一次数据库。
但这个方法也有问题,万一这个空值刚缓存起来,数据库就加了这个数据,导致明明存在的数据查出来的确实空值。
(2)布隆过滤器:
一种些许暴力的方法:如果数据比较少,直接把数据库中的数据全部放到内存的一个map中。这样能够非常快速的识别数据在缓存中是否存在。如果存在,则让其访问缓存。如果不存在,则直接拒绝该请求。
但一般数据量都很大,上述方法会占用太多的内存空间了。这样就可以考虑使用一些技术来减少内存空间,比如之前分享的布隆过滤器
使用布隆过滤器也是可以,但是也有问题:比如误判率(如果布隆过滤器判断出某个key存在,可能出现误判。如果判断某个key不存在,则它在数据库中一定不存在),感觉影响不大。
使用布隆过滤器还有个问题是:如果数据库中的数据更新了,需要同步更新布隆过滤器。但它跟数据库是两个数据源,就可能存在数据不一致的情况。
当然还有一些别的方法,但是感觉并没有十分完美的方法能去解决缓存穿透,多多少少都有一些问题。
3、缓存雪崩
在使用缓存时,通常会对缓存设置过期时间,一方面目的是保持缓存与数据库数据的一致性,另一方面是减少冷缓存占用过多的内存空间。
缓存雪崩指的是:当缓存中大量热点缓存采用了相同的实效时间,就会导致缓存在某一个时刻同时实效,请求全部转发到数据库,从而导致数据库直接挂了,从而形成一系列的连锁反应,造成系统崩溃等情况,这就是缓存雪崩。(当然还有可能缓存服务器出问题了也可能会导致缓存雪崩)
一些解决方法:
通常的解决方案是将key的过期时间后面加上一个随机数(比如随机1-5分钟),让key均匀的失效。
当然还有很多别的方法,这里就先不具体展开。
4、缓存击穿
上面的缓存雪崩是大量热点key同时失效的情况,如果是单个热点key,在不停的扛着大并发,在这个key失效的瞬间,持续的大并发请求就会击破缓存,直接请求到数据库,数据库受不了又直接挂了,这就是缓存击穿
比如热搜排行上,一个热点新闻被同时大量访问,当缓存过期的一瞬间就可能导致缓存击穿。
解决方法:
在下面主要介绍如何使用golang的singleflight库来解决缓存击穿以及其原理。
当然还有别的方法。
二、singleflight在缓存击穿中的应用
偶然发现golang里面自带singleflight包
但是不幸的是它是go的内部包,并不能直接使用,但是官方辅助包golang.org/x/...里提供了singlefligh