穿透
概念
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。
解决
- 缓存穿透我会在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等
- Redis还有一个高级用法布隆过滤器(Bloom Filter)这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
- 另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
击穿
概念
1、就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况
2、当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
3、击穿和缓存雪崩的区别在于击穿针对某一key缓存,雪崩则是很多key
解决方案
1、可以将热点数据设置为永远不过期
2、实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
有mutex和SETNX两种实现互斥锁的方案
SETNX是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
public String get(key) {
String value = redis.get(key);
if (value == null) { //代表缓存值过期
//设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
value = db.get(key);
redis.set(key, value, expire_secs);
redis.del(key_mutex);
} else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
sleep(50);
get(key); //重试
}
} else {
return value;
}
}
雪崩
概念
1、缓存中大批量热点数据过期后系统涌入大量查询请求
2、因为大部分数据在Redis层已经失效,请求渗透到数据库层
3、大批量请求犹如洪水一般涌入,引起数据库压力造成查询堵塞甚至宕机。
解决
1、事前:(集群),redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃
2、事中:(限流,降级),本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
3、事后:(持久化),redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
4、不通的key设置不同的超时时间
Redis一些知识: