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

一、缓存击穿

(一)概念

某个热点 Key 过期的瞬间,大量并发请求同时打到数据库,导致数据库压力瞬间飙升,甚至被打崩。

复制代码
大量并发请求  ---> 访问同一个热点 key  
                  ↓  
             这个 key 正好过期  
                  ↓  
     所有请求同时绕过 Redis 访问数据库  
                  ↓  
           DB 瞬间压力过大(被打爆)

(二)缓存击穿的后果

1. 数据库压力瞬间飙升

热点 Key 过期后,大量并发请求直接落到数据库

数据库瞬间承受超高并发请求,CPU、IO 占用飙升

2. 系统性能下降

数据库响应变慢 → 接口响应延迟增加

3. 请求可能超时或失败

高并发下,整体系统吞吐量下降

4. 可能触发雪崩效应

一个热点 Key 击穿导致数据库压力过大

可能影响其他业务请求

形成连锁反应,多个 Key 失效 → 系统整体性能下降

5. 运维风险增加

数据库连接耗尽、事务阻塞

CPU/内存占用过高 → 可能导致服务宕机

需要紧急干预,影响业务连续性


(三)触发条件

1. 热点 Key(高访问频率)

**概念:**热点 Key 是指在短时间内被大量请求访问的数据。

**特征:**访问量远高于其他普通 Key,可能占据系统绝大部分流量。

具体示例:

电商网站的某个秒杀商品库存信息

热门文章或新闻详情页

排行榜数据,如"本周最热商品 Top 10"

**为什么关键:**只有热点数据过期,才会有大量请求瞬间打到数据库,引发压力。


2. Key 过期或被淘汰

**概念:**缓存中存放的 Key 可能由于以下原因失效:

**(1)TTL 到期:**设置了过期时间,到时间就失效

**(2)手动删除:**开发或运维手动清理缓存

**(3)内存淘汰:**Redis 达到内存上限,根据 LRU/LFU 策略淘汰 Key

具体示例:

设置 SET product_123 100 EX 60,60 秒后过期

服务器更新产品信息,删除缓存强制刷新

Redis 内存满了,大对象或低频 Key 被淘汰

**为什么关键:**Key 过期或消失后,下一次请求会直接落到数据库,这才是击穿的直接触发点。


3. 高并发访问

**概念:**短时间内大量请求同时访问同一个 Key。

**特征:**瞬时并发量远高于数据库处理能力。

具体示例:

秒杀活动开始时,几千甚至上万用户同时访问同一商品库存

热点文章推送后,瞬间大量用户点击访问

**为什么关键:**如果并发量很小,即使 Key 过期,数据库也能承受压力;只有高并发访问,才会引发真正的缓存击穿。


4. 少了任意一个条件,就不会发生缓存击穿

解释:

热点 Key 不存在 → 就算并发很高,也不会击穿数据库(普通 Key 的请求量本来就小)

Key 没有过期或被淘汰 → 缓存命中,所有请求都打到 Redis,不会落到数据库

高并发访问 不存在 → 即使 Key 过期,也只有少量请求访问数据库,数据库能轻松承受

所以 三个条件必须同时满足,缓存击穿才会发生。


(四)典型场景

1. 热门商品详情

场景描述:

电商平台的秒杀商品或促销商品在短时间内被大量用户访问

每个用户都要查询库存、价格、折扣等信息

为什么容易击穿:

热点 Key:这个商品的缓存是热点,因为秒杀活动期间访问量极高

Key 过期或缓存未命中:TTL 到期,或者缓存被手动刷新

高并发请求:几千甚至上万用户同时访问数据库查询库存

技术后果:

数据库瞬时压力飙升

查询延迟增加 → 可能出现秒杀失败或页面崩溃


2. 排行榜或统计数据

场景描述:

热门文章阅读量、音乐/视频播放排行榜、实时交易额统计

大量用户同时查询"Top N"数据

