缓存三大问题(击穿、穿透、雪崩)原理及解决方案是什么?

摘要:缓存的击穿、穿透、雪崩是后端开发中高频出现、极易引发服务雪崩的核心问题,三者看似相似但原理和解决方案完全不同。本文用「原理+场景+落地方案+代码」的形式,一次性讲透,看完就能直接落地到生
缓存的击穿、穿透、雪崩是后端开发中高频出现、极易引发服务雪崩的核心问题,三者看似相似但原理和解决方案完全不同。本文用「原理+场景+落地方案+代码」的形式,一次性讲透,看完就能直接落地到生产环境。 先分清:三大问题核心区别(一张表看懂) 问题类型 核心定义 典型场景 直接后果 缓存穿透 请求根本不存在的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. 缓存未命中,查DB User 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是否存在,不存在则直接返回,完全拦截无效请求。 核心逻辑: 启动时将DB中所有有效key加载到布隆过滤器; 请求过来先过布隆过滤器,不存在则直接返回; 存在则走正常缓存/DB流程。
阅读全文