一、缓存雪崩
缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
二、示意图
缓存正常从Redis中获取,示意图如下:
缓存失效瞬间示意图如下:
三、解决方案
以下列举两种方案,不仅限于此两种
方案一
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上
加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法!
注意:加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验很差!因此,在真正的高并发场景下很少使用!
方案二
在原有的缓存失效时间上加一个随机说,比如随机城市1-10分钟内的随机数,这样的操作很简单,相比第一种更轻!
三、压力测试
这里是对方案二的测试,方案一没事测试,
压测前提
在3s内压1000个并发,缓存失效时间为2s
代码示例
伪代码,没有实际意义,只是为了测试;
get方法进行了加时处理
get3方法没有加时处理
@GetMapping(value = "get")
public Object get(@RequestParam(value = "key") String key){
Object obj = redisUtil.get(key);
if(obj ==null){
System.out.println("这里进行数据库查询----get");
obj = "Java有货";
redisUtil.set(key,obj, (long) (2+Math.random()* 10));
}
return obj;
}
@GetMapping(value = "get3")
public Object get3(@RequestParam(value = "key") String key){
Object obj = redisUtil.get(key);
if(obj ==null){
System.out.println("这里进行数据库查询---- get3");
obj = "Java有货";
redisUtil.set(key,obj, 2);
}
return obj;
}
访问情况
与数据库连接测试
从下图可以看出,在相同的压力环境下,进行随机时间处理,很大程度减少了开销!
总结:
以上只是一个接口的压测,如果整个系统所有接口用到缓存的地方设置的时间都是一样的,出现大面积失效,之后的所有请求都击中在DB上,可想而知,我们的服务会出现什么的状况!
关注 Java有货领取更多资料
联系小编。微信:372787553,带您进群互相学习
左侧小编微信,右侧获取免费资料
- Java 设计模式学习代码 https://github.com/Dylan-haiji/design-pattern
- SpringCloud学习代码: https://github.com/Dylan-haiji/javayh-cloud
- AlibabaCloud学习代码:https://github.com/Dylan-haiji/javayh-cloud-nacos
- SpringBoot+Mybatis 多数据源切换:https://github.com/Dylan-haiji/javayh-boot-data-soure
- Redis、Mongo、Rabbitmq、Kafka学习代码: https://github.com/Dylan-haiji/javayh-middleware
- SpringBoot+SpringSecurity实现自定义登录学习代码:https://github.com/Dylan-haiji/javayh-distribution