redis缓存预热、缓存击穿、缓存穿透、缓存雪崩

redis缓存预热、缓存击穿、缓存穿透、缓存雪崩

文档

  1. redis单机安装
  2. redis集群模式 -集群搭建
  3. 布隆过滤器 -Bloom Filter
  4. springboot整合redisson单机模式
  5. redis实现布隆过滤器
  6. guava布隆过滤器及cuckoo过滤器

官方文档

  1. 官网操作命令指南页面:https://redis.io/docs/latest/commands/?name=get&group=string
  2. Redis cluster specification

说明

  1. redis版本:7.0.0
  2. springboot版本:3.2.0

缓存预热

含义
  1. 系统启动或上线前,提前把热点数据加载进缓存,避免刚上线时大量请求打到 DB。
使用场景
  1. 平时:主要预热「常驻热点」和「冷启动必用」数据,减轻日常 DB 压力、提升首屏体验
  2. 活动时:在平时预热基础上,额外、提前预热「本次活动相关」数据,避免活动开始瞬间打垮 DB
  3. 总结:高并发促销时不是"才做预热",而是"在平时预热的基础上,再针对本次活动多做一层、提前做"。
注意事项
  1. 每个 key 必须设置合理的过期时间,并在基础 TTL 上加随机偏移,避免大量 key 在同一时刻过期引发缓存雪崩。
  2. 只预热真正热点(Top N、配置指定的 id 等),不要全表预热,避免占满缓存、拉高 DB 压力。

缓存击穿

形成场景
  1. 一个热点key
  2. 大量并发同期请求这个key
  3. 此时缓存中不存在这个key(例如刚过期或被删除)
  4. 这些请求会同时去访问数据库,导致缓存击穿
解释说明
  1. 本身行为是非恶意的,本意是请求一个有数据的key
  2. 缓存中没有这个key,在数据库中也能查到,这里不强调数据库中没有数据的情况
  3. 重点在于:缓存中没有时,不应该所有请求都去访问数据库,只需一个请求去访问数据库,并写缓存即可
解决方案
  1. 双检加锁(互斥锁)

    1. 思路:只让一个请求去访问数据库,并写缓存

    2. 示例

      java 复制代码
      public Data get(String key) {
          // 一检:先查缓存
          Data data = cache.get(key);
          if (data != null) {
              return data;
          }
      
          // 抢锁(同一 key 互斥)
          String lockKey = "lock:" + key;
          if (tryLock(lockKey)) {
              try {
                  // 二检:拿到锁后再查一次缓存
                  data = cache.get(key);
                  if (data != null) {
                      return data;
                  }
                  // 查 DB 并写缓存
                  data = db.get(key);
                  cache.set(key, data);
                  return data;
              } finally {
                  unlock(lockKey);
              }
          } else {
              // 没抢到锁,短暂等待后重试查缓存
              Thread.sleep(50);
              return cache.get(key);
          }
      }
  2. 热点 key 永不过期 / 逻辑过期

    1. 思路:热点 key 不设 TTL,或设逻辑过期时间,到期后异步刷新,读时仍返回旧值。

    2. 示例

      java 复制代码
      public Data get(String key) {
          Data data = cache.get(key);
          if (data != null) {
              // 逻辑过期:检查是否过期,过期则异步刷新
              if (isExpired(data)) {
                  asyncRefresh(key);  // 异步刷新,不阻塞
              }
              return data;  // 仍返回旧值,避免击穿
          }
          // 缓存没有才查 DB
          data = db.get(key);
          cache.set(key, data, Long.MAX_VALUE);  // 永不过期
          return data;
      }
  3. 单飞(SingleFlight)

    1. 思路:同一 key 的并发请求合并为一次 DB 查询,其他请求等待结果。单机版直接用内存实现,分布式版需要用redis等中间件模拟

    2. 示例

      java 复制代码
      ConcurrentHashMap<String, CompletableFuture<Data>> flights = new ConcurrentHashMap<>();
      
      public Data get(String key) {
          CompletableFuture<Data> future = flights.computeIfAbsent(key, k ->
              CompletableFuture.supplyAsync(() -> {
                  try {
                      Data data = cache.get(key);
                      if (data != null) return data;
                      data = db.get(key);
                      cache.set(key, data);
                      return data;
                  } finally {
                      flights.remove(key);
                  }
              })
          );
          return future.join();
      }
  4. 提前续期(异步刷新)

    1. 思路:在 key 即将过期时,后台异步刷新,避免到期瞬间失效。
  5. 多级缓存(本地缓存 + Redis)

    1. 思路:本地缓存(如 Caffeine)作为第一层,Redis 作为第二层,减少打到 Redis/DB 的请求。

