1.1穿透优化
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,由于每次请求都要到存储层去查询,导致存储层负载加大,造成存储层宕机。
1.1.2缓存空对象
当客户端查询数据不存在时,将空对象保留到缓存中,之后在访问这个数据将会从缓存中获取,有效的减轻了存储层的压力。伪代码如下:
String cacheValue = redis.get(key)
if(isNull(cacheValue)){
String dbValue=mysql.get(key);
redis.set(key,dbValue);
if(dbValue == null){
redis.expire(key,expireTime);
}
return dbValue;
}else{
return cacheValue;
}
缓存空对象需要更多的缓存空间,而且会出现数据不一致的情况。
1.1.2布隆过滤器
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来。bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中,可以利用Redis的Bitmaps实现布隆过滤器。布隆过滤器的相关知识可以参考:https://www.cnblogs.com/liyulong1982/p/6013002.html。
1.2雪崩优化
由于在同一时刻出现大面积的缓存过期,所有原本应该访问缓存的请求都去查询数据库了,造成存储层负载巨大压力,严重导致数据库宕机。
1.2.1加锁
利用加锁的方式保证不会有大量的并发请求对数据库一次性进行读写,从而避免缓存失效时大量的并发请求查询数据库。伪代码如下:
String get(String key){
//从redis中获取数据
String value = redis.get(key);
if(value=null){
//只允许一个线程重构缓存
String mutexKey= "mutex:key:"+key;
if(redis.set(mutexKey,"1","ex 180","nx")){
//从数据库获取数据
value = db.get(key);
//写redis,设置过期时间
redis.setex(key,timeout,value);
//删除互斥锁
redis.delete(mutexKey);
}else{
Thead.sleep(50);
get(key);
}
}
return value;
}
1.2.2设置不过期
该方法就是缓存不设置过期时间,另外可以为value设置一个逻辑过期时间,当发现超过逻辑时间,会使用单独的线程去数据库重新获取数据。该方法会产生数据不一致的情况。伪代码如下:
String get(String key){
V v = redis.get(key);
String value = v.getValue();
//获取逻辑过期时间
long logicTimeout = v.getLogicTimeout();
//如果逻辑过期时间小于当前时间,开始重新获取数据
if(v.logicTimeout<=System.currentTimeMillis()){
String mutexKey = "mutex:key:"+key;
if(redis.set(mutexKey,"1","ex 180","nx")){
theadPool.execute(new Runnable(){
public void run(){
String dbValue = db.get(key);
redis.set(key,(dbvalue,newLogicTimeout));
redis.delete(mutexKey);
}
})
}
}
return value;
}
1.3参考资料
《Redis开发与运维》