博主系统内redis的使用伪代码如下:
Object o = redis.get(key); //1
if(o != null){
return (Student)o; //2
}
Student value = read db; //3
return value; //4
这段代码也不知道最早谁开始使用的,反正就是这么流传下来了,其实这也是正常的逻辑,先从缓存内读取,如果有,直接返回。如果没有,从DB读取。
今天是来研究缓存方面的问题,所以我们先来看看缓存穿透的问题。
缓存穿透:指查询一个根本不存在的数据,缓存层和持久层都不会命中,但是出于容错的考虑,如果持久层都查不到的话就不会往缓存层去更新数据。
后果:导致了不存在的数据每一次都去持久层查询数据,失去了缓存保护后端的意义。
分析原因:1.可能是自身代码的错误导致了很多空命中,需要去读持久层的数据。
2.恶意攻击导致的空命中,一下子全跑去持久层,导致后台宕机。
解决方案:
1.缓存空对象。
缓存空对象是一个有效的方法,当步骤3获取不到数据时,为当前key缓存一个空对象,之后的访问就从缓存中读取,保护了后端数据。
带来的问题:
1.1使用更多的内存空间(如果是攻击的话,影响更严重),可行解决办法:针对此类数据设置一个较短的失效时间。
1.2数据不一致问题,当首次查询不存在时缓存了空对象,但是紧接着,持久层添加了这个数据,就会导致持久层和缓存层数据不一致的现象,因此这边需要注意缓存内容的更新。
2.布隆过滤器。
参考文章:
算法的话比较复杂,也不是我关注的对象,不过稍微记录下几个术语吧。
False Positive:FP(集合内没有该元素,查找结果有该元素),就是误报。
False Negative:FN(集合内有该元素,查找结果没有该元素),就是漏报。
适用场景:数据实时性不高,数据命中要求不高,相对固定的场景。
两种方案对比
热点key的重建优化
一般采用缓存+过期时间的策略可以很好加速数据读写,但是一旦有两个问题同时出现的话,可能造成致命缺陷。
1.当前key是一个热点key,并发量特别大。
2.重建缓存的过程比较缓慢。
因此在缓存失效的时候会出现大量线程来重建缓存,可能造成后端负载过大导致崩溃。
解决问题需要注意的点:
减少缓存重建次数,数据尽可能一致,注意其他潜在风险。
解决方式:
1. 互斥锁
只允许一个线程重建缓存,其他线程等待重建的线程执行完再从缓存读取数据。
2.永远不过期。
2.1不设置key的过期时间。
2.2设置value的逻辑过期时间。
重点回顾:
参考文章:
redis开发与运维