缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,可是出于容错的考虑,若是从存储层查不到数据则不写入缓存层,如图 11-3 所示整个过程分为以下 3 步:前端
缓存穿透将致使不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。git
缓存穿透模型github
缓存穿透问题可能会使后端存储负载加大,因为不少后端存储不具有高并发性,甚至可能形成后端存储宕掉。一般能够在程序中分别统计总调用数、缓存层命中数、存储层命中数,若是发现大量存储层空命中,可能就是出现了缓存穿透问题。redis
形成缓存穿透的基本有两个。第一,业务自身代码或者数据出现问题,第二,一些恶意攻击、爬虫等形成大量空命中,下面咱们来看一下如何解决缓存穿透问题。算法
缓存穿透的解决方法后端
1)缓存空对象缓存
以下图所示,当第 2 步存储层不命中后,仍然将空对象保留到缓存层中,以后再访问这个数据将会从缓存中获取,保护了后端数据源。并发
缓存空值应对穿透问题高并发
缓存空对象会有两个问题:工具
第一,空值作了缓存,意味着缓存层中存了更多的键,须要更多的内存空间 ( 若是是攻击,问题更严重 ),比较有效的方法是针对这类数据设置一个较短的过时时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有必定影响。例如过时时间设置为 5 分钟,若是此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时能够利用消息系统或者其余方式清除掉缓存层中的空对象。
下面给出了缓存空对象的实现伪代码:
2)布隆过滤器拦截
以下图所示,在访问缓存层和存储层以前,将存在的 key 用布隆过滤器提早保存起来,作第一层拦截。例如: 一个个性化推荐系统有 4 亿个用户 ID,每一个小时算法工程师会根据每一个用户以前历史行为作出来的个性化放到存储层中,可是最新的用户因为没有历史行为,就会发生缓存穿透的行为,为此能够将全部有个性化推荐数据的用户作成布隆过滤器。若是布隆过滤器认为该用户 ID 不存在,那么就不会访问存储层,在必定程度保护了存储层。
开发提示:
有关布隆过滤器的相关知识,能够参考:https://en.wikipedia.org/wiki/Bloom_filter
能够利用 Redis 的 Bitmaps 实现布隆过滤器,GitHub 上已经开源了相似的方案,读者能够进行参考:
https://github.com/erikdubbelboer/Redis-Lua-scaling-bloom-filter
使用布隆过滤器应对穿透问题
这种方法适用于数据命中不高,数据相对固定实时性低(一般是数据集较大)的应用场景,代码维护较为复杂,可是缓存空间占用少。
两种方案对比
前面介绍了缓存穿透问题的两种解决方法 ( 实际上这个问题是一个开放问题,有不少解决方法 ),下面经过下表从适用场景和维护成本两个方面对两种方案进行分析。
缓存空对象和布隆过滤器方案对比
###redis的缓存雪崩?
从下图能够很清晰出什么是缓存雪崩:因为缓存层承载着大量请求,有效的保护了存储层,可是若是缓存层因为某些缘由总体不能提供服务,因而全部的请求都会达到存储层,存储层的调用量会暴增,形成存储层也会挂掉的状况。 缓存雪崩的英文原意是 stampeding herd(奔逃的野牛),指的是缓存层宕掉后,流量会像奔逃的野牛同样,打向后端存储。
缓存层不可用引发的雪崩
预防和解决缓存雪崩问题,能够从如下三个方面进行着手。
1)保证缓存层服务高可用性。
和飞机都有多个引擎同样,若是缓存层设计成高可用的,即便个别节点、个别机器、甚至是机房宕掉,依然能够提供服务,例如前面介绍过的 Redis Sentinel 和 Redis Cluster 都实现了高可用。
2)依赖隔离组件为后端限流并降级。
不管是缓存层仍是存储层都会有出错的几率,能够将它们视同为资源。做为并发量较大的系统,假若有一个资源不可用,可能会形成线程所有 hang 在这个资源上,形成整个系统不可用。降级在高并发系统中是很是正常的:好比推荐服务中,若是个性化推荐服务不可用,能够降级补充热点数据,不至于形成前端页面是开天窗。
在实际项目中,咱们须要对重要的资源 ( 例如 Redis、 MySQL、 Hbase、外部接口 ) 都进行隔离,让每种资源都单独运行在本身的线程池中,即便个别资源出现了问题,对其余服务没有影响。可是线程池如何管理,好比如何关闭资源池,开启资源池,资源池阀值管理,这些作起来仍是至关复杂的,这里推荐一个 Java 依赖隔离工具 Hystrix(https://github.com/Netflix/Hystrix),以下图所示。