缓存穿透

形成场景
  1. 请求查询一个 key
  2. 该 key 在数据库中不存在
  3. 缓存中也不会有这个 key(因为 DB 没有,不会写入缓存)
  4. 每次请求都会穿透缓存,直接访问数据库
  5. 如果大量并发请求不存在的 key(如恶意刷接口),会导致数据库压力过大
解释说明
  1. 行为可能是恶意的,也可能是正常的业务查询(如用户查询不存在的订单号、商品 id)
  2. 缓存中没有这个 key,数据库中也没有对应的数据
  3. 重点在于:对于不存在的 key,不应该每次都去访问数据库,应该用布隆过滤器先判断,或缓存"不存在"的结果
解决方案
  1. 布隆过滤器

    1. 思路:在查询缓存和数据库之前,先用布隆过滤器判断 key 是否可能存在,如果布隆过滤器返回 false,直接返回"不存在",不查缓存和数据库,如果返回 true,再查缓存和数据库

    2. 前提:在缓存穿透场景下使用布隆过滤器,需要先把"存在的元素"加载到布隆过滤器,并持续维护,否则会误拦所有请求,起不到"防穿透且放行有效数据"的作用。布隆过滤器需要先"知道"哪些元素存在,才能判断"不存在"。标准布隆过滤器不支持删除,若业务会删数据,需要定期重建布隆过滤器,或改用支持删除的布谷鸟过滤器。

    3. 示例

      java 复制代码
      public Data get(String key) {
          // 先用布隆过滤器判断
          if (!bloomFilter.mightContain(key)) {
              return null;  // 一定不存在,直接返回
          }
               
          // 布隆过滤器说可能存在,再查缓存
          Data data = cache.get(key);
          if (data != null) return data;
               
          // 查数据库
          data = db.get(key);
          if (data != null) {
              cache.set(key, data);
          }
          return data;
      }
  2. 缓存空值

    1. 思路:对于查询结果为"不存在"的情况,也写入缓存(如缓存 null 或占位值),设置较短的过期时间(如 5 分钟),避免占用过多内存
  3. 参数校验

    1. 思路:在入口处校验请求参数,过滤明显不合法的请求(如负数 id、超长字符串)

缓存雪崩

形成场景
  1. 大量 key 在同一时刻或短时间内同时失效(例如设置了相同或接近的过期时间)
  2. Redis 节点/集群宕机、网络不可用,导致整块缓存不可用
  3. 此时大量请求发现缓存没有,同时去访问数据库
  4. 数据库瞬时压力剧增,甚至宕机,引发雪崩式故障
解释说明
  1. 行为通常不是恶意,而是设计或运维不当(如批量 key 同 TTL、Redis 故障)
  2. 与击穿不同:击穿是「一个热点 key」失效;雪崩是「大量 key 同时失效」或「整块缓存不可用」
  3. 重点在于:避免大量 key 同时过期,以及保证缓存服务高可用,防止瞬时流量全部打到数据库
