目前的IO设备远不能知足互联网应用海量的读写请求。因而便出现了缓存,利用内存的高速读写性能来应付海量的查询请求。然而内存资源很是宝贵,将全量数据存储在内存中显然是不切合实际的。所以目前采用内存和IO结合的方式,内存只存储热点数据,而IO设备存储全量数据。
缓存的设计包含不少技巧,设计不当将会致使严重的后果。本文将介绍缓存使用中常见的三大问题,并给出相应的解决方案。
在大多数互联网应用中,缓存的使用方式以下图所示:程序员
了解了上述过程后,下面说说缓存穿透。数据库
业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,因为缓存中不存在,而后再前往数据库中查询。因为该数据压根就不存在,所以数据库也返回空。这就是缓存穿透。segmentfault
综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。缓存
若是存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会致使系统崩溃(你要知道,目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,因此咱们要想种种办法保护它)。分布式
发生缓存穿透的缘由有不少,通常为以下两种:工具
下面来介绍两种防止缓存穿透的手段。性能
之因此发生缓存穿透,是由于缓存中没有存储这些空数据的key,致使这些请求全都打到数据库上。spa
那么,咱们能够稍微修改一下业务系统的代码,将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直接返回null,而无需查询数据库。设计
第二种避免缓存穿透的方式即为使用BloomFilter。blog
它须要在缓存以前再加一道屏障,里面存储目前数据库中存在的全部key,以下图所示:
当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,所以缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
这两种方案都能解决缓存穿透的问题,但使用场景却各不相同。
对于一些恶意攻击,查询的key每每各不相同,并且数据贼多。此时,第一种方案就显得提襟见肘了。由于它须要存储全部空数据的key,而这些恶意攻击的key每每各不相同,并且同一个key每每只请求一次。所以即便缓存了这些空数据的key,因为再也不使用第二次,所以也起不了保护数据库的做用。
所以,对于空数据的key各不相同、key重复请求几率低的场景而言,应该选择第二种方案。而对于空数据的key数量有限、key重复请求几率较高的场景而言,应该选择第一种方案。
经过上文可知,缓存其实扮演了一个保护数据库的角色。它帮数据库抵挡大量的查询请求,从而避免脆弱的数据库受到伤害。
若是缓存因某种缘由发生了宕机,那么本来被缓存抵挡的海量查询请求就会像疯狗同样涌向数据库。此时数据库若是抵挡不了这巨大的压力,它就会崩溃。
这就是缓存雪崩。
也就是在雪崩发生以前,作好预防手段,防止雪崩的发生。
PS:关于分布式高可用问题不是今天讨论的重点,套路就那些,后面会有高可用的相关文章,尽请关注。
Hystrix是一款开源的“防雪崩工具”,它经过 熔断、降级、限流三个手段来下降雪崩发生后的损失。
Hystrix就是一个Java类库,它采用命令模式,每一项服务处理请求都有各自的处理器。全部的请求都要通过各自的处理器。处理器会记录当前服务的请求失败率。一旦发现当前服务的请求失败率达到预设的值,Hystrix将会拒绝随后该服务的全部请求,直接返回一个预设的结果。这就是所谓的“熔断”。当通过一段时间后,Hystrix会放行该服务的一部分请求,再次统计它的请求失败率。若是此时请求失败率符合预设值,则彻底打开限流开关;若是请求失败率仍然很高,那么继续拒绝该服务的全部请求。这就是所谓的“限流”。而Hystrix向那些被拒绝的请求直接返回一个预设结果,被称为“降级”。
更多Hystrix的介绍请参阅:https://segmentfault.com/a/11...
咱们通常都会给缓存设定一个失效时间,过了失效时间后,该数据库会被缓存直接删除,从而必定程度上保证数据的实时性。
可是,对于一些请求量极高的热点数据而言,一旦过了有效时间,此刻将会有大量请求落在数据库上,从而可能会致使数据库崩溃。其过程以下图所示:
若是某一个热点数据失效,那么当再次有该数据的查询请求[req-1]时就会前往数据库查询。可是,从请求发往数据库,到该数据更新到缓存中的这段时间中,因为缓存中仍然没有该数据,所以这段时间内到达的查询请求都会落到数据库上,这将会对数据库形成巨大的压力。此外,当这些请求查询完成后,都会重复更新缓存。
咱们可使用缓存自带的锁机制,当第一个数据库查询请求发起后,就将缓存中该数据上锁;此时到达缓存的其余查询请求将没法查询该字段,从而被阻塞等待;当第一个请求完成数据库查询,并将数据更新值缓存后,释放锁;此时其余被阻塞的查询请求将能够直接从缓存中查到该数据。
当某一个热点数据失效后,只有第一个数据库查询请求发往数据库,其他全部的查询请求均被阻塞,从而保护了数据库。可是,因为采用了互斥锁,其余请求将会阻塞等待,此时系统的吞吐量将会降低。这须要结合实际的业务考虑是否容许这么作。
互斥锁能够避免某一个热点数据失效致使数据库崩溃的问题,而在实际业务中,每每会存在一批热点数据同时失效的场景。那么,对于这种场景该如何防止数据库过载呢?
当咱们向缓存中存储这些数据的时候,能够将他们的缓存失效时间错开。这样可以避免同时失效。如:在一个基础时间上加/减一个随机数,从而将这些缓存的失效时间错开。