为什么容易击穿:

热点 Key:排行榜数据被频繁访问

Key 过期:排行榜缓存每隔一段时间刷新一次,TTL 到期或逻辑过期

高并发访问:缓存过期瞬间,所有请求直接打数据库计算排行

技术后果:

数据库需要进行复杂聚合计算,CPU/IO 占用高

多个 Key 可能同时被访问 → 加剧系统压力


3. 系统配置或元数据

场景描述:

经常查询的基础信息,例如用户角色权限、地区列表、字典表数据

访问频率高,但数据量相对固定

为什么容易击穿:

热点 Key:这些 Key 被多次访问

Key 过期或被刷新:配置更新或 TTL 到期

高并发访问:短时间内多个服务/用户请求这些基础数据

技术后果:

一旦缓存失效,基础数据请求直接打数据库

影响整个业务链条的正常访问


(五)根本原因

1. Redis 只是缓存,并非数据库防护墙

Redis 的主要作用是加速数据访问,减少数据库压力

Redis 并不会阻止数据库本身被访问

一旦缓存未命中(Key 过期或被淘汰),请求就直接打数据库

**技术点:**缓存只是读写加速层,它不存储业务逻辑约束,也不限制请求流量


2. 高并发请求集中在失效 Key 上

当一个热点 Key 过期或被删除,瞬间所有访问这个 Key 的请求都会落到数据库

数据库承受能力有限,如果瞬时 QPS 超过数据库峰值 → 查询排队

技术点:

Redis hit rate 高 → 大部分请求在缓存层就被拦截

Key 失效瞬间 → 缓存失效窗口,hit rate 突然变低 → DB 瞬时压力飙升


3. 数据库无法承受大量并发请求

数据库在高并发下可能出现:

(1)CPU 饱和 → 查询速度下降

(2)IO 瓶颈 → 磁盘或网络访问慢

(3)连接耗尽 → 数据库拒绝新连接

(3)事务阻塞 → 并发写操作阻塞其他请求

**结果:**接口响应变慢、请求超时,甚至数据库宕机

**技术点:**缓存击穿不是 Redis 的问题,而是热点数据失效 + 数据库瞬时承载能力不足的系统问题


(六)解决策略

1. 热点 Key 永不过期 / 逻辑过期

思路:

对真正热点的数据,不设置 TTL,让缓存一直存在

或者使用"逻辑过期",即缓存中记录一个过期时间,但读取时仍然可以返回旧数据,同时后台异步刷新

技术实现:

永不过期:SET key value 不设置 EX 参数

逻辑过期:缓存结构如 {value: ..., expireTime: 1234567890}

读取时判断 expireTime 是否过期

如果过期,后台线程异步更新缓存,用户仍然能读取旧值

适用场景:

热门商品、排行榜、系统配置、常用字典数据


2. 互斥锁 / 单线程加载

思路:

当缓存失效时,只有一个线程去加载数据库,其它线程等待缓存更新,避免高并发同时打数据库

技术实现(Java 示例):

java 复制代码
String cache = redis.get(key);
if (cache == null) {
    if (tryLock(key + "_lock")) {        // 尝试获取锁
        String dbData = queryFromDB();   // 查询数据库
        redis.set(key, dbData, 60);      // 回写缓存
        unlock(key + "_lock");           // 释放锁
        return dbData;
    } else {
        Thread.sleep(50);                // 等待一段时间
        return redis.get(key);           // 重试读取缓存
    }
}
return cache;

适用场景:

秒杀活动、热点 Key 高并发访问场景


3. 缓存预热 + 定时刷新

思路:

系统启动或缓存即将过期时提前加载热点数据到缓存

避免缓存失效瞬间出现大量请求落到数据库

技术实现:

缓存预热:系统启动时读取数据库,将热点 Key 加入 Redis

定时刷新:使用定时任务或后台线程,提前更新即将过期的 Key

适用场景:

系统启动后的热门数据

