前言
设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。
缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从储存层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到储存层去查询,失去了缓存的意义。在流量大时,可能DB挂掉,要是有人利用不存在的key频繁攻击我们的应用这就是漏洞
解决方案
方案一:布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
方案二:如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们让然把这个空结果进行缓存,但他的过期时间会很短,最长不超过5分钟
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬间压力过重导致雪崩
解决方案
缓存数据的过期时间随机设置,防止同一时间大量数据过期现象发生
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中,分散压力
设置热点数据永远不过期
缓存击穿
缓存击穿是指缓存中没有这些数据但是数据库中有数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大的压力
解决方案
设置热点数据永远不过期
使用互斥锁
public String get(key){
//缓存中读取数据
String value = redis.get(key);
//缓存中不存在
if(value == null){//代表缓存之过期
//加锁 设置3分钟的超时,防止del操作失败的时候,下次缓存过期一直不能load 数据库
if(redis.setnx(key_mutex,1,3*60) == 1){
//从数据库中获取数据
value = db.get(key);
//更新缓存
redis.set(key,value,expire_secs);
//释放锁
redis.del(key_mutex);
}else{//这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
//重新尝试获取缓存
value = redis.get(key);
}
}
return value;
}