Redis 三大缓存问题:缓存穿透、缓存击穿、缓存雪崩

Redis 虽然拥有极高的读写性能,但缓存并不是万能的。在高并发场景下,如果缓存设计不合理,就可能导致大量请求直接访问数据库,甚至引发数据库过载。

本文从 产生原因、解决方案、代码示例、注意事项 四个方面,梳理 Redis 最经典的三大缓存问题。

文章目录

    • 一、缓存穿透:查询不存在的数据
      • [1. 什么是缓存穿透?](#1. 什么是缓存穿透?)
      • [2. 解决方案](#2. 解决方案)
      • [3. 适用场景](#3. 适用场景)
    • [二、缓存击穿:热点 Key 突然失效](#二、缓存击穿:热点 Key 突然失效)
      • [1. 什么是缓存击穿?](#1. 什么是缓存击穿?)
      • [2. 解决方案](#2. 解决方案)
      • [3. 适用场景](#3. 适用场景)
    • 三、缓存雪崩:大量缓存同时失效
      • [1. 什么是缓存雪崩?](#1. 什么是缓存雪崩?)
      • [2. 解决方案](#2. 解决方案)
      • [3. 适用场景](#3. 适用场景)
    • 四、三大缓存问题对比与实战注意事项
      • [1. 三大问题对比](#1. 三大问题对比)
      • [2. 实际开发中的注意事项](#2. 实际开发中的注意事项)
    • 总结

一、缓存穿透:查询不存在的数据

1. 什么是缓存穿透?

缓存穿透是指用户查询的数据 在 Redis 和数据库中都不存在。由于缓存无法命中,请求每次都会访问数据库,导致大量无效查询消耗数据库资源。

例如,用户不断请求 user:999999,但这个用户实际上并不存在。

⚠️ 核心问题: 缓存和数据库都没有数据,请求无法被缓存拦截。

2. 解决方案

方案一:缓存空值

当数据库查询不到数据时,将一个特殊的空值标记写入 Redis,并设置较短的过期时间。

go 复制代码
// 将查询不存在的结果缓存 60 秒
err := rdb.Set(ctx, "user:999999", "__NULL__", 60*time.Second).Err()

后续请求命中这个标记后,就可以直接返回数据不存在的结果,无须重复查询数据库。

优点: 实现简单,能够有效减少重复的无效查询。

缺点: 会占用一定的缓存空间;如果大量随机 Key 都被缓存,也可能浪费内存。

💡 注意: 必须区分"缓存未命中"和"缓存中存储了空值标记"。如果数据库新增了原本不存在的数据,还要考虑空值缓存导致的短暂数据不一致问题。

方案二:布隆过滤器(Bloom Filter)

布隆过滤器是一种节省空间的数据结构,用于快速判断某个数据是否可能存在。

  • ✅ 判断为不存在:直接拦截请求,不访问数据库。
  • ⚠️ 判断为可能存在:继续查询 Redis 或数据库。
  • ❗ 可能出现误判:把不存在的数据判断为可能存在,但在正确实现和维护的前提下,不会把实际存在的数据判断为不存在。
text 复制代码
用户请求
   ↓
布隆过滤器判断
   ├── 一定不存在 → 直接返回
   └── 可能存在   → 查询 Redis → 必要时查询 MySQL

优点: 能够在大量无效请求到达数据库之前进行拦截。

缺点: 存在一定的误判率;数据新增、删除时需要设计合理的维护策略。

方案三:参数校验

在请求进入缓存和数据库之前,先检查参数是否合法。例如,用户 ID 必须为正整数,明显非法的参数直接拒绝。

3. 适用场景

  • 缓存空值: 适合不存在的数据可能被反复查询的业务。
  • 布隆过滤器: 适合数据规模较大、无效查询较多的业务。
  • 参数校验: 适合能够提前识别非法请求的场景。

二、缓存击穿:热点 Key 突然失效

1. 什么是缓存击穿?

缓存击穿是指某个被大量请求访问的热点 Key 突然过期或被删除,导致大量并发请求同时访问数据库。

例如,某件商品正在参加秒杀活动,其缓存恰好过期,大量用户同时查询商品详情,数据库就可能瞬间承受巨大的压力。

⚠️ 核心问题: 单个热点 Key 失效,引发大量并发请求同时回源。

2. 解决方案

方案一:互斥锁(Mutex Lock)

当缓存失效时,只允许一个请求获得锁并查询数据库、重建缓存,其他请求等待、重试或返回降级结果。

text 复制代码
缓存未命中
    ↓
尝试获取互斥锁
    ├── 获取成功 → 查询数据库 → 重建缓存 → 释放锁
    └── 获取失败 → 等待、重试或返回降级结果

优点: 能够控制并发回源的数量,避免大量请求同时查询数据库。

缺点: 可能增加请求延迟;锁的使用不当还可能造成死锁、重复回源等问题。

💡 注意: 实际开发中需要考虑锁的过期时间、异常释放、等待超时等问题。分布式场景下,不能简单地认为执行一次 SETNX 就解决了所有锁安全问题。

方案二:逻辑过期

不直接依靠 Redis Key 的 TTL 删除数据,而是在缓存数据中保存一个逻辑过期时间。

当数据超过逻辑过期时间时:

  1. 请求仍然读取旧缓存,避免大量请求同时访问数据库。
  2. 只有一个请求负责异步刷新缓存。
  3. 刷新成功后,用新数据替换旧数据。
text 复制代码
请求到达
   ↓
读取缓存
   ↓
判断逻辑过期时间
   ├── 未过期 → 返回缓存数据
   └── 已过期 → 返回旧数据,并触发异步刷新

优点: 能够降低请求等待时间,适合高并发读取场景。

缺点: 可能返回旧数据,不适合所有业务。

❗ 注意: 逻辑过期不是让数据永远不更新。必须配合后台刷新机制,并考虑刷新失败、重复刷新和数据一致性问题。

方案三:热点数据主动刷新

对于访问量极高的数据,可以通过定时任务、消息通知或业务更新事件提前刷新缓存,降低热点 Key 过期时的并发冲击。

3. 适用场景

  • 互斥锁: 适合希望严格控制数据库回源并发量的场景。
  • 逻辑过期: 适合允许短时间读取旧数据的高并发业务。
  • 主动刷新: 适合商品详情、热门文章等访问频率较高的数据。

三、缓存雪崩:大量缓存同时失效

1. 什么是缓存雪崩?

缓存雪崩是指大量缓存 Key 在短时间内集中失效,或者 Redis 服务整体不可用,导致大量请求涌向数据库。

常见原因主要有两种:

  1. 大量 Key 设置了相同或相近的过期时间,导致缓存集中失效。
  2. Redis 出现故障,导致大量缓存请求无法正常处理。

例如,系统批量缓存了大量商品数据,并统一设置为 30 分钟后过期。到期后,大量请求同时访问数据库,可能引发数据库过载。

⚠️ 核心问题: 受到影响的不再只是一个热点 Key,而可能是大量缓存数据甚至整个缓存服务。

2. 解决方案

方案一:过期时间增加随机值

为每个 Key 的 TTL 增加随机偏移量,避免大量缓存同时过期。

go 复制代码
// 在 30 分钟基础上增加 0~299 秒的随机时间
ttl := 30*time.Minute +
    time.Duration(rand.Intn(300))*time.Second

err := rdb.Set(ctx, key, value, ttl).Err()

优点: 实现简单,能够分散缓存过期时间。

缺点: 只能缓解集中失效,无法解决 Redis 服务整体故障。

💡 注意: 随机值的范围应根据业务流量和缓存规模确定。Go 中使用 math/rand 时,也要结合所用 Go 版本及并发场景选择合适的随机数使用方式。

方案二:构建 Redis 高可用架构

通过 Redis Sentinel(哨兵)或 Redis Cluster(集群)等方式,提高缓存服务的可用性。

  • 哨兵模式: 监控主从节点,在主节点发生故障时执行故障转移。
  • 集群模式: 通过数据分片和节点冗余等机制,提升扩展能力与故障处理能力。

优点: 降低单点故障对业务的影响。

缺点: 架构和运维复杂度增加;故障转移期间仍可能出现短暂的请求失败。

❗ 注意: 高可用不等于绝对不会发生故障。还需要做好容量规划、故障演练、监控告警和客户端重试控制。

方案三:限流、熔断与降级

当 Redis 故障或数据库压力过大时,对请求进行限流,限制进入数据库的请求数量。

  • 限流: 控制单位时间内允许处理的请求数量。
  • 熔断: 当下游服务持续异常时,暂时停止调用,避免故障扩散。
  • 降级: 非核心业务可以返回默认值、旧数据或提示稍后重试。

优点: 能够保护数据库和核心业务,避免故障进一步扩大。

注意: 降级策略必须结合业务设计。支付、库存等核心操作不能简单地返回成功或使用过期数据完成写入。

方案四:多级缓存

在 Redis 前增加本地缓存,例如 Go 项目中的进程内缓存,用于保存部分热点数据。

text 复制代码
用户请求
   ↓
本地缓存
   ├── 命中 → 直接返回
   └── 未命中
         ↓
       Redis
         ├── 命中 → 返回数据
         └── 未命中 → 查询 MySQL

优点: Redis 出现短暂故障时,本地缓存仍可能承担一部分读取请求。

缺点: 多实例之间可能出现数据不一致,还需要考虑本地缓存的容量和更新策略。

3. 适用场景

  • 随机 TTL: 适合批量缓存、定时刷新等场景。
  • Redis 高可用: 适合对缓存服务可用性要求较高的系统。
  • 限流降级: 适合需要保护数据库和核心业务的系统。
  • 多级缓存: 适合热点数据明显、读取频率较高的业务。

四、三大缓存问题对比与实战注意事项

1. 三大问题对比

对比维度 缓存穿透 缓存击穿 缓存雪崩
核心原因 查询的数据不存在 热点 Key 失效 大量 Key 集中失效或 Redis 故障
影响范围 无效查询反复访问数据库 单个或少数热点数据 大量缓存请求受到影响
典型表现 数据库反复执行无效查询 大量并发请求同时回源 数据库请求量突然暴增
主要方案 缓存空值、布隆过滤器 互斥锁、逻辑过期 随机 TTL、高可用、限流降级
核心目标 拦截无效查询 控制热点数据回源 分散失效并保护系统

2. 实际开发中的注意事项

① 缓存问题需要组合治理

不要认为使用某一种方案就能解决所有问题。例如,随机 TTL 可以缓解缓存雪崩,但不能阻止非法请求造成的缓存穿透。

② 缓存一致性不能忽略

数据库更新后,缓存可能仍然保存旧数据。需要结合业务场景选择合适的缓存更新、删除与重建策略。

③ 不要让缓存故障演变为数据库故障

当 Redis 不可用时,如果所有请求都直接访问 MySQL,缓存故障就可能进一步演变为数据库过载。应结合限流、超时、熔断和降级保护数据库。

④ 热点数据需要重点监控

可以关注缓存命中率、Redis 延迟、数据库 QPS、连接池使用情况、缓存 Key 过期情况以及异常请求比例,及时发现潜在风险。

⑤ 核心业务必须保证数据正确性

对于支付、库存扣减等业务,不能只依赖缓存。需要结合数据库事务、原子操作、幂等机制等手段,保证业务数据的正确性。


总结

Redis 三大缓存问题可以用三个关键词记忆:

  • 🔍 缓存穿透:查不到。 缓存和数据库中都没有数据,使用缓存空值、布隆过滤器等方式拦截。
  • 🔥 缓存击穿:热点失效。 热门 Key 突然过期,使用互斥锁、逻辑过期或主动刷新控制并发回源。
  • 🌊 缓存雪崩:大面积失效。 大量缓存同时过期或 Redis 故障,使用随机 TTL、高可用架构、限流降级等方式降低影响。

真正可靠的缓存设计,不是只解决某一种问题,而是从请求拦截、缓存更新、并发控制、服务可用性和数据库保护等多个层面建立防护机制。

相关推荐
这是谁的博客?1 小时前
AI 芯片存算一体(PIM/CIM)架构深度解析:从冯·诺依曼瓶颈与近存计算到 RRAM 模拟矩阵乘的 LLM 推理显存墙突破原理
ai·pim·1024程序员节·cim·存算一体·hbm-pim·lpddr5x-pim
WebKoalaBoy1 小时前
一台电脑同时配置多个 Git 账号:从原理到实战
1024程序员节
啵啵啵鱼1 小时前
用 Claude Code 重构遗留系统:从评估到落地的完整实践
数据结构·1024程序员节
I Am a robert girl1 小时前
当扩散编辑器“看不见”参考图:从 RefGAP 拆解参考注意力分配问题
计算机视觉·注意力机制·扩散模型·1024程序员节·参考引导编辑·refgap·免训练方法
jinbao_x1 小时前
关于git提交代码时的LF、CRLF提示的问题
1024程序员节
troy1281 小时前
构建金融系统 Reference Data Asset 的完整指南
1024程序员节
鲁Q同志1 小时前
IDEA无限重置激活三十天
intellij-idea·1024程序员节
紧跟先前的步伐1 小时前
Go AI Agent 实战 14|多轮对话与上下文历史管理
人工智能·ai·golang·1024程序员节
ss2731 小时前
AI时代首先数据库-PostgreSQL实战笔记 | 25 PostgreSQL checkpoint与脏页处理机制
1024程序员节