为什么会存在缓存三剑客的问题
我们要知道我是引路缓存的主要目的是减少大量对数据库的直接访问请求导致数据库扛不住这么多并发而宕机。如果单单只把数据放入缓存这个操作,在某些情况下还是会发生大量请求打在数据库。
缓存击穿
比如当我们一个热点key(经常被访问的数据)突然过期了,从缓存中获取不到数据就会从数据库中查,如果此时有大量的请求,这些请求都会打在数据库层面而数据库扛不住。
解决方案:
1.很容易想到的就是不给这个热key设置过期时间,让他们永不过期。
2.提前通过缓存预热的方式将他们全部写入缓存可以避免大量的请求访问
3.使用分布式锁来控制访问数据库的请求数量。
下面我用代码演示一下第3种方案
//分布式锁解决缓存击穿问题
public CoursePublish getCoursePublishCache2(Long courseId) {
//查询缓存不存在在查数据库
Object o = redisTemplate.opsForValue().get("course" + courseId);
if(o!=null){
String s = o.toString();
return JSON.parseObject(s, CoursePublish.class);
}else{
RLock lock = redissonClient.getLock("coursqueeylock" + courseId);
lock.lock();
try {
//从数据库中查
CoursePublish coursePublish = getCoursePublish(courseId);
redisTemplate.opsForValue().set("course"+courseId,JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);
return coursePublish;
}finally {
//释放锁
lock.unlock();
}
}
}
当缓存中查询不到,我们就尝试获取锁,再从数据库里面查,这样看似通过锁机制控制了线程访问次数。如果又大量的请求在第一步查缓存的时候都没有查到,那么他们都会执行else语句查询数据库,性能还是不高。
此时我们可以在获的锁之后,再判断一次缓存是否存在,只要某一个请求查询完数据库,那么后面来的请求都会再判断一次缓存从缓存中拿到数据,进一步又减少了数据库访问量。代码如下:
//分布式锁解决缓存击穿问题
public CoursePublish getCoursePublishCache2(Long courseId) {
//查询缓存不存在在查数据库
Object o = redisTemplate.opsForValue().get("course" + courseId);
if(o!=null){
String s = o.toString();
return JSON.parseObject(s, CoursePublish.class);
}else{
RLock lock = redissonClient.getLock("coursqueeylock" + courseId);
lock.lock();
try {
o = redisTemplate.opsForValue().get("course" + courseId);
if(o!=null) {
String s = o.toString();
return JSON.parseObject(s, CoursePublish.class);
}
//从数据库中查
CoursePublish coursePublish = getCoursePublish(courseId);
redisTemplate.opsForValue().set("course"+courseId,JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);
return coursePublish;
}finally {
//释放锁
lock.unlock();
}
}
}
缓存穿透
如果用户恶意大量访问一个数据库不存在的值,此时查缓存并不能查到,所有请求又都到了数据库导致数据库负载急剧增加。
解决方案:
1.缓存设为空值,及查询缓存的时候返回的是null。(附上代码理解)
public CoursePublish getCoursePublishCache2(Long courseId) {
//查询缓存不存在在查数据库
Object o = redisTemplate.opsForValue().get("course" + courseId);
if(o!=null){
String s = o.toString();
return JSON.parseObject(s, CoursePublish.class);
}else{
if(redisTemplate.hasKey("course" + courseId)){
throw new RuntimeException("课程不存在");
}
//从数据库中查
CoursePublish coursePublish = getCoursePublish(courseId);
if(coursePublish==null) {
redisTemplate.opsForValue().set("course"+courseId,"",30,TimeUnit.SECONDS);
}else {
redisTemplate.opsForValue().set("course" + courseId, JSON.toJSONString(coursePublish), 30, TimeUnit.SECONDS);
}
return coursePublish;
}
}
2.使用布隆过滤器过滤掉哪些不存在的值。
以Redis中的布隆过滤器实现为例,Redis中的布隆过滤器底层是一个大型位数组(二进制数组,默认都是0)+多个无偏hash函数,用于判断元素是否再集合中,一般我们再添加数据的时候会将数据的主键啊放入布隆过滤器,布隆过滤器会通过多次hash函数得到一个数组下标并把数组下标的值改为1。当我们需要查询某个元素的是否存在,通过hash得到的值检查对于的位数组下标的值是否为1,如果是则证明该元素存在。
//布隆过滤器解决缓存穿透
public CoursePublish getCoursePublishCache3(Long courseId) {
//查询缓存不存在在查数据库
Object o = redisTemplate.opsForValue().get("course" + courseId);
if(o!=null){
String s = o.toString();
return JSON.parseObject(s, CoursePublish.class);
}else{
boolean contains = rBloomFilter.contains(String.valueOf(courseId));
if(!contains){
throw new RuntimeException("课程不存在");
}
//从数据库中查
CoursePublish coursePublish = getCoursePublish(courseId);
redisTemplate.opsForValue().set("course" + courseId, JSON.toJSONString(coursePublish), 30, TimeUnit.SECONDS);
return coursePublish;
}
}
}
缓存雪崩
缓存雪崩是指在某一时刻,大量缓存数据同时失效,导致大量请求直接访问数据库,造成数据库压力过大甚至宕机的现象。缓存雪崩的发生通常有两种情况:一是大量热门缓存同时失效,二是缓存服务器宕机。
解决方案:
1.过期时间加随机数,为避免缓存同时失效,可以在设置缓存过期时间时,加上一个随机数。例如,过期时间可以设置为原定时间加上1到60秒的随机数。这样即使在高并发情况下,也不会有太多缓存同时失效。
2.针对缓存服务器宕机的情况,可以在系统设计时采用高可用架构。例如,使用Redis的哨兵模式或集群模式,避免单节点故障导致整个缓存服务不可用。哨兵模式下,当某个主节点下线时,会自动将其下的某个从节点提升为主节点,继续处理请求。
如果高可用架构仍然无法避免缓存服务宕机,可以采用服务降级策略。配置一些默认的兜底数据,当检测到缓存服务不可用时,程序可以直接返回这些默认数据1。例如,可以设置一个全局开关,当在一定时间内有多个请求从缓存中获取数据失败时,开启全局开关,后续请求直接从配置中心获取默认数据。
3.请求限流机制是只将少部分请求发送到数据库进行处理,再多的请求就在入口直接拒绝服务,等到 Redis 恢复正常并把缓存预热完后,再解除请求限流的机制。该方案减少对业务的影响
标签:缓存,courseId,数据库,Redis,course,coursePublish,CoursePublish,三剑客 From: https://blog.csdn.net/Liulijie_/article/details/144936280