一、概念
-
缓存穿透:
key对应的数据在缓存和数据库中都不存在,用户不断发起请求,由于缓存中取不到,导致每个请求都会去查询DB,严重时会击垮数据库。这一问题往往出自故意攻击,黑客利用一个不存在的用户id获取用户信息,进而压垮数据库。
-
缓存击穿:
key对应的数据存在,但在redis中过期失效,而在热点key失效的瞬间,若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,持续的大并发就穿破缓存,直接访问数据库,导致数据库奔溃。
-
缓存雪崩:
与缓存击穿类似,缓存击穿指的是单个热门key过期失效,而缓存雪崩是指当Redis服务器重启或者大量缓存在同一时期失效,此时大量的流量全部冲击到数据库上,导致数据库瞬间压力过大而崩溃。
二、解决方案
-
缓存穿透:
- 接口校验:在接口层对所有请求参数进行校验,无论页面或者API接口,非法请求参数直接代码return,比如用户id校验,如果id长度不对则直接拦截等。此方法为事前预防。
- 保存null值:在首次请求缓存和数据库都没取到对应value值时,在缓存中将该key值对应value设置为null,对空的结果进行缓存。同时设置一个相对较短的过期时间。此方法属于事后补救。
- 布隆过滤器(Bloom Filter):直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。
和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。项目中可用google提供的guava开源jar包实现。但此方法有一定的误判率,并且加入元素不能被删除。
-
缓存击穿:
- 热点数据永不过期:不设置过期时间,默认永不过期。并且redis设置为noeviction机制,当redis服务器占用内存达到maxmemory最大的情况下时,也不会对不过期key进行删除。
- 互斥锁(mutex key):在缓存KEY过期去更新的时候,先让程序去获取锁,只有获取到锁的线程才有资格去更新缓存KEY。其他没有获取到锁的线程则休眠片刻之后再次去获取最新的缓存数据。通过这种方式,同一时刻永远只有一个线程会去读取数据库,这样也就避免了海量数据库请求对于数据库的冲击。
public static String getData(String key) throws InterruptedException {
//redis中查询数据
String result = getDataByKey(key);
//校验参数
if(StringUtils.isBlank(result)){
//建锁
ReentrantLock lock = new ReentrantLock();
if(lock.tryLock()){
//从DB中查询
result = getDataByDB(key);
//校验数据
if(StringUtils.isNotBlank(result)){
//存入缓存
setDataToRedis(key,result);
}
//释放锁
lock.unLock();
}else{
Thread.sleep(100L);
result = getData(key);
}
}
return result
}
-
缓存雪崩
- 设置不同过期时间:热点key不设置过期时间,非热点key可以在原有失效时间的基础上增加随机数,每个缓存的过期时间重复率就会降低,减少缓存雪崩的发生概率。
public object GetProductListNew() {
int cacheTime = 30;
String cacheKey = "product_list";
//缓存标记
String cacheSign = cacheKey + "_sign";
String sign = CacheHelper.Get(cacheSign);
//获取缓存值
String cacheValue = CacheHelper.Get(cacheKey);
if (sign != null) {
return cacheValue; //未过期,直接返回
} else {
CacheHelper.Add(cacheSign, "1", cacheTime);
ThreadPool.QueueUserWorkItem((arg) -> {
//这里一般是 sql查询数据
cacheValue = GetProductListFromDB();
//日期设缓存时间的2倍,用于脏读
CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2);
});
return cacheValue;
}
}
三、总结
缓存穿透是业务层面的漏洞导致非法请求,与请求量、缓存失效没关系。缓存击穿则只会出现在热点数据上,发生在缓存失效的瞬间,与业务没多大关系。缓存雪崩则是因为多个 KEY 同时失效,导致数据库请求太多。非热点数据也会导致缓存雪崩,只要同时失效的 KEY 足够多。
参考资料:
Redis 缓存雪崩、击穿、穿透
REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案