Redis生产问题
1:缓存穿透
客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会访问数据库。导致DB的压力瞬间变大而卡死或者宕机。
-
大量的高并发的请求打在redis上
-
这些请求发现redis上并没有需要请求的资源,redis命中率降低
-
因此这些大量的高并发请求转向DB请求对应的资源
-
DB压力瞬间增大,直接将DB打垮,进而引发一系列“灾害”
缓存穿透发生的场景一般有两类:
- 原来数据是存在的,但由于某些原因(误删除、主动清理等)在缓存和数据库层面被删除了,但前端或前置的应用程序依旧保有这些数据;
- 恶意攻击行为,利用不存在的Key或者恶意尝试导致产生大量不存在的业务数据请求。
解决方案:
-
接口校验
类似于用户权限的拦截,对于id = -3872这些无效访问就直接拦截,不允许这些请求到达Redis、DB上。对空值进行缓存 -
使用布隆过滤器
布隆过滤器的基本原理:使用BitMap结构和多个哈希函数,向其中加入数据xxx时,将hash1( x ),hash2 ( x ),hash3(x)等位置的bit值置为1。查询时,如果相应的位置均为1,则认为该数据存在于布隆过滤器中。
性能较高但也有缺点:可能会存在误判,使用时误判率可以作为参数进行设置,其实是调节了这个过程中使用到的哈希函数,使用的哈希函数越多,被误判的可能性越小,但相应地,判断过程会越慢。通常建议值是 1%。
不易于进行数据的删除操作,因为两个不同数据,可能会在某一个哈希函数上具有相同的哈希值
欲使其发挥作用,需要将MySQL中可能出现的所有参数、即全量数据都存入布隆过滤器中,代价较大
所以如果它告诉你不存在,则一定不存在;如果它告诉你存在,则可能不存在。通常很适合判断海量元素中元素是否存在,比如设置网站的黑名单
使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中,当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截。
3.缓存空值或特殊值
比如,虽然数据库中没有id = 1022的用户的数据,但是在redis中对其进行缓存(key=1022, value=null),这样当请求到达redis的时候就会直接返回一个null的值给客户端,避免了大量无法访问的数据直接打在DB上。
但需要注意:
key设置的过期时间不能太长,防止占用太多redis资源,设置一个合适的TTL,比如两三分钟。
当遇到黑客暴力请求很多不存在的数据时,就需要写入大量的null值到Redis中,可能导致Redis内存占用不足的情况。
public CoursePublish getCoursePublishCache(Long courseId) {
//查询缓存
Object jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
if(jsonObj!=null){
String jsonString = jsonObj.toString();
if(jsonString.equals("null"))
return null;
CoursePublish coursePublish = JSON.parseObject(jsonString,CoursePublish.class);
return coursePublish;
} else {
//从数据库查询
System.out.println("从数据库查询数据...");
CoursePublish coursePublish = getCoursePublish(courseId);
//设置过期时间300秒
redisTemplate.opsForValue().set("course:" + courseId,
JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);
return coursePublish;
}
}
2:缓存雪崩
缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间,比如对课程信息设置缓存过期时间为10分钟,在大量请求同时查询大量的课程信息时,此时就会有大量的课程存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。
解决方案:
-
将失效时间分散开
常用且易于实现通过使用自动生成随机数使得key的过期时间TTL是随机的,防止集体过期。 -
给业务添加多级缓存
使用nginx缓存 + redis缓存 + 其他缓存,不同层使用不同的缓存,可靠性更强。 -
构建缓存高可用集群
主要针对缓存服务故障的情景,使用Redis集群来提高服务的可用性。 -
使用锁或者队列的方式
如果查不到就加上排它锁,其他请求只能进行等待,但这种方式可能影响并发量。 -
设置缓存标记
热点数据可以不考虑失效,后台异步更新缓存,适用于不严格要求缓存一致性的情景。 -
缓存预热
不用等到请求到来再去查询数据库存入缓存,可以定时任务, 提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。
3:缓存击穿
缓存击穿是指大量并发访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。
解决方案:
-
使用互斥锁
只有一个请求可以获取到互斥锁,然后到DB中将数据查询并返回到Redis,之后所有请求就可以从Redis中得到响应。【缺点:所有线程的请求需要一同等待】 -
”提前“使用互斥锁 / 逻辑过期
在value内部设置一个比缓存(Redis)过期时间短的过期时间标识,当异步线程发现该值快过期时,马上延长内置的这个时间,并重新从数据库加载数据,设置到缓存中去。【缺点:不保证一致性,实现相较互斥锁更复杂】 -
提前对热点数据进行设置
类似于新闻、某博等软件都需要对热点数据进行预先设置在Redis中,或者适当延长Redis中的Key过期时间。 -
监控数据,适时调整
监控哪些数据是热门数据,实时的调整key的过期时长。