您的位置:首页 > 其它

缓存穿透 & 缓存雪崩 & 缓存击穿

2018-01-11 19:11 381 查看

一 缓存穿透

1. 行为

查询一个一定不存在的数据。存储层(姑且认为是db,下面都用db指代)查不到数据则不写入缓存,那么下次请求这个不存在的数据同样会到db层查询,失去了缓存的意义。流量大或人为恶意攻击可能会使db宕掉。

2. 解决方案

(1) 布隆过滤器。将全量可能存在的数据哈希到一个足够大的bitmap中,布隆可能误报,但绝不会漏报,那么一定不存在的数据会被拦截掉,从而缓解了对db的压力

(2) 空结果也进入缓存。如果查询返回的结果为空 (数据不存在 | 服务不可用), 仍将数据-空结果进行缓存,注意将其过期时间设置非常短(不超过5min)

二 缓存雪崩

1. 行为

设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时实效,请求全部打到db,db瞬间压力过重雪崩。

2. 解决方案

(1) 加锁或采用队列保证缓存的单线程,避免失效时大量请求落到db存储系统

(2) 缓存时间离散化。在愿缓存的失效时间基础上增加一个随机值,降低同一时间集体失效概率

三 缓存击穿

1. 行为

对于设置了过期时间的某些key,在过期的时间点,恰好对这个key有大量的并发请求过来,这些请求发现缓存过期同时请求db加载数据并回设到缓存,这个高并发的请求可能瞬间把后端db压垮。

2.解决方案

(1) 永不过期

(2) 使用互斥锁。缓存失效的时候,不直接load db,而是使用缓存工具中带有成功返回标识的方法(比如redis的setnx,memcache的add)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存。否则重试整个get缓存的方法。

在redis2.6.1之前版本未实现setnx的过期时间,所以给出两种版本代码参考

a) setnx无过期时间版本:

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);
}
}
}


b) redis2.6.1后, setnx有过期时间版本:

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);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key);  //重试
}
} else {
return value;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: