首页 > 编程知识 正文

缓存穿透 击穿 雪崩解决方案,缓存雪崩 缓存击穿

时间:2023-05-05 09:31:58 阅读:207667 作者:1323

如何解决缓存雪崩、击穿、穿透 一、介绍

目前电商首页以及热点数据都会去做缓存 ,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,定时任务刷新就有一个问题。

举个简单的例子:如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是我理解的缓存雪崩。

我刻意看了下我做过的项目感觉再吊的都不允许这么大的QPS直接打DB去,不过没慢SQL加上分库,大表分表可能还算能顶,但是跟用了Redis的差距还是很大

同一时间大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏,你怎么重启用户都会把你打挂。

二、解决缓存雪崩 将每个key的失效时间都加上一个随机数,不要让缓存的数据在同一时间失效。

在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会在同一时间大面积失效,我相信,Redis这点流量还是顶得住的。

setRedis(Key,value,time + Math.random() * 10000);

如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题,不过本渣我在生产环境中操作集群的时候,单个服务都是对应的单个Redis分片,是为了方便数据的管理,但是也同样有了可能会失效这样的弊端,失效时间随机是个好策略。

将热点数据不要设置过期时间。

或者设置热点数据永远不过期,有更新操作就更新缓存就好了(比如运维更新了首页商品,那你刷下缓存就完事了,不要设置过期时间),电商首页的数据也可以用这个操作,保险。

三、缓存穿透

指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库。

解决缓存穿透

1、 缓存穿透我会在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。

2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、位置错误、稍后重试这样的值具体取啥问产品,或者看具体的场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。

3、这样可以防止攻击用户反复用同一个id暴力攻击,但是我们要知道正常用户是不会在单秒内发起这么多次请求的,那网关层Nginx本渣我也记得有配置项,可以让运维大大对单个IP每秒访问次数超出阈值的IP都拉黑。

4、 Redis还有一个高级用法**布隆过滤器(Bloom Filter)**这个也能很好的防止缓存穿透的发生,他的原理也很简单就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

那又有小伙伴说了如果黑客有很多个IP同时发起攻击呢? 把系统的高可用做好了,集群还是很能顶的。 其他具体的方法,暂时没找到。

四、缓存击穿

缓存击穿和缓存雪崩有点类似,但是也有区别。 缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。

解决缓存击穿

方案一

缓存并发:

有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。

双重校验防止缓存击穿:

List<Map> userinfo = (List<Map> ) redisTemplate.opsForValue().get("userInfo");//双重校验防止缓存击穿if(userinfo==null){synchronized (this) {userinfo = (List<Map> ) redisTemplate.opsForValue().get("userInfo");if(userinfo ==null){userinfo = acceptTempDao.getServiceInfo();redisTemplate.opsForValue().set("userInfo", userinfo, 120, TimeUnit.SECONDS);System.out.println("请求的数据库。。。。。。");}else { System.out.println("请求的缓存。。。。。。"); }}} else { System.out.println("请求的缓存。。。。。。"); }

ehcache防止缓存击穿:
tomcat jvm堆内存缓存,主要是抗redis出现大规模灾难。如果redis出现了大规模的宕机,导致nginx大量流量直接涌入数据生产服务,那么最后的tomcat堆内存缓存也可以处理部分请求,避免所有请求都直接流向DB

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>/** * 缓存Service实现类 * @author Administrator * */@Service("cacheService")public class CacheServiceImpl implements CacheService {public static final String CACHE_NAME = "local";/** * 将商品信息保存到本地缓存中 * @param productInfo * @return */@CachePut(value = CACHE_NAME, key = "'key_'+#productInfo.getId()")public ProductInfo saveLocalCache(ProductInfo productInfo) {System.out.println("ttttttt22tttttt");return productInfo;}/** * 从本地缓存中获取商品信息 * @param id * @return */@Cacheable(value = CACHE_NAME, key = "'key_'+#id")public ProductInfo getLocalCache(Long id) {return null;}}@EnableCaching@SpringBootApplicationpublic class EhCacheApp {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(EhCacheApp.class, args); }}

思路:取本地缓存,取不到,取redis缓存,取不到加同步锁,取数据库,更新缓存,释放锁

方案2

设置热点数据永远不过期。或者加上互斥锁就能搞定了 。

五、如何避免

一般避免以上情况发生我们从三个时间段去分析下:

事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。事中:本地 ehcache 缓存 + Hystrix 限流+降级,避免MySQL被打死。事后:Redis 持久化 RDB+AOF,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

好处:

数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。只要数据库不死,就是说,对用户来说,3/5 的请求都是可以被处理的。只要有 3/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。

这个在目前主流的互联网大厂里面是最常见的,你是不是好奇,某明星爆出什么事情,你发现你去微博怎么刷都空白界面,但是有的人又直接进了,你多刷几次也出来了,现在知道了吧,那是做了降级,牺牲部分用户的体验换来服务器的安全,可还行?

可以参考博客:https://mp.weixin.qq.com/s/DjvFpO7OGdGWnyFS6N75fw

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。