redis之缓存预热,缓存雪崩,缓存击穿,缓存穿透

目录

redis缓存预热

redis缓存雪崩

redis缓存击穿

redis缓存穿透


redis缓存预热

Redis 缓存预热是指在系统启动或者缓存失效后,提前将部分数据加载到 Redis 缓存中,以便用户在访问时能够快速获取数据,提高系统性能和响应速度。

  • 全量加载: 在系统启动时,将所有需要缓存的数据从数据库或其他数据源加载到 Redis 中,确保缓存中包含了所有可能被访问的数据。这种方式适用于数据量较小、变动频率低的场景。

  • 按需加载: 根据访问模式和数据访问频率,预先加载部分热门数据到 Redis 缓存中,以及时满足用户请求。这种方式适用于数据量较大、热点数据集中的场景。

  • 定时加载: 设置定时任务,在系统空闲时段或低峰期,定期从数据库加载数据到 Redis 缓存中,以确保缓存数据的及时性和完整性。

  • 异步加载: 在系统启动后,通过异步任务的方式逐步将数据加载到 Redis 缓存中,避免系统启动时的压力过大。

redis缓存雪崩

缓存雪崩是指在某个时间段,缓存中大量的数据同时失效或者因为某种原因都不可用,导致大量请求直接落到了后端数据库上,压力过大而导致数据库或其他服务崩溃,最终影响整个系统的稳定性和可用性。

缓存雪崩一般发生在以下情况:

  • 缓存服务器宕机或故障
  • 所有缓存数据在同一时间失效
  • 缓存数据没有设置过期时间或者过期时间过短
  • 大量请求访问热门数据,导致缓存击穿,无法承受巨大的并发请求

避免缓存雪崩,可以采取以下措施:

  • 分布式缓存: 将缓存分散到多个节点上,避免所有节点同时失效导致缓存雪崩。比如使用 Redis Cluster、Memcached Cluster 等分布式缓存方案。

  • 缓存预热: 在系统启动时,预先将热门数据加载到缓存中,避免缓存全部失效时,大量请求落到数据库上。

  • 设置过期时间: 设置合理的缓存过期时间,避免所有缓存同时失效。

  • 限流降级: 当请求量超过系统承受范围时,通过限流或者降级等措施,分流请求,保证系统的稳定性和可用性。

  • 多级缓存: 使用多级缓存架构,将热门数据放到内存中的缓存,避免频繁读取数据库和外部接口。

redis缓存击穿

缓存击穿是指当某个热点数据的缓存过期或不存在时,大量请求同时访问该数据,导致请求直接落到了后端数据库上,造成数据库压力过大,影响系统性能和可用性。

缓存击穿一般发生在以下情况:

  • 热点数据缓存失效:由于缓存的过期时间到达或被手动删除等原因,导致热点数据不再存在于缓存中。
  • 并发请求访问热点数据:大量并发请求同时访问热点数据,此时缓存未命中,导致请求直接访问数据库。

为了避免缓存击穿,可以采取以下措施:

  1. 加锁机制: 当检测到缓存失效时,先进行加锁操作,只允许一个请求去查询数据库,其他请求等待结果。查询完毕后,更新缓存并释放锁,其他请求再从缓存获取数据。

  2. 设置短暂的空值缓存: 在缓存失效时,先将空值(null)或者占位数据设置到缓存中,并给予较短的过期时间,避免大量请求同时访问数据库。同时,在缓存中设置一个较长的过期时间,以防止后续请求再次击穿。

  3. 异步更新缓存: 当检测到缓存失效后,异步更新缓存,而不是在请求时即时更新缓存。这样可以避免大量请求同时访问数据库,在缓存更新完成后,后续请求再从缓存获取数据。

  4. 使用互斥锁或分布式锁: 在多服务器环境下,使用互斥锁或者分布式锁来保证只有一个请求去查询数据库,其他请求等待结果,避免大量请求同时访问数据库。

redis缓存穿透

缓存穿透是指恶意请求或者不存在的数据被请求,导致缓存无法命中,每次请求都直接访问数据库或其他数据源,造成数据库压力过大。

缓存穿透一般发生在以下情况:

  • 恶意请求:恶意用户发送的请求,例如使用遍历查询参数、非法字符等,导致缓存无法命中,直接访问数据库。
  • 数据不存在:请求查询的数据本身就不存在于数据库或其他数据源中,无论怎样的请求都无法在缓存中命中。

