当前位置: 首页 > news >正文

一文搞懂:缓存三大问题(击穿、穿透、雪崩)原理及全套解决方案

缓存的击穿、穿透、雪崩是后端开发中高频出现、极易引发服务雪崩的核心问题,三者看似相似但原理和解决方案完全不同。本文用「原理+场景+落地方案+代码」的形式,一次性讲透,看完就能直接落地到生产环境。

先分清:三大问题核心区别(一张表看懂)

问题类型 核心定义 典型场景 直接后果
缓存穿透 请求根本不存在的key,缓存和DB都查不到 恶意攻击(批量查不存在的用户ID)、业务bug(传错参数) DB被大量无效请求打满,连接耗尽
缓存击穿 热点key突然失效(过期/被删除),大量请求瞬间打到DB 秒杀商品key过期、热门榜单key被删除 DB单点压力骤增,瞬间超时/宕机
缓存雪崩 大量缓存key同时失效,或缓存服务整体宕机 缓存key集中设置相同过期时间、Redis集群宕机 DB被海量请求压垮,整个服务不可用

一、缓存穿透:查「不存在的key」把DB打崩

1. 原理

请求的key在缓存中不存在,且在数据库中也不存在,导致每次请求都「穿透」缓存直接打在DB上。如果是恶意高频请求(比如每秒上万次查不存在的用户ID),DB很快会被压垮。

2. 全套解决方案(按优先级排序)

方案1:参数校验(最基础,成本最低)

在请求到达缓存/DB前,先做合法性校验,过滤掉明显无效的请求:

  • 比如用户ID是正整数,直接拦截负数/0/超长字符串;
  • 比如商品ID有固定格式,拦截不符合格式的请求。

代码示例(Java)

public User getUserById(Long userId) {// 第一步:参数合法性校验,直接拦截无效请求if (userId == null || userId <= 0 || userId > 10000000) {return null;}// 后续缓存/DB查询逻辑...
}

方案2:空值缓存(核心方案)

对查询结果为空的key,也写入缓存(设置极短的过期时间,比如1-5分钟),避免后续请求重复打DB。

代码示例(Java + Redis)

public User getUserById(Long userId) {String cacheKey = "user:" + userId;// 1. 查缓存String userJson = redisTemplate.opsForValue().get(cacheKey);// 空值标记(避免JSON解析问题,用特定字符串表示空)if ("NULL".equals(userJson)) {return null;}if (userJson != null) {return JSON.parseObject(userJson, User.class);}// 2. 缓存未命中,查DBUser user = userMapper.selectById(userId);if (user == null) {// 3. 空值写入缓存,设置短过期时间(防止恶意攻击占满缓存)redisTemplate.opsForValue().set(cacheKey, "NULL", 5, TimeUnit.MINUTES);return null;}// 4. 正常数据写入缓存redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), 30, TimeUnit.MINUTES);return user;
}

方案3:布隆过滤器(高并发/海量key场景)

如果无效key的范围极大(比如电商场景查不存在的商品ID),用布隆过滤器提前判断key是否存在,不存在则直接返回,完全拦截无效请求。

核心逻辑

  1. 启动时将DB中所有有效key加载到布隆过滤器;
  2. 请求过来先过布隆过滤器,不存在则直接返回;
  3. 存在则走正常缓存/DB流程。

代码示例(Guava布隆过滤器)

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;// 初始化布隆过滤器(预计100万key,误判率0.01)
private static BloomFilter<Long> userBloomFilter = BloomFilter.create(Funnels.longFunnel(),1000000,0.01
);// 启动时加载所有有效用户ID到过滤器
@PostConstruct
public void initBloomFilter() {List<Long> allUserId = userMapper.selectAllUserId();for (Long userId : allUserId) {userBloomFilter.put(userId);}
}public User getUserById(Long userId) {// 第一步:布隆过滤器拦截无效keyif (!userBloomFilter.mightContain(userId)) {return null;}// 后续缓存/DB查询逻辑...
}

方案4:限流/熔断(兜底方案)

用Sentinel/Hystrix对接口做限流,当QPS超过阈值时直接拒绝;或监控DB压力,达到阈值时熔断缓存穿透的请求,避免DB被打垮。

缓存穿透方案总结

  • 基础:参数校验
  • 核心:空值缓存(中小规模)、布隆过滤器(大规模)
  • 兜底:限流/熔断

二、缓存击穿:热点key失效导致DB单点压力暴增

