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失效 | 互斥更新、随即退避、差异失效时间 |
| 缓存雪崩 | 缓存挂掉 | 快速失败熔断、主从模式、集群模式 |

相关推荐
爬山算法18 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
编程、小哥哥39 分钟前
设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)
redis·设计模式·抽象工厂模式
2401_8574396942 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66643 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索1 小时前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨1 小时前
Filter和Listener
java·filter
qq_4924484461 小时前
Java实现App自动化(Appium Demo)
java
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
找了一圈尾巴2 小时前
前后端交互通用排序策略
java·交互
PGCCC4 小时前
【PGCCC】postgresql 缓存池并发设计
数据库·缓存·postgresql