高频访问的排行榜或统计数据


4. 降级处理 / 限流

思路:

当缓存击穿瞬间,可以对部分请求做降级或限流,保护数据库

部分请求直接返回默认值或提示稍后再试

技术实现:

使用限流器(如令牌桶、漏桶算法)限制数据库访问频率

对热点查询返回缓存的旧值或默认值

结合互斥锁或逻辑过期,提高系统容错能力

适用场景:

秒杀活动、热门接口请求暴增场景

保护数据库稳定,保证系统可用性


(七)优化角度

1. 热点识别

**概念:**先识别哪些 Key 是真正的热点数据(访问频率高、压力大的 Key),针对这些 Key 才做特殊处理。

具体做法:

(1)在 Redis 中记录访问频率或使用监控统计访问量

(2)根据访问量排序,识别访问量大的 Key 为热点

(3)对热点 Key 可采取"永不过期"或"逻辑过期 + 异步刷新"等策略

作用:

只针对真正的热点做优化,避免资源浪费

降低缓存击穿风险


2. TTL 差异化

**概念:**给不同 Key 设置不同过期时间,避免大量热点 Key 同时过期。

具体做法:

对普通 Key 设置短 TTL

对热点 Key 设置长 TTL 或逻辑过期

可以给相似热点 Key 设置错开 TTL,避免同时失效

作用:

避免多个热点 Key 同时过期 → 大量请求同时打数据库

平滑系统压力


3. 分布式锁 / 单点加载

**概念:**缓存失效时,保证只有一个请求去访问数据库加载数据,其他请求等待或重试缓存。

具体做法:

使用 Redis 的 SETNX 或 Redisson 提供的分布式锁

第一个请求获取锁去加载数据库

其他请求等待锁释放或读取缓存

作用:

避免高并发请求同时击穿数据库

控制数据库瞬时压力

示例流程:

复制代码
请求1 → 获取锁 → 查询 DB → 回写缓存 → 释放锁
请求2 → 获取锁失败 → 等待 / 重试缓存
请求3 → 同上

4. 逻辑过期 + 异步刷新

**概念:**缓存中存储数据的逻辑过期时间,而不是直接让 Key 过期。

具体做法:

(1)缓存结构:{value: ..., expireTime: timestamp}

(2)读取时判断 expireTime 是否过期

未过期 → 直接返回缓存

已过期 → 后台线程异步刷新缓存,前端仍返回旧值

(3)异步刷新完成后更新缓存

作用:

用户请求不会直接落到数据库

避免缓存失效瞬间击穿数据库

保证系统高并发下可用性


(八)注意事项

1. 分布式系统注意点

在多节点或微服务环境下,如果采用 分布式锁 或 逻辑过期刷新,需要保证跨节点的一致性。

可使用成熟的工具或框架实现分布式锁,例如 Redisson、ZooKeeper 或 Etcd。

这样可以避免多个节点同时刷新缓存,防止数据库压力再次集中。

2. 数据一致性问题

使用 逻辑过期 + 异步刷新 时,可能会在短时间内返回过期的旧数据(脏数据)。

需要根据业务可容忍度判断是否可以接受这种短暂的不一致,例如:排行榜、商品浏览量等统计类数据通常允许短暂延迟。

对于对实时性要求高的关键业务,需额外设计一致性策略。

3. 限流/降级策略的组合使用

在高并发场景下,单独的策略可能不足以完全保护数据库。

推荐组合方案:逻辑过期 + 分布式锁 + 限流/降级

这样可以确保:

请求不会直接击穿数据库

缓存刷新有序

系统在瞬时高并发下仍保持可用性


二、缓存穿透

(一)概念

缓存穿透指的是 查询一个根本不存在的数据时,请求会绕过缓存直接打到数据库,如果这种请求量很大,会导致数据库压力急剧增加。

核心:请求的数据在缓存和数据库中都不存在

