题目
如何设计解决方案应对Redis缓存穿透问题?
信息
- 类型:问答
- 难度:⭐⭐
考点
缓存穿透原理,布隆过滤器应用,空对象缓存策略,防御性编程
快速回答
应对Redis缓存穿透的核心方案:
- 布隆过滤器:前置过滤非法请求
- 缓存空对象:对不存在的数据设置短时缓存
- 请求校验:在业务层增加参数合法性检查
- 热点Key监控:实时监控高频访问的Key
一、问题原理与危害
缓存穿透指查询不存在的数据,导致请求直接穿透缓存层到达数据库:
- 恶意攻击:攻击者伪造大量不存在ID(如负值、超大整数)
- 业务缺陷:程序BUG生成非法查询Key
- 危害:大量请求直接冲击数据库,可能引发服务雪崩
二、解决方案与代码示例
1. 布隆过滤器(Bloom Filter)
原理:使用位数组和哈希函数,以极小空间判断元素一定不存在或可能存在于集合中
# Python伪代码示例
from pybloom_live import BloomFilter
# 初始化布隆过滤器(预计元素量100万,误判率0.001)
bf = BloomFilter(capacity=1000000, error_rate=0.001)
# 预热数据:将有效ID加入过滤器
for id in valid_id_list:
bf.add(id)
# 查询拦截
def get_data(key):
if key not in bf: # 一定不存在
return None
# 继续查询缓存/数据库
data = redis.get(key)
if not data:
data = db.query(key)
redis.setex(key, 300, data or '') # 空值缓存
return data注意事项:
- 误判率与位数组大小成反比,需根据业务调整
- 删除数据时需同步更新过滤器(可用Counting Bloom Filter)
2. 空对象缓存(Null Caching)
// Java示例
public String getData(String key) {
String value = redis.get(key);
if (value != null) {
return "".equals(value) ? null : value; // 处理空值
}
value = db.query(key);
if (value == null) {
// 设置短时空缓存(5分钟)
redis.setex(key, 300, "");
return null;
}
redis.setex(key, 3600, value); // 正常缓存
return value;
}关键点:
- 空值缓存时间不宜过长(建议2-5分钟)
- 需特殊处理空值(如返回特定错误码)
3. 请求校验与监控
- 参数校验:在Controller层校验ID格式(如正则匹配正整数)
- 限流熔断:对高频访问Key实施监控,异常时触发限流
- 日志审计:记录异常查询模式,识别攻击行为
三、最佳实践
- 分层防御:API网关校验 → 布隆过滤器 → 空值缓存 → 数据库限流
- 组合策略:布隆过滤器+空值缓存(应对过滤器误判)
- 动态调整:根据QPS自动延长空值缓存时间
四、常见错误
- ❌ 永久缓存空值 → 导致脏数据长期存在
- ❌ 未设置布隆过滤器容量 → 误判率急剧上升
- ❌ 忽略Key删除同步 → 过滤器与数据库状态不一致
五、扩展知识
- 布隆过滤器变种:
- Cuckoo Filter:支持删除操作
- Scalable Bloom Filter:动态扩容版本
- Redis原生方案:RedisBloom模块(官方扩展)
BF.ADD myFilter 12345 # 添加元素 BF.EXISTS myFilter 999 # 检查是否存在 - 缓存击穿对比:缓存穿透(数据不存在) vs 缓存击穿(热点Key失效)