欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

缓存雪崩、缓存穿透、缓存击穿、缓存预热、缓存更新、缓存降级梳理

程序员文章站 2022-06-19 11:58:21
...

一、缓存使用流程

时间 事件
T1 客户端发起请求
T2 后台接收请求,先去缓存中取数据,如果取到数据直接返回数据,请求结束,如果没有取到数据,执行T3
T3

从数据库中取数据,如果取到数据更新缓存,并返回数据,如果数据库中也没有取到数据,那直接返回空结果

注:如果数据库中没有取到数据,也建议在缓存中存储一个默认空值,这样可以避免用户一直请求去查询数据库

以上是缓存正常使用的一个请求流程,而缓存雪崩、缓存穿透、缓存击穿则是在使用缓存过程中存在的一些情况,下面将详细分析一下

二、缓存雪崩

场景:同一时间大面积的缓存数据过期(或缓存服务器挂了),此时又有大批量的请求这些数据,缓存中取不到数据,就会将请求全部转发到数据库,数据库瞬时压力过重雪崩。例一大批商品抢购,同一时间下架,将商品数据从缓存中删除,同时大批用户去访问这些商品,缓存中没有数据,所有的请求都会去查询数据库

解决方式:

    1、尽量错开过期时间,不要大批量设置同一过期时间,如热数据和冷数据设置不同的过期时间,不同类型的数据设置不同的过期时间

    2、一般并发量不是特别多的时候,使用最多的解决方案是加锁排队

    3、给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存(实际缓存数据的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存)

三、缓存穿透

场景:指查询数据库中一定不存在的数据,缓存和数据库中都没有这个数据,以致于产生两条无用查询,每次请求都会到达数据库,就失去了使用缓存的意义,在流量大的时候,数据库可能会挂掉,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞

解决方式:

    1、代码中如果可以明确判断一些值,则请求数据之前进行验证拦截,如商品编号只会在1-100,如果客户端请求编号为-1,则直接进行拦截返回,避免无效请求到达缓存和数据库

    2、如果查询结果为空,我们设置一个默认值进行缓存,过期时间设置短一些,最长不超过五分钟,这样下次再请求的时候可以直接从缓存中返回数据,不用再去访问数据库,这种方式最简单粗暴

三、缓存击穿

场景:指缓存中没有但数据库中有数据,一般是因为缓存数据时间到期了,如果这时由于并发用户特别多,同时大量请求读缓存没读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力

解决方式:

    1、使用互斥锁(mutex key)

    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、热点数据设置永远不过期(第一种不设置过期时间,第二种设置过期时间,过期时间快到的时候异步延长过期时间)


缓存雪崩和缓存击穿、缓存穿透和缓存击穿的区别:

缓存雪崩:缓存雪崩是不同数据都过期了很多数据都查不到从而查数据库

缓存击穿:缓存击穿指并发查同一条数据

 

缓存穿透:缓存和数据库中都没有需要的值

       缓存击穿:缓存中没有需要的数据,但数据库中有(击穿比较形象,击打后穿透,是有一个过程,说明是有缓存的;穿透就比较直接,相当于直接就过了,缓存没生效)


四、缓存预热

场景:系统上线时,同时将数据更新到缓存中,这样用户请求的时候,就可以直接访问缓存,不必去请求数据库

解决方式:

    1、数据量不大的情况下,可以在系统启动的时候同时加载缓存

    2、写一个刷新缓存的功能按钮或页面,特定的时候维护人员进行手动刷新

五、缓存更新

场景:除了redis设置过期时间的缓存数据清理,redis也提供了一些其他的更新淘汰策略,如缓存的数据量太大,达到了内存限制的最大值,将会出现redis内存不足的问题。redis允许用户通过配置maxmemory-policy 参数,指定redis在内存不足时的解决策略(详细可访问博客:https://blog.csdn.net/lemon_linaa/article/details/110200659

noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。大多数写命令都会导致占用更多的内存(redis默认选项)
maxmemory-policy noeviction	
 
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰(LRU算法)
使用场景:如果设置了过期时间,且分热数据与冷数据,推荐使用 volatile-lru 策略
其他:热数据(需要被计算节点频繁访问的在线类数据)、冷数据(是对于离线类不经常访问的数据,比如企业备份数据、业务与操作日志数据、话单与统计数据),可参考https://www.jianshu.com/p/053ba529bf02
maxmemory-policy volatile-lru	
 
allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰(LRU算法)
使用场景:使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
值得一提的是,设置 expire 会消耗额外的内存,所以使用 allkeys-lru 策略,可以更高效地利用内存,因为这样就可以不再设置过期时间了
maxmemory-policy allkeys-lru 
 
volatile-lfu:从已设置过期时间的数据集中挑选访问频率最少的数据淘汰(LFU算法)
maxmemory-policy volatile-lfu	
 
allkeys-lfu:从所有数据集中挑选访问频率最少的数据淘汰(LRU算法)
maxmemory-policy allkeys-lfu 
 
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
使用场景:很少使用
maxmemory-policy volatile-random	
 
allkeys-random:从所有数据集中任意选择数据进行淘汰
maxmemory-policy allkeys-random	
 
volatile-ttl:从已设置过期时间的数据集中优先删除剩余时间短的数据
使用场景:如果让 Redis 根据 TTL 来筛选需要删除的key,请使用 volatile-ttl 策略
maxmemory-policy volatile-ttl 

 

六、缓存降级

场景:当并发量过高、服务出现问题(响应时间慢或不响应),为了使程序正常可用,需要对程序的使用进行降级处理,对一些非核心模块的业务进行请求访问降级,如支付宝的核心业务模块是转账,如果进行降级,可以选择对查看账单、蚂蚁森林、查看热销活动等非核心业务模块进行降级,降级的最终目的是保证核心服务可用,所以进行降级之前要对系统的业务模块进行梳理,理出哪些是可以降级的,哪些是无法降级的,根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务;根据服务范围:可以砍掉某个功能,也可以砍掉某些模块

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量,已达到保护系统的目的,一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的,比如:延迟处理,拒绝处理,或者部分拒绝处理等等。

解决方式:

    1、代码中核心业务模块进行峰值捕获,然后将非核心业务请求进行降级

    2、编写降级页面,特定的时候维护人员进行手动降级

相关标签: Redis