1. 原理

某个高频访问的「热点key」(比如秒杀商品、热门榜单)突然过期/被删除,大量请求瞬间绕过缓存直接访问DB,导致DB单点压力骤增,甚至宕机。

2. 全套解决方案(按优先级排序)

方案1:热点key永不过期(最简单)

对核心热点key,不设置过期时间,由业务代码主动更新/删除,避免被动过期。

注意:需在代码中保证热点key的更新逻辑(比如商品价格修改时,主动更新缓存),防止缓存脏数据。

方案2:互斥锁(分布式锁)(最通用)

当缓存失效时,不是所有请求都去查DB,而是只有一个请求获取锁后去查DB并更新缓存,其他请求等待锁释放后直接查缓存。

代码示例(Redis分布式锁)

public User getHotUserById(Long userId) {String cacheKey = "hot_user:" + userId;String lockKey = "lock:hot_user:" + userId;// 1. 查缓存String userJson = redisTemplate.opsForValue().get(cacheKey);if (userJson != null) {return JSON.parseObject(userJson, User.class);}// 2. 缓存失效,尝试获取分布式锁Boolean lockSuccess = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);if (lockSuccess) {try {// 3. 获取锁成功,查DB并更新缓存User user = userMapper.selectById(userId);if (user != null) {redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(user), 60, TimeUnit.MINUTES);}return user;} finally {// 4. 释放锁redisTemplate.delete(lockKey);}} else {// 5. 未获取锁,等待50ms后重试try {Thread.sleep(50);} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 递归重试return getHotUserById(userId);}
}

方案3:提前预热(主动更新)

在热点key过期前,主动查询DB并更新缓存,避免过期瞬间的请求冲击。

  • 比如定时任务:在key过期前10分钟,主动加载最新数据到缓存;
  • 比如监控热点key的过期时间,临近过期时触发更新。

方案4:本地缓存(多级缓存)

对极致热点key,增加「本地缓存」(比如Caffeine/Guava Cache),请求先查本地缓存,再查分布式缓存,最后查DB,进一步降低分布式缓存失效的影响。

缓存击穿方案总结

  • 简单版:热点key永不过期
  • 通用版:分布式互斥锁
  • 进阶版:提前预热 + 多级缓存

三、缓存雪崩:大量key同时失效/缓存宕机导致服务雪崩

1. 原理

两种场景会引发缓存雪崩:

  1. 大量缓存key在同一时间段集中过期,海量请求瞬间打向DB;
  2. 缓存服务(如Redis集群)整体宕机,所有请求直接穿透到DB。

最终导致DB压力暴增,服务不可用,甚至整个系统雪崩。

2. 全套解决方案(分场景应对)

场景1:大量key集中过期 → 打散过期时间

核心思路:给每个key的过期时间增加随机值,避免所有key同时过期。

代码示例(Java)

// 基础过期时间30分钟,随机增加0-10分钟
int baseExpire = 30;
int randomExpire = new Random().nextInt(10);
redisTemplate.opsForValue().set(cacheKey, value, baseExpire + randomExpire, TimeUnit.MINUTES);

场景2:缓存服务宕机 → 提高缓存可用性

  1. 缓存集群化:使用Redis主从+哨兵/Redis Cluster,避免单点故障;
  2. 熔断降级:用Sentinel/Hystrix监控缓存服务状态,当缓存不可用时,暂时熔断请求(返回默认值/提示),避免DB被打垮;
  3. 限流:对所有请求做限流,即使缓存宕机,DB也只接收可控的请求量;
  4. 多级缓存:增加本地缓存(Caffeine),即使分布式缓存宕机,本地缓存仍能承接部分请求;
  5. 缓存降级:缓存宕机时,返回兜底数据(如商品默认价格、空列表),保证服务不挂。

代码示例(熔断降级伪代码)

