本文有参考其他文章及部分个人理解,如有错误以及意见,欢迎交流。
缓存雪崩
理解:
key批量失效:大面积的缓存失效,请求打崩数据库同时大面积失效,redis等同于无,这个数量等级的请求直接打到数据库,如果没做熔断等策略,基本就瞬间挂,那么依赖这个库的所有接口都会报错。
解决方案:
- 往redis中存数据时,给没有设置过期时间的每个key加上随机失效时间,这样可以保证数据不会同一时间大面积失效。
- redis集群部署,将热点数据均匀分布在不同的redis库中也能避免全部失效的问题。
- 设置热点数据永不过期,有更新操作就更新缓存就好了。
缓存击穿
理解:
热点key失效瞬间:非常热点key,在不停的扛着大并发,并发集中在这个一个点,当key失效的瞬间,持续的大并发就击穿缓存。
解决方案:
- 设置热点数据永不过期 。
缺陷:数据不能保持最新、定时线程更新数据。 - synchronized+双重检查机制:某个key只让一个线程查询,堵塞其他线程,在同步块中,继续判断检查,保证不存在,再去查数据库 。
缺陷:会堵塞其他线程。
示例:
public String getValue(String key) {
String value= redis.get(key, String.class);
if (Tools.isEmpty(value)) {
synchronized(lockHelp) {
value = redis.get(key,String.class);
if (Tools.isEmpty(value)) {
value = db.query(key);
redis.set(key, value, 1000);
}
}
}
return value;
}
- 增加互斥锁:在缓存失效的时候(根据判断从缓存中取出来的值是否为空),不是马上去数据库查询,而是先使用带成功操作返回值的方法去set一个mutex key(热点key),当操作返回成功时,再进行查询数据库的操作并回设缓存;否则,就重试整个get缓存方法。
示例
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);//查询db的值
redis.set(key, value, expire_secs);//set进key
redis.del(key_mutex);//删除热点key
return value;
} else {//这个时候代表同时间的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(10);
get(key); //重试
}
} else {
return value;
}
}
缓存穿透
理解:
不存在的key:用户不断发起缓存和数据库中均不存在的数据请求,导致数据库压力过大,严重会击垮数据库。
解决方案:
- 增加参数校验。
- 从网关层Nginx增加配置项,对单个ip美妙访问次数超出阈值的ip都拉黑。
- .布隆过滤器(Bloom Filter)能很好防止缓存穿透的发生,原理就是利用高效的数据结构和算法快速判断出这个key是否在数据库中存在,不存在就return,存在就去查数据库刷新缓存再return。