表现:缓存永远无法命中,数据库承受全部请求

复制代码
请求数据A(不存在)
       ↓
Redis查询,未命中
       ↓
直接访问数据库
       ↓
数据库也没有该数据
       ↓
返回结果给用户

特点:

数据不存在 → 每次请求都要查询数据库

缓存没有命中 → 每次都落到数据库

高并发下可能造成数据库压力过大


(二)触发原因

1. 用户恶意请求

恶意刷接口,随机生成不存在的 ID 请求

例如:/product/999999999,数据库中根本没有

2. 程序缺陷或参数错误

前端或接口调用传错参数,导致请求不存在的数据

例如:用户传了非法商品 ID 或拼写错误的关键字

3. 缓存未处理空结果

查询不存在的数据,缓存层没有保存空对象

每次请求都走数据库 → 穿透


(三)典型场景

1. 商品查询接口

用户请求不存在的商品 ID

没有缓存空对象 → 每次都打数据库

2. 用户登录或注册校验接口

查询不存在的用户名或手机号

高并发时可能形成数据库压力

3. 通用搜索或统计接口

查询历史不存在的记录或随机关键字

数据库承载能力下降


(四)可能后果

1. 数据库压力增加

大量不存在的数据请求直接落到数据库

CPU、IO 占用增加

2. 接口响应变慢 / 超时

高并发下数据库响应慢,接口可能返回超时

3. 系统可用性降低

数据库过载 → 其他正常请求也受影响

4. 潜在安全风险

恶意请求可能导致拒绝服务(DoS)攻击


(五)解决策略

1. 缓存空对象(Null Cache)

将数据库查询不存在的数据也缓存起来(空对象或特殊标记)

设置较短 TTL 避免缓存无限膨胀

示例:

java 复制代码
String cache = redis.get(key);
if (cache != null) {
    return cache.equals("NULL") ? null : cache;
} else {
    String dbData = queryFromDB();
    if (dbData == null) {
        redis.set(key, "NULL", 60); // 缓存空对象
        return null;
    } else {
        redis.set(key, dbData, 3600);
        return dbData;
    }
}

2. 布隆过滤器(Bloom Filter)

在请求到达缓存/数据库之前,用布隆过滤器快速判断 key 是否存在

不存在的请求直接拦截,避免落到数据库

特点:

支持海量数据

允许少量误判(存在概率误判为存在,但不会漏判)

场景:热门商品 ID、用户 ID 等高频接口


3. 接口层校验 / 参数校验

对传入请求参数做合法性校验

非法 ID、空 ID、格式错误的请求直接拒绝


4. 限流和防刷策略

对接口增加限流、频率限制

防止恶意请求或高并发重复查询不存在数据


(六)进一步优化与注意事项

1. 分布式环境注意点

如果系统是多节点或微服务架构,需要保证布隆过滤器或缓存空对象在各节点的一致性。

可以使用 集中式布隆过滤器 或分布式缓存同步策略,避免不同节点出现数据不一致。

2. 空对象缓存策略优化

缓存空对象时需要控制 TTL,避免占用过多缓存资源。

对于热门但不存在的数据,可以适当加长 TTL;对于随机或恶意请求的数据,TTL 设置短一些即可。

结合日志监控,发现频繁穿透的 Key,可进一步处理或封禁请求源。

3. 结合限流与防刷策略

在高并发或恶意请求场景,单靠缓存空对象或布隆过滤器可能不足。

可以使用 限流 + 防刷,例如:

限制接口每秒请求次数

对异常频繁请求 IP 或用户进行封禁或验证码验证

结合缓存策略可以更有效保护数据库稳定性。

4. 监控与报警

对缓存命中率、数据库查询量进行监控

当发现缓存命中率下降、数据库异常增多时,触发告警

帮助运维及时发现缓存穿透攻击或异常请求


三、缓存雪崩

(一)概念

