当前位置: 首页 > 科技观察

如果你没有遇到过这三个问题,你都不好意思说你用过Redis

时间:2023-03-22 10:58:58 科技观察

缓存是互联网应用不可或缺的一部分。说到缓存,就不得不提到它的三个经典问题——缓存穿透、缓存击穿、缓存雪崩。我称他们为缓存问题的三兄弟。缓存的作用主要有两个:一是提高访问速度;另一个是保护数据库。业务量不大的时候,一般不会出现大问题。但是当业务量变大的时候,如果缓存的使用不合理,三哥一定会如约而至,让你体验一下现实的残酷。三兄弟不来就好了。如果它们来了,会影响系统性能,严重的话会直接拖垮数据库,导致系统瘫痪。因此,我们不能掉以轻心,防患于未然。当缓存穿透请求到达服务器时,通常会进行如下处理。即按照以下步骤:查询缓存,命中则返回。如果缓存未命中,则查询数据库。将从数据库中查询到的数据写入缓存并返回。如果每次都这样一步步处理,就没事了。然而,凡事都不怕。但总有例外。如果请求者访问了不存在的数据(在数据库中),那么按照上面的流程,缓存就没有用了。因为不存在,所以不会写入缓存,所以每次请求都会命中数据库。这种现象就是所谓的“缓存穿透”。如果只是因为个别请求查询不存在的数据,其实没什么大不了的。但是缓存渗透通常伴随着一些“恶意请求”,通常是短时间内的大量请求。如果放任不管,只需等待数据库关闭即可。如何解决如果了解了缓存穿透的原因,那么解决的办法就很清楚了。可以从两个方面入手:缓存不存在的记录。过滤不存在??的请求。什么?如何缓存不存在的记录?其实很简单。如果在数据库中找不到,则将缓存的值设置为null(注意要根据业务特点设置合理的过期时间)。过滤不存在??的请求。当请求到达服务器时,例如:GET/api/user/1,过滤器会先判断资源是否存在,存在则释放,不存在则直接返回,从而保护系统。这种方式也有比较成熟的解决方案。比如Bloomfilter和cuckoofilter(升级版Bloom布隆过滤器)。无论是否有意请求不存在的资源,双重加固都不是我们想要的。因此,我们可以设置一个访问频率,在一定时间内频繁访问(超出正常用户的限制),并对请求者进行限制(如IP限制)。另外,有些接口是可以认证的,必须登录才能访问。缓存击穿通常,我们会给缓存设置一个过期时间。但是,如果在某个资源的缓存过期之后(或者未来还没有缓存之前)大量查询该资源的请求涌入,那么这些请求都会涌向数据库。这时候我们的数据库就惨了。秒挂。我们称这种情况为缓存崩溃。如何解决解决缓存崩溃的方法有两种:永不过期。锁。我们先来看第一种。热点资源通常在短时间内被大量访问。对于这类资源,我们可以不设置一个过期时间(永不过期),在资源变化时通过程序更新缓存。让我们看看第二个。我们可以使用锁定(一般JVM级别的锁就足够了)来避免崩溃。当缓存过期时,传入的请求首先要获得锁(即查询数据库的资格),然后查询数据库,最后将数据添加到缓存中。这样可以保证同一时间只有一个请求查库(一个服务实例),其他线程会等待缓存有值,然后再去缓存取。锁定的伪代码示例:publicStringgetData()throwsInterruptedException{//从缓存中获取值Stringresult=getFromCache();//获取值直接返回if(Objects.nonNull(result)){returnresult;}//尝试获取锁if(!lock.tryLock()){//如果锁失败,则休息Thread.sleep(10);返回获取数据();}//如果加锁成功,去数据库取值result=getFromDB();//取完后放入缓存setFromCache();returnresult;}缓存雪崩缓存雪崩是指缓存中的大量key同时集体过期,导致大量请求涌入数据库。有人把缓存服务由于某些原因导致的不可用称为缓存雪崩,我觉得不太恰当。想象一下什么是雪崩。大量的雪花集体从山上跳下来就是雪崩。那么对应缓存场景,我们可以把Redis看成一座山,Redis中的key就是一片雪花。Redis中大量的key同时失效,就像大量的雪花同时落在山上一样。因此,用雪崩来比喻大量密钥的集中失效显然更为合适。缓存服务失效应该是缓存服务失效,可以使用缓存集群来提高可用性。如何解决缓存雪崩问题,有两种思路:分散过期时间。永不过期。很容易想到分散的过期时间。由于雪崩是由密钥集体过期引起的,分散它们的过期时间可以避免这个问题。另一种思路,和解决缓存击穿一样,就是设置缓存永不过期。永不过期方案有一定的局限性。视具体业务而定,所有的缓存都不能设置为永不过期。综上所述,每种技术方案都有其适用的业务场景和局限性。没有一种方案可以解决所有问题,适合的才是好的。但是从上面的方案中,我们还是可以看出一些大概的思路,比如:尽快返回。你怎么理解的?就是让调用链尽可能的短,永远不要在应用服务(布隆过滤器)之前释放那些可以阻塞的;如果可以从缓存中获取它们,则永远不要检查库。