缓存穿透
概念
缓存的作用就是将一些可能的热点数据存起来放在DB访问的前面,减少对于DB的查询操作减轻DB的压力。
而缓存穿透就是我故意查询一些不命中缓存的东西,让他去查询DB。就像这层缓存完全不存在,被穿透了一样。
解决方案
- 每次查询这种数据库不存在的key,就将这个key和其对应的null值缓存起来,下次查询的时候直接在缓存层返回null。缺点: 如果大范围的key去扫数据库的话,可能会占用缓存非常多的key(比如整1000W个无意义的key去不停查),建议这种瞎XX查的key设短一点的过期时间。 适用范围,key值范围比较小的那种
- 比较推荐的一种是在缓存层之前再设一个BloomFilter。先在BloomFilter里找一下此次查询的key是否存在,不存在直接返回,存在的话再从缓存中查询,缓存没有再查DB
这里理解一下,BloomFilter要对每一个数据库的值进行涂黑操作,数据库有新值注入的时候就要进行涂黑。
String get(String key) {
if (!bloomfilter.mightContain(key)) { // 恶意数据
return null;
} else {
String value = redis.get(key);
if (null == value) { // 缓存没有就查库+装填缓存
value = db.get(key);
redis.set(key, value);
}
return value;
}
}
缓存击穿
概念
听起来和缓存穿透差不多,但是还是有区别的。
缓存穿透强调的是绕过了缓存,缓存击穿强调的是原本缓存应该是命中的,但是由于缓存自身的原因现阶段查不到该key(或者大范围的key)的值,从而击穿到了DB。
而且缓存击穿往往伴随着那个击穿的key可能会被高频的查询从而导致DB的压力猛然上升。
解决方案
使用互斥锁
单机的通过lock就行,集群环境使用分布式锁。具体做法在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。
集群环境redis代码示例:
String get(String key) {
String value = redis.get(key);
if (value == null) {
if (redis.setnx(key_mutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(key_mutex, 3 * 60);
value = db.get(key);
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其他线程休息50毫秒后重试
Thread.sleep(50);
get(key);
}
}
return value;
}
异步构建缓存
在这种方案下,构建缓存采取异步策略,会从线程池中取线程来异步构建缓存,从而不会让所有的请求直接怼到数据库上。
缓存雪崩
概念
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力,造成数据库后端故障,从而引起应用服务器雪崩。
解决方案
- 避免缓存集中失效,不同的key设置不同的超时时间
- 增加互斥锁,控制数据库请求,重建缓存。
- 提高缓存的HA,如:redis集群。