request 第一次访问缓存,缓存中没有,继续访问存储层(DB),当存储层(DB)访问有数据,就会缓存到Cache层,Request下次访问到Cache层就会访问到了。
出现这个问题的原因:
1、业务代码自身问题
比如Cache层第一次访问DB层的时候,调用的是接口,当接口返回有问题,就会导致缓存丢失。也有的是开发人员自身代码逻辑有问题,比如:某人从DB中拿取到了数据,但是他写代码的时候,返回的又是空的数据,当然这些都是比较低级的错误。
2、恶意攻击、爬虫等等。
比如视频网站,视频url的生成都是有一些规则的,当某些黑客知道这些url规则之后,他就会凭借这种规则去服务器获取一些视频信息,他会强制访问服务器,他的访问ID可能根本都不在你的服务器访问范围内,这些访问会触发到缓存,缓存又触发到DB层,这样就会出现缓存穿透问题。
如何发现呢?
1、业务的响应时间。
如果我们用缓存来扛很大的访问量,如果命中缓存的响应时间是可以预期的。
但是如果出现缓存传统这种现象,流量达到存储层,它必然会在响应时间上体现。
2、业务本身问题
RPC返回为空的问题,通过监听接口异常
3、相关指标:总调用数、缓存层命中数、存储层命中数
缓存利用率,你要能够采集到总调用数、缓存层命中数、存储层命中数。通过监听分析到底有没有出现问题。
解决方法:
1、缓存空对象
这也是一种比较直观,或者说简单粗暴的一种方法
当访问到Cache层,Cache层没有数据后,触发DB层访问,当我们确认DB层的确没有该数据后,我们可以在Cache层设置NULL,并设置过期时间5分钟、3分钟
当然这要根据你自己的业务场景,那么对于下一次访问的来说,直接从Cache层直接返回NULL。
这就是缓存空对象的解决方法,当然它存在着许多问题:
1、需要更多的缓存键值,当遇到爬虫或者恶意攻击的时候,会存储很多无用的空对象,虽然只是缓存了一些键和空值,
但是当这个访问量很大的时候,从业务上或者缓存使用率来说,也会有一定的风险。可以设置过期时间来降低这样的风险。
2、缓存层和存储层数据会短期不一致。
当访问缓存层和存储层的时候,数据会出现短期的不一致,会有一定的间隔。比如:刚开始A调用RPC服务B的时候,服务B由于某些原因它是处于不可用状态,此时缓存会Cache 为null,
当服务B恢复正常的时候,其实它的返回结果是不为NULL的,这就出现缓存与存储层DB数据不一致的状态。
这也是一个问题,当然可以做一些解决方案,比如订阅一些消息,当服务B恢复正常到时候,我们把缓存重新刷新一遍。甚至可以设置专门的消息队列来刷新缓存,异步刷新,或者针对某一些"key*",
或者某一些业务进行缓存刷新来解决缓存不一致的情况,这绝对不是一个强一致性,是最终一致的解决方案
我们来看下,缓存空对象的示例代码:
public E getPassThrough(String key){
E cacheEntity=(E)redis.get(key);
if(cacheEntity==null){
E entity=RPC.get(key);
redis.set(key,entity);
//如果RPC服务调用数据为空,需要设置一个过期时间 300秒
if(entity==null){
redis.expire(key,5*60);
}
return entity;
}
return cacheEntity;
}
第二种解决方法:布隆过滤器拦截
实际上它是通过很小的内存来实现对数据的过滤。比如巨大的电话本,10亿行电话,我要判断电话是否在这个电话本里,怎么去做呢?如果全部放在内存里,这样的话,内存就会非常大,可以通过一些算法将这些10亿行电话本或者大数据,放在布隆过滤器里面预热一遍,当你下一次访问判断这个电话是否在这个电话本里面时候,可以迅速反应。
在Redis中将所有课能存在的key数据缓存到布隆过滤器中,在缓存访问前需要再做一次拦截,如果需要访问的key在布隆过滤器中存在,则这个请求是有效的,如果没被过滤掉,则从Cache中去拿。
有一个问题就是,布隆过滤器怎么去生成?
离线去生成,还是说怎么去做?这个就涉及到很多问题,也就是说布隆过滤器的适用场景,对于这种比较固定的数据来说是比较好的;对于频繁更新的数据,如何构建这种过滤器实际上是有很多很多问题的。
举个例子:比如推荐股,不是现在实时推荐,而是根据用户前一天数据和操作日志去得出这样的一些结果,这些结果一般都是在夜间去计算的,计算结果会存储到Hbase上,第二天会把这些执行结果推荐给用户,这些针对用户的行为,可以将每个相关用户的key做成一个布隆过滤器,这个布隆过滤器就是相对可信的。
这两种解决方案都要根据自己的适用场景和实现成本来决定使用哪种,缓存空对象的解决方案,代码比较简单,但是会缓存更多的key,消耗一定的空间,同时也存在缓存数据不一致的问题。布隆过滤器用更小的内存来实现对数据的过滤,但是要维护实时布隆过滤器成本还是很大的,也是要消耗一定的空间。
缓存雪崩
如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是缓存雪崩。
处理缓存雪崩简单,在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效
setRedis(Key,value,time + Math.random() * 10000);