public User getUserById(Long userId) {try {// 先查分布式缓存String userJson = redisTemplate.opsForValue().get("user:" + userId);if (userJson != null) {return JSON.parseObject(userJson, User.class);}} catch (Exception e) {// 缓存异常,触发降级,返回本地缓存/兜底数据log.error("缓存异常,触发降级", e);return getLocalCacheUser(userId); // 本地缓存兜底}// 缓存异常/未命中,查DB(同时限流)return userMapper.selectById(userId);
}

场景3:终极兜底 → DB层防护

即使缓存完全不可用,也要保证DB不被打垮:

  • DB读写分离/分库分表,提高DB处理能力;
  • DB加连接池限流,避免连接数耗尽;
  • 对DB查询做缓存(比如MyBatis一级/二级缓存)。

缓存雪崩方案总结

  • 预防集中过期:过期时间加随机值
  • 提高缓存可用性:集群化 + 熔断降级 + 多级缓存
  • 终极兜底:DB层限流 + 读写分离

四、三大问题核心方案对比表

问题类型 核心原因 首选方案 兜底方案
缓存穿透 查不存在的key 空值缓存(中小规模)、布隆过滤器(大规模) 参数校验 + 限流
缓存击穿 热点key失效 分布式互斥锁 热点key永不过期
缓存雪崩 大量key过期/缓存宕机 过期时间随机化 + 缓存集群化 熔断降级 + DB限流

总结

  1. 缓存穿透:核心是拦截无效请求,用空值缓存/布隆过滤器挡住不存在的key;
  2. 缓存击穿:核心是保护热点key,用分布式锁避免并发查DB,或直接设置永不过期;
  3. 缓存雪崩:核心是提高可用性 + 打散风险,过期时间随机化防集中失效,集群化+熔断降级防缓存宕机;
  4. 所有方案都需配合限流/熔断作为兜底,确保极端场景下服务不雪崩。

如果需要某类方案的完整生产级代码(比如Redis分布式锁、布隆过滤器落地、Sentinel限流配置),可以告诉我,我会补充对应的可直接运行的代码。

http://www.jsqmd.com/news/450359/

相关文章:

  • 你的电视 2.3.8 | 空壳直播软件,支持多个线路,附直播源
  • SpringBoot实战:高效实现API限流策略
  • 基于Java+SSM+Flask疫情防控管理系统(源码+LW+调试文档+讲解等)/疫情防控/管理系统/防疫管控/公共卫生/健康管理/疫情监测/疾病控制/病毒防范/流行病学/疫情报告/健康监测/疫区管理
  • 2026年河北抖音短视频代运营5强推荐榜单发布 - 精选优质企业推荐榜
  • AI 数学的秘密花园:09.多头注意力是什么?(一群专家分工合作,竞争又抱团)
  • 复杂 Agent 系统的 10 个核心设计模式(源码级)
  • Harmonyos应用实例十七:找规律——图形与数列规律
  • Swoole的利弊的核心概念的庖丁解牛
  • nodejs vue3农产品网上商城系统 半亩菜园线上预售系统
  • Dify 助力企业级 AI Agents 开发:2026 最新真实案例深度解析与实战指南
  • C#上位机PLC通信全栈实战:西门子/三菱/欧姆龙/汇川全品牌通用框架,一次开发终身复用
  • HarmonyOS APP开发:从理论到实践
  • 【2026年最新600套毕设项目分享】基于BS的企业财务管理信息系统(14071)
  • 每天了解几个MCP SERVER:让 AI 能够获取股票、加密货币等市场数据Alpaca
  • GUI学习——day3
  • 基于vue+nodejs的大学生实习招聘系统
  • vue基于nodejs的电子外设销售商城系统
  • 工程设计类学习(DAY13):SMT红胶制程:电子制造的工艺奥秘
  • 动环监控的优势是什么?它如何助力机房运维管理的智能化升级?
  • 科研党收藏!巅峰之作的降AIGC平台 —— 千笔·专业降AIGC智能体
  • 浏览器内浏览器钓鱼攻击的演进机制与防御策略研究——基于Facebook BitB案例的实证分析
  • 2026年江西抖音短视频代运营5强推荐榜单发布 - 精选优质企业推荐榜
  • [特殊字符] 免费!用 Windows11+飞书+Qwen网页版,10分钟搭建你的 OpenClaw 小龙虾智能体
  • VLA 动作序列生成深度解析
  • 实测才敢推 9个降AI率平台测评对比,专科生必看的降AI率神器
  • 2026年湖南抖音短视频代运营公司排行榜TOP5公布 - 精选优质企业推荐榜
  • 完整、结构化的复杂 Agent 系统模板
  • Python+ai技术的微信小程序 同城社区蔬菜配送 骑手抢单 商家
  • 基于遗传算法优化的BP神经网络分类实现(MATLAB)
  • 【Kubernetes(1)】Kubernetes 架构与核心组件详解:管理者(Control Plane)与工作节点(Worker Nodes)的概念与协作