瞅一瞅缓存穿透、缓存击穿和缓存雪崩

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_28018283/article/details/82776544

概念

  1. 缓存穿透:正常来说,一个合理设计的缓存命中率肯定是在50%以上,如果大量 的去避开缓存 ,就会因为 miss cache 造成对DB的负载

  2. 缓存击穿:某一个高流量的 热点Key,在失效的一瞬间,造成的负载

  3. 缓存雪崩:很多Key的失效时间相同 ,缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常

缓存雪崩

  1. 给一类缓存的失效时间,加上一个随机值,比如set(key1,value1,time1+random(1,5)) ,避免集体失效。
  2. 双缓存。对于 Value1 做两级缓存,Key1Key2 ,获取数据流程如下:
	/**
	*获取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
		}
	}

缓存击穿

  1. 互斥锁,如果缓存失效了,则去获取对应的互斥锁,如果获取到了再去访问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 的过程 。

  1. 采用 异步更新策略,无论 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等待。

  1. 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的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;
}

猜你喜欢

转载自blog.csdn.net/qq_28018283/article/details/82776544