在 Redis 缓存的实际应用中,缓存击穿、缓存穿透、缓存雪崩是三种典型的 “缓存失效” 问题,它们会导致大量请求直接穿透到数据库,引发数据库压力骤增甚至宕机,严重影响系统稳定性。三者的核心差异在于 “触发场景” 和 “影响范围”,以下从定义、成因、解决方案三个维度展开详细解析,帮助你精准识别和应对。
一、缓存击穿(Cache Breakdown)
1. 定义
缓存击穿是指:某一个 “热点 Key”(如热门商品、热门文章 ID)的缓存突然失效(过期 / 被删除),此时恰好有大量并发请求访问该 Key,导致所有请求瞬间穿透到数据库,数据库在短时间内承受巨大压力,甚至被打垮。
简单说:“一个热门 Key 的缓存挂了,所有请求都去查数据库”。
2. 核心成因
- 热点 Key 过期:缓存设置了过期时间,到期后自动失效,而业务中该 Key 的访问量极高(如电商秒杀商品、热搜话题)。
- 主动删除热点 Key:运维或业务操作中误删除了热点 Key 的缓存,且未及时重建。
3. 解决方案(按优先级排序)
针对 “热点 Key 失效后并发重建缓存” 的核心矛盾,解决方案围绕 “避免并发重建” 和 “延长热点 Key 生命周期” 展开:
方案 | 原理 | 优点 | 注意事项 |
---|---|---|---|
互斥锁(Mutex) | 当缓存失效时,只有一个线程能获取锁去查询数据库并重建缓存,其他线程等待重试。 | 保证缓存数据一致性,无脏数据 | 需避免锁超时(如使用 Redis 的SET NX EX ),可能有短暂等待延迟 |
热点 Key 永不过期 | 对热点 Key 不设置过期时间,通过业务代码手动更新缓存(如商品价格变动时主动更新)。 | 彻底避免击穿,性能最优 | 需确保业务层有 “主动更新” 逻辑,避免缓存数据陈旧 |
预热 + 定时刷新 | 系统启动时提前将热点 Key 加载到缓存(预热),并通过定时任务(如每隔 5 分钟)主动刷新缓存,避免过期。 | 减少运行时失效风险,稳定性高 | 需准确识别热点 Key(如通过访问日志统计),否则资源浪费 |
熔断降级 | 当缓存失效且数据库压力超过阈值时,暂时返回默认值(如 “商品暂时无法访问”),拒绝请求穿透到数据库。 | 保护数据库,防止雪崩蔓延 | 需合理设置熔断阈值,避免过度降级影响用户体验 |
二、缓存穿透(Cache Penetration)
1. 定义
缓存穿透是指:请求查询的是 “不存在的数据”(如用户查询 ID=-1 的商品、邮箱格式错误的用户),这类数据既不在缓存中,也不在数据库中。由于缓存无法命中,所有请求都会穿透到数据库,若存在恶意攻击(如大量构造不存在的 Key 请求),会导致数据库持续承受无效查询,最终崩溃。
简单说:“查一个根本不存在的数据,缓存和数据库都没有,所有请求都打数据库”。
2. 核心成因
- 业务逻辑漏洞:如用户输入校验不严格,允许查询非法 ID(如负数、超长字符串)。
- 恶意攻击:黑客通过脚本批量发送 “不存在 Key” 的请求(如每秒 10 万次查询 ID=100000~200000 的不存在商品),意图耗尽数据库资源。
3. 解决方案(按防御强度排序)
针对 “不存在的数据穿透”,核心思路是 “在缓存层拦截无效请求”,避免其到达数据库:
方案 | 原理 | 优点 | 注意事项 |
---|---|---|---|
参数校验 + 业务过滤 | 在接口层对请求参数进行严格校验(如 ID 必须为正整数、邮箱格式必须合法),直接拒绝非法请求。 | 成本最低,从源头拦截无效请求 | 需覆盖所有业务场景,避免校验遗漏 |
缓存空值(Null Cache) | 若数据库查询结果为 “不存在”,则在缓存中存储该 Key 对应的空值(如Key=-1, Value=null ),并设置较短的过期时间(如 5 分钟)。 | 简单易实现,拦截重复无效请求 | 需控制空值缓存的数量和过期时间,避免缓存膨胀 |
布隆过滤器(Bloom Filter) | 提前将数据库中所有 “有效 Key”(如所有商品 ID、用户 ID)存入布隆过滤器,请求到来时先通过过滤器判断 Key 是否存在:若不存在,直接拒绝;若存在,再查缓存和数据库。 | 拦截效率极高(O (1) 时间复杂度),适合海量 Key 场景 | 存在 “误判率”(需合理设置哈希函数数量和位数),且不支持删除操作(需用布谷鸟过滤器等改进方案) |
IP 限流 + 黑名单 | 对单个 IP 的请求频率进行限制(如每秒最多 10 次),对频繁发送无效请求的 IP 加入黑名单,禁止访问。 | 针对性防御恶意攻击 | 需避免误封正常用户 IP,可结合动态黑名单策略 |
三、缓存雪崩(Cache Avalanche)
1. 定义
缓存雪崩是指:在某一时间段内,缓存中 “大量 Key 集中失效”(如同一时间过期),或 Redis 集群整体故障(如断电、网络中断),导致所有依赖这些 Key 的请求全部穿透到数据库。数据库在短时间内承受远超平时的流量,最终因过载而宕机,进而引发整个系统的连锁故障。
简单说:“大量 Key 的缓存同时挂了,或者 Redis 直接挂了,所有请求都打数据库,系统雪崩”。
2. 核心成因
- Key 集中过期:缓存初始化时,大量 Key 设置了相同的过期时间(如电商活动开始时,所有商品缓存都设置 24 小时过期,24 小时后集中失效)。
- Redis 集群故障:Redis 主从 / 哨兵 / Cluster 集群因硬件故障(如服务器断电)、网络问题(如机房断网)、软件 BUG(如内存溢出)导致整体不可用。
3. 解决方案(分 “预防过期雪崩” 和 “预防集群故障” 两类)
(1)预防 “Key 集中过期” 导致的雪崩
核心思路是 “打散 Key 的过期时间”,避免集中失效:
- 过期时间加随机值:在设置基础过期时间(如 24 小时)的同时,额外增加一个随机值(如 0
1 小时),使 Key 的实际过期时间分散在 2425 小时之间,避免集中失效。
示例代码(伪代码):expire(key, 24*3600 + rand(0, 3600))
- 热点 Key 分层缓存:对核心热点 Key(如首页 Banner、秒杀商品),采用 “多级缓存”(本地缓存 + Redis 缓存)。即使 Redis 缓存失效,请求会先命中本地缓存(如 Java 的 Caffeine),避免直接穿透到数据库。
- 缓存分片:将缓存 Key 按业务维度分片(如商品 ID%100,分为 100 个分片),每个分片的 Key 设置不同的基础过期时间(如分片 1 过期 24 小时,分片 2 过期 25 小时),进一步打散过期时间。
(2)预防 “Redis 集群故障” 导致的雪崩
核心思路是 “提高 Redis 可用性” 和 “降级保护数据库”:
- Redis 高可用架构:部署主从复制 + 哨兵集群(至少 3 个哨兵节点)或 Redis Cluster(至少 3 个主节点),确保单个节点故障时,哨兵 / Cluster 能自动完成故障转移,快速恢复缓存服务。
- 熔断降级 + 限流:当 Redis 集群不可用时,通过熔断机制(如 Spring Cloud Circuit Breaker)暂时切断缓存查询,直接返回默认值或降级页面(如 “服务暂时繁忙”);同时对数据库设置请求限流(如每秒最多处理 1000 次查询),避免数据库被冲垮。
- 持久化兜底:开启 Redis 的 AOF 持久化(并配置
appendfsync everysec
)和 RDB 持久化,即使 Redis 集群彻底故障,也能通过 AOF/RDB 文件快速恢复数据,减少缓存不可用的时间。
三者对比与总结
为了更清晰地区分三者,以下是核心差异对比表:
维度 | 缓存击穿 | 缓存穿透 | 缓存雪崩 |
---|---|---|---|
触发条件 | 单个热点 Key 失效 | 请求不存在的 Key | 大量 Key 集中失效 / Redis 集群故障 |
影响范围 | 局部(仅该 Key 的请求) | 局部(无效请求)→ 全局(数据库压力) | 全局(所有依赖缓存的请求) |
核心矛盾 | 并发重建缓存 | 无效请求穿透 | 缓存整体不可用 |
典型场景 | 秒杀商品缓存过期 | 黑客批量查询非法 ID | 凌晨 3 点缓存集中过期、Redis 机房断电 |
核心应对原则
- 优先预防:通过 “热点 Key 永不过期”“过期时间随机化”“布隆过滤器” 等方案,从源头减少问题发生的概率。
- 分层防御:结合 “缓存层(互斥锁)→ 接口层(参数校验)→ 数据库层(限流)”,形成多层防护,避免单点故障引发全局问题。
- 业务适配:根据业务场景选择方案(如金融场景需强一致性,优先用互斥锁;非核心业务可接受降级,优先用熔断)。