解决方案
  1. 过期时间加随机值

    1. 思路:避免所有 key 在同一时刻过期,在基础 TTL 上加随机偏移,把过期时间打散

    2. 示例

      java 复制代码
      int baseTtl = 3600;  // 1 小时
      int randomTtl = baseTtl + ThreadLocalRandom.current().nextInt(0, 300);  // 加 0~300 秒随机
      cache.set(key, value, randomTtl, TimeUnit.SECONDS);
  2. 多级缓存

    1. 思路:本地缓存(如 Caffeine)+ Redis,Redis 不可用时仍有本地缓存兜底

    2. 示例

      java 复制代码
      public Data get(String key) {
          Data data = localCache.get(key);
          if (data != null) return data;
               
          data = redis.get(key);
          if (data != null) {
              localCache.put(key, data);
              return data;
          }
               
          data = db.get(key);
          if (data != null) {
              redis.set(key, data);
              localCache.put(key, data);
          }
          return data;
      }
    3. Redis 高可用

      1. 思路:使用 Redis 哨兵或集群,主节点宕机时自动故障转移,减少因单点故障导致整块缓存不可用
    4. 限流与熔断

      1. 思路:对访问数据库的请求做限流,防止瞬时打满 DB,当 DB 或 Redis 异常时熔断,快速失败,避免雪崩扩散

缓存击穿 / 穿透 / 雪崩 对比

  1. 缓存击穿 / 穿透 / 雪崩 对比表(简要)

    维度 缓存击穿 缓存穿透 缓存雪崩
    key 数量 1 个热点 key 可能很多 key(每次 1 个),都不存在 大量 key 同时失效,或整块缓存挂掉
    是否恶意 一般 非恶意(正常高并发访问热门数据) 可能正常(查不存在),也可能 恶意刷不存在的 key 多为配置/运维问题,通常 非恶意
    缓存是否有数据 此刻 缓存没有 该热点 key 缓存没有该 key(不会有) 大量 key 没缓存 / Redis 不可用
    DB 是否有数据 (DB 能查到) 没有(DB 本来就没这条) 多数 key (DB 能查到)
    现象 同一热点 key 的大量请求在失效瞬间同时打 DB 每次请求都穿透缓存直查 DB,且都是"查不到"的请求 大量本应走缓存的请求在短时间内一起打到 DB,DB 瞬时压力剧增或宕机
    核心解决方案 双检 + 互斥锁、单飞、热点 key 不过期/逻辑过期、提前续期、多级缓存 布隆/布谷鸟过滤器、缓存空值(NULL 占位)、参数校验/黑名单 TTL 加随机偏移、Redis 高可用、多级缓存、限流/熔断、缓存预热、错峰过期

参考资料

  1. https://www.bilibili.com/video/BV13R4y1v7sP

注意事项

  1. 部分内容由AI生成
  2. 如有不对,欢迎指正!!!
相关推荐
计算机程序设计小李同学2 小时前
基于 Spring Boot + Vue 的龙虾专营店管理系统的设计与实现
java·spring boot·后端·spring·vue
shuair2 小时前
guava布隆过滤器及cuckoo过滤器
redis·guava
上架ipa2 小时前
redis图形化客户端功能对比
redis·缓存
qq_12498707534 小时前
基于Java Web的城市花园小区维修管理系统的设计与实现(源码+论文+部署+安装)
java·开发语言·前端·spring boot·spring·毕业设计·计算机毕业设计
Chasmれ4 小时前
Spring Boot 1.x(基于Spring 4)中使用Java 8实现Token
java·spring boot·spring
计算机学姐4 小时前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
落霞的思绪4 小时前
Spring AI Alibaba 集成 Redis 向量数据库实现 RAG 与记忆功能
java·spring·rag·springai
indexsunny5 小时前
互联网大厂Java面试实战:微服务与Spring生态技术解析
java·spring boot·redis·kafka·mybatis·hibernate·microservices
Remember_9935 小时前
MySQL 索引详解:从原理到实战优化
java·数据库·mysql·spring·http·adb·面试