为了避免缓存穿透,可以采取以下措施:

  1. 布隆过滤器(Bloom Filter): 使用布隆过滤器来预先过滤掉不存在的数据,当请求到来时,首先经过布隆过滤器的检查,如果不在布隆过滤器中,则直接拒绝请求,避免直接访问数据库。

  2. 空对象缓存: 当查询数据库为空时,在缓存中设置一个空对象(如null),并设置较短的过期时间,避免下次请求再次穿透到数据库。

  3. 合法性检查: 对于用户发送的请求参数进行合法性检查,避免恶意请求导致缓存穿透,可以使用验证 token、接口鉴权等方式进行有效性验证。

  4. 限流和监控: 对请求进行限流,避免大量的恶意请求进入系统,同时建立监控系统,及时发现异常请求并进行处理。

空对象缓存解决缓存穿透

复制代码
    public Customer findCustomerById(Integer customerId) {
        Customer customer = null;
        // 缓存redis的key名称
        String key = CACHE_KEY_CUSTOMER + customerId;
        // 1.去redis上查询
        customer = (Customer) redisTemplate.opsForValue().get(key);

        // 2. 如果redis有,直接返回  如果redis没有,在mysql上查询
        if (customer == null) {
            // 3.对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql(大公司的操作 )
            synchronized (CustomerService.class) {
                // 3.1 第二次查询redis,加锁后
                customer = (Customer) redisTemplate.opsForValue().get(key);
                // 4.再去查询我们的mysql
                customer = customerMapper.selectByPrimaryKey(customerId);

                // 5.mysql有,redis无
                if (customer != null) {
                    // 6.把mysql查询到的数据会写到到redis, 保持双写一致性  7天过期
                    redisTemplate.opsForValue().set(key, customer, 7L, TimeUnit.DAYS);
                }else {
                    // defaultNull 规定为redis查询为空、MySQL查询也没有,缓存一个defaultNull标识为空,以防缓存穿透
                    redisTemplate.opsForValue().set(key, "defaultNull", 7L, TimeUnit.DAYS);
                }
            }
        }
        return customer;
    }

Google布隆过滤器Guava解决缓存穿透

添加pom文件

复制代码
        <!--guava Google 开源的 Guava 中自带的布隆过滤器-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>23.0</version>
        </dependency>

业务类

  • GUavaBloomFilterController

    import com.xfcy.service.GuavaBloomFilterService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;

    import javax.annotation.Resource;

    @Api(tags = "gogle工具Guava处理布隆过滤器")
    @RestController
    @Slf4j
    public class GuavaBloomFilterController {

    复制代码
      @Resource
      private GuavaBloomFilterService guavaBloomFilterService;
    
      @ApiOperation("guava布隆过滤器插入100万样本数据,额外10w(110w)测试是否存在")
      @RequestMapping(value = "/guavafilter", method = RequestMethod.GET)
      public void guavaBloomFilter() {
          guavaBloomFilterService.guavaBloomFilter();
      }

    }

  • GUavaBloomFilterService

    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;

    import java.util.ArrayList;

    @Slf4j
    @Service
    public class GuavaBloomFilterService {
    // 1.定义一个常量
    public static final int _1W = 10000;
    // 2.定义我们guava布隆过滤器,初始容量
    public static final int SIZE = 100 * _1W;
    // 3.误判率,它越小误判的个数也越少(思考:是否可以无限小? 没有误判岂不是更好)
    public static double fpp = 0.01; // 这个数越小所用的hash函数越多,bitmap占用的位越多 默认的就是0.03,5个hash函数 0.01,7个函数
    // 4.创建guava布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), SIZE, fpp);

    复制代码
      public void guavaBloomFilter() {
          // 1.先让bloomFilter加入100w白名单数据
          for (int i = 0; i < SIZE; i++) {
              bloomFilter.put(i);
          }
          // 2.故意取10w个不在合法范围内的数据,来进行误判率的演示
          ArrayList<Integer> list = new ArrayList<>(10 * _1W);
    
          // 3.验证
          for (int i = SIZE + 1; i < SIZE + (10 * _1W); i++){
              if (bloomFilter.mightContain(i)){
                  log.info("被误判了:{}", i);
                  list.add(i);
              }
          }
          log.info("误判总数量:{}", list.size());
      }

    }

缓存问题总结

|---------|-------------|-----------------------|
| 缓存问题 | 产生原因 | 解决方案 |
| 缓存更新不一致 | 数据变更、缓存时效性 | 同步更新、失效更新、异步更新、定时更新 |
| 缓存不一致 | 同步更新失败、异步更新 | 增加重试、补偿任务、最终一致 |
| 缓存穿透 | 恶意攻击 | 空对象缓存、bloomFilter 过滤器 |
| 缓存击穿 | 热点key失效 | 互斥更新、随即退避、差异失效时间 |
| 缓存雪崩 | 缓存挂掉 | 快速失败熔断、主从模式、集群模式 |

相关推荐
用户37215742613511 分钟前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊7 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端