缓存雪崩指的是大量缓存同时失效,导致大量请求直接打到数据库,使数据库瞬间承受超高压力,可能导致系统不可用。

核心:大量缓存同时过期或失效

表现:数据库压力骤增,接口响应延迟或失败

流程示意:

java 复制代码
大量热点Key同时过期
       ↓
缓存全部未命中
       ↓
请求全部打到数据库
       ↓
数据库承受高压
       ↓
接口响应变慢或宕机

特点:

大量 Key 同时失效 → 大量请求集中落到数据库

高并发下数据库无法承受 → 系统可能整体不可用

不同于缓存击穿:缓存雪崩可能涉及 多个 Key,而击穿通常是 单个热点 Key


(二)触发原因

1. 大量 Key TTL 相同

系统初始化时给缓存设置了统一过期时间,例如都 1 小时

到期瞬间,大量请求同时访问数据库

2. Redis 宕机或重启

Redis 服务不可用 → 缓存全部失效

所有请求直接落到数据库

3. 缓存淘汰策略触发

当 Redis 内存不足,根据 LRU/LFU 策略淘汰大量 Key

瞬时失效 → 请求直接访问数据库


(三)典型场景

1. 秒杀活动或促销活动

活动前预热缓存,设置统一过期时间

活动开始或 TTL 到期 → 大量商品缓存同时过期

大量请求直接打数据库 → 宕机风险

2. 系统启动或 Redis 重启

系统重启或 Redis 重启 → 缓存清空

热门 Key 未预热 → 请求直接落数据库

3. 大规模缓存失效

一些批量刷新或缓存清理操作

例如定时清理排行榜、统计数据

多个 Key 同时失效 → 请求瞬间集中


(四)可能后果

1. 数据库瞬时压力飙升

CPU、IO、连接数暴涨

2. 接口响应延迟或失败

请求排队 → 用户体验差

3. 系统可用性下降

多个业务模块同时受影响 → 链式反应

4. 触发缓存击穿或雪崩放大

一个 Key 的高并发击穿 + 大量 Key 同时过期 → 系统雪崩


(五)解决策略

1. 缓存过期时间错开 / 随机化 TTL

给 Key TTL 增加随机偏移量,避免大量 Key 同时过期

例如:设置 TTL = 3600 ± random(0~300) 秒

2. 缓存预热(Cache Preload)

系统启动或缓存即将失效前,提前加载热点数据到缓存

避免大量请求落到数据库

3. 互斥锁或单线程加载

当缓存失效时,控制只有一个请求访问数据库,其它请求等待

避免高并发直接打数据库

4. 降级与限流

对部分请求返回默认值或提示稍后重试

使用限流器控制访问数据库的速率

5. 多级缓存 / 本地缓存 + Redis

本地缓存存热点数据 → Redis 失效前可以命中本地缓存

降低对 Redis 和数据库瞬时压力

相关推荐
李慕婉学姐2 小时前
Springboot猪肉销售网站的设计与实现mk77pcvj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
en-route2 小时前
深入理解 Redisson 分布式锁
redis·redission
摇滚侠2 小时前
Redis 零基础到进阶,类型概述,命令查询,key 操作命令,大小写和帮助命令,string,list,hash,set,笔记11-19
redis·笔记·哈希算法
嘻哈baby2 小时前
Redis突然变慢,排查发现是BigKey惹的祸
数据库·redis·缓存
旧梦吟2 小时前
脚本 生成图片水印
前端·数据库·算法·golang·html5
祖国的好青年2 小时前
XAMPP出现Error: MySQL shutdown unexpectedly.
数据库·mysql
Kiddyup2 小时前
记一次Redis连接泄露问题排查
redis·排查问题
梓沂2 小时前
dockercompose启动mysql容器和springboot项目容器时,mysql容器启动慢导致springboot项目容器启动失败
数据库·spring boot·mysql
CodeAmaz3 小时前
MySQL 各种锁机制详解
数据库·mysql·mysql锁