导言

在分布式系统中,Redis 作为高性能分布式缓存,可有效减轻数据库压力、提升接口响应速度,但高并发场景下,缓存雪崩缓存击穿缓存穿透三类异常易引发系统稳定性问题。

一、缓存穿透 ——“不存在数据” 的持续穿透

1. 核心定义

缓存穿透是指查询的数据在缓存和数据库中均不存在(如查询不存在的 ID、非法参数),导致每次请求都穿透缓存直接查询数据库,且由于缓存无法存储 “不存在的数据”,后续相同请求会持续穿透,造成数据库无效查询压力激增的现象。

核心特征:区别于前两类异常,其关键在于 “查询无结果数据”,缓存无法拦截,请求持续打向 DB。

2. 触发条件

缓存穿透的发生源于两类场景:

  • 非法请求攻击:恶意用户构造非法参数(如负数 ID、超长字符串)发起大量查询,这类数据在缓存和 DB 中均不存在;

  • 业务空数据查询:正常业务场景中,部分查询天然无结果(如查询已删除的用户、未创建的订单),且未做缓存处理。

3. 解决方案对比

解决方案 实现逻辑 优点 缺点 适用场景
空值缓存 查询 DB 无结果时,将 “空值”(如空字符串、默认对象)存入 Redis,并设置较短过期时间(如 5-10 分钟),拦截后续相同请求 实现简单,无额外组件依赖;可快速拦截重复请求 占用 Redis 内存(存储大量空值);短期存在数据不一致(如 DB 新增数据前,缓存仍返回空值) 非法请求较少、空数据查询量可控的场景
布隆过滤器 提前将 DB 中所有有效数据的 “标识”(如 ID)存入布隆过滤器;查询前先通过过滤器校验,若标识不存在则直接返回,不查询缓存和 DB 内存占用极低(比空值缓存节省 90% 以上);拦截效率高 存在误判率(无法 100% 精准拦截);需维护过滤器与 DB 数据一致性(如数据新增 / 删除时同步更新过滤器) 非法请求量大、空数据查询频繁的场景(如用户 ID 查询、商品 ID 查询)
请求参数校验 在业务层或网关层添加参数校验(如 ID 范围校验、格式校验),直接拦截非法参数请求(如负数 ID、非数字 ID) 从源头拦截无效请求,无缓存 / DB 压力;实现简单 仅能拦截 “格式非法” 的请求,无法拦截 “格式合法但 DB 无结果” 的请求(如合法 ID 但已删除) 可作为基础防护,需与其他方案配合使用

4. 代码示例(空值缓存・Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Object getDataById(Long id) {
// 1. 先查缓存
String cacheKey = "cache:data:" + id;
Object cacheData = redisTemplate.opsForValue().get(cacheKey);

// 2. 缓存命中(包括空值),直接返回
if (cacheData != null) {
// 区分空值与正常数据(如约定空值为特定字符串)
return "NULL_DATA".equals(cacheData) ? null : cacheData;
}

// 3. 缓存未命中,查DB
Object dbData = dbMapper.selectById(id);
if (dbData != null) {
// 4. DB有结果,缓存正常数据(过期时间1小时)
redisTemplate.opsForValue().set(cacheKey, dbData, 3600, TimeUnit.SECONDS);
} else {
// 5. DB无结果,缓存空值(过期时间5分钟,避免长期占用内存)
redisTemplate.opsForValue().set(cacheKey, "NULL_DATA", 300, TimeUnit.SECONDS);
}
return dbData;
}

5. 关键监控指标

  • 无结果查询量:Redis 无结果查询量 + DB 无结果查询量≥总请求量的 10%,需警惕穿透风险;

  • DB 无结果查询占比:DB 查询中无结果的比例≥20%,可能存在穿透;

  • 非法参数请求量:网关层拦截的非法参数请求≥500 次 / 秒,需加强参数校验;

  • 布隆过滤器误判率:若使用布隆过滤器,误判率应控制在 1% 以下,超过需调整过滤器参数(如哈希函数数量、bit 数组大小)。

二、三类缓存异常核心差异总结

异常类型 核心诱因 影响范围 关键防护思路
缓存雪崩 大量缓存集中过期 / 服务不可用 全局 打散过期时间、多级缓存、保障服务可用性
缓存击穿 单个热点数据过期 局部(热点) 互斥锁、逻辑过期、热点数据永不过期
缓存穿透 查询不存在的数据 全局(DB) 空值缓存、布隆过滤器、参数校验