版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_28018283/article/details/82776544
概念
-
缓存穿透
:正常来说,一个合理设计的缓存命中率肯定是在50%以上,如果大量
的去避开缓存 ,就会因为miss cache
造成对DB的负载 -
缓存击穿
:某一个
高流量的热点Key
,在失效的一瞬间,造成的负载 -
缓存雪崩
:很多Key的失效时间相同 ,缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常
缓存雪崩
- 给一类缓存的失效时间,加上一个随机值,比如
set(key1,value1,time1+random(1,5))
,避免集体失效。 - 双缓存。对于
Value1
做两级缓存,Key1
和Key2
,获取数据流程如下:
/**
*获取2级缓存
**/
function getKeyDouble(Key1){
value = redis->get(Key1)
//如果Key1缓存失效
if(!value){
Key2 = getKeyD(Key1)
value = redis->get(Key2)
//异步 set Key1,Key2
DbValue = DB.get(sql)
ThreadSetKey(Key1,DbValue)
ThreadSetKey(Key2,DbValue)
}else{
return value
}
}
缓存击穿
- 互斥锁,如果缓存失效了,则去获取对应的互斥锁,如果获取到了再去访问DB,如果没有就短暂休眠,然后重试,或者返回NUll 让用户刷新
set nx 是原子命令,不存在 set 的并发同时设置的问题
在请求 Key 的时候 ,Get 某个热点 Key
做如下步骤来保证 不被缓存击穿的问题
1 先 Get Redis Key ,返回 True 则正常返回缓存
2 如果 1 返回 False 则设置一个 key_mutex 作为 Key 的互斥锁(保证线程的Lock状态),,如果设置成功,代表互斥锁可写,在这个线程中去 DB Loading 新的缓存,设置成功之后,删除互斥锁
3 如果2 setnx 失败,代表互斥锁处于Lock状态,线程等待一定的时间,这里代表Key在写入的阶段,按照业务逻辑做处理,可以选择,递归的继续获取Key方法;短暂的返回Null ,服务器繁忙请刷新重试等
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.get + set
出问题导致释放锁失败,就会存在 死锁
的风险,用户需要等待,会有 loading
的过程 。
- 采用
异步更新
策略,无论 key 是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
String get(final String key) {
V v = redis.get(key);
String value = v.getValue();
long timeout = v.getTimeout();
if (v.timeout <= System.currentTimeMillis()) {
// 异步更新后台异常执行
threadPool.execute(new Runnable() {
public void run() {
String keyMutex = "mutex:" + key;
if (redis.setnx(keyMutex, "1")) {
// 3 min timeout to avoid mutex holder crash
redis.expire(keyMutex, 3 * 60);
String dbValue = db.get(key);
redis.set(key, dbValue);
redis.delete(keyMutex);
}
}
});
}
return value;
}
相当于 在上面(1)互斥锁的基础上加上了一个 维护的过期时间,如果过期就做 缓存预热
,提前使用互斥锁 ,好处就是用户没有 loading等待。
- 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
布隆过滤器 来判断请求的key 是否在 keys 列表中,如果不在那么就进入了 处理流程,而不是对DB 进行冲击 。
// 缓存穿透的应对方案
String get(String key) {
String value = redis.get(key);
if (value == null) {
//没有命中缓存,并且key不存在
if (!bloomfilter.mightContain(key)) {
return null;
} else {
value = db.get(key);
redis.set(key,value);
}
}
return value;
}