缓存相关也做了个大概总结,分享一下吧,从网上看得资料

解决缓存的相关问题

a.缓存穿透的问题:

解决方案:

1.布隆过滤器 把所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉

2.如果一个查询返回的数据是空的,无论数据是否存在,还是系统故障,我们仍然将这个空结果进行缓存,但设置很短的过期时间

b.缓存雪崩的问题: 设置缓存的时候采用了相同的过期时间,导致缓存在同一时刻失效,请求全部转发到db,db压力瞬间变大雪崩。

解决方案

1.用加锁或者队列的方式保证缓存的单线程写,避免失效时大量的并发请求落到底层的存储系统上

2.将缓存的失效时间分散开,在原来失效时间的基础上加上一个随机值,比如说1-5分钟的随机,这样每一个缓存过期时间的重复率就会降低,很难引起集体失效的事件。

c.缓存击穿的问题。对于一些设置了过期时间的key,这些key某些时间点被超高并发地访问,是非常热点的数据,缓存在某个时间点过期的时候,恰好这个时间节点对这个key有大量的并发请求过来,这些请求发现缓存的key过期就一般都会从后端数据库请求数据并且设置到缓存,大的并发请求就可能会压垮后端db

解决方案

1.使用互斥锁(mutex) , 在缓存失效的时候,判断拿出来的值为空,不立即去load db,而是先使用缓存工具的某些带有返回值的操作(比如说redis的setnx),去set一个mutex key,当操作返回成功的时候,在进行load db的操作并且设置回缓存,否则,就重试整个get缓存的方法。
String get(String key){
    String value = reids.get(key);
    if(value == null){
        if(redis.setnx(key_mutex,"1")){
            reids.expire(key_mutex,3*60);
            value = db.get(key);
            redis.set(key,value);
            redis.delete(key_mutex);
        }else{
            //其他线程休息50ms后重试
            Thread.sleep(50);
            get(key);
        }
    }
}

2.设置热点数据永不过期

a.从redis上来看,确实不设置过期时间,保证不会出现热点key过期的问题,也就是物理上的不过期

b.从功能上看,我们把过期时间存在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")){
                    //设置三分钟的超时
                    redis.expire(keyMutex,3*60);
                    String dbValue = db.get(key);
                    redis.set(key,dbValue);
                    redis.delete(keyMutex);
                }
            }   
        });
    }
 
}




3.提前使用互斥锁
在value内部设置一个超时值(timeout1),timeout1比实际的mem*** timeout(timeout2)小,当从***读取到timeout1发现它已经过期的时候,马上延长timeout1并重新设置到***。然后再从数据库加载数据并且设置到***中

v = mem***.get(key);
if(v==null){
    if(mem***.add(key_mutex,3*60*1000) == true){
        value = db.get(key);
        mem***.set(key,value);
        mem***.delete(key_mutex);
    }else{
        sleep(50);
        retry();
    }else{
        if(v.timeout <= now()){
            if(mem***.add(key_mutex,3*60*1000) == true){
                v.timeout += 3*60*1000;
                mem***.set(key,v,KEY_TIMEOUT*2);
                
                //从数据库加载最新的数据
                v = db.get(key);
                v.timeout = KEY_TIMEOUT;
                mem***.set(key,value,KEY_TIMEOUT*2);
                mem***.delete(key_mutex);
            }else{
                sleep(50);
                retry();
            }
        }
    }
}