今天想起之前面试的时候问我缓存击穿问题及其解决方案,当时还并没有接触到缓存知识,啥也没答出来,回去后查了一下,也没有记录下来,现在就这个问题好好总结一下吧!
1.缓存处理流程
以laravel框架开发的项目为例,客户端请求数据,控制器接收用户请求调用模型查询用户所需的数据返回给客户端,业务逻辑非常简单,但是当成千上万的客户频繁的请求数据呢?那样系统会变得很慢,客户的请求长时间得不到回应,这时候就要使用缓存技术了,这样业务逻辑就变成了前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
2.缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起id为负数或者不存在的很大的数值,这样会给数据库带来很大压力。
解决方案:(1)对用户的输入数据进行校验,如id的值只能是正整数,对于id<=0的数直接拦截,使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一定不存在的数据会被拦截掉
(2)如果一个查询返回的为空,仍然将这个空结果存入到缓存中,但是它的过期时间非常的短,最长不会超过5分钟。这样可以防止攻击者反复用同一个id暴力攻击。
3.缓存雪崩
缓存雪崩是指在设置缓存的时候采用了相同的过期时间,导致大量的数据在某一时间同时过期,用户的请求全部转向数据库,数据库压力瞬间增大导致雪崩
解决方案:(1)用加锁或者队列来保证缓存的单线程
(2)在设置失效时间的基础上增加随机值如1-5分钟,这样可以使得过期时间的重复率降低
4.缓存击穿
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:(1)使用互斥锁
在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
代码实现如下:
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;
}
}
就是在第一次查询缓存不存在的情况下先查询数据库,之后回设缓存,当再次请求时就可以读取到缓存中的数据
(2)设置热点信息永不过期