Redis 缓存击穿、穿透、雪崩与布隆过滤器详解
一、缓存击穿(Cache Breakdown)
1. 定义与场景
缓存击穿是指某个热点key在缓存过期的一瞬间,同时有大量请求涌入,导致所有请求直接打到数据库,造成数据库瞬时压力过大。
2. 核心特征
- 针对单个热点key
- 该key在缓存中刚好过期
- 并发请求量极大
3. 解决方案
(1) 互斥锁(Mutex Lock)
public String getData(String key) {
String data = redis.get(key);
if (data == null) {
if (redis.setnx(key + "_mutex", "1", 60)) { // 获取分布式锁
data = db.get(key); // 查数据库
redis.set(key, data, 30); // 写入缓存
redis.del(key + "_mutex"); // 释放锁
} else {
Thread.sleep(100); // 等待重试
return getData(key); // 递归调用
}
}
return data;
}
(2) 逻辑过期(Logical Expiration)
public String getDataWithLogicalExpire(String key) {
Item item = redis.get(key);
if (item == null) {
return null;
}
if (item.expireTime <= System.currentTimeMillis()) {
if (redis.setnx(key + "_mutex", "1", 60)) { // 获取锁
new Thread(() -> { // 异步更新
Item newItem = db.get(key);
redis.set(key, newItem, 30);
redis.del(key + "_mutex");
}).start();
}
}
return item.data;
}
二、缓存穿透(Cache Penetration)
1. 定义与场景
缓存穿透是指查询不存在的数据,导致请求直接穿透缓存到达数据库,可能被恶意利用发起大量无效查询。
2. 核心特征
- 查询的key在数据库和缓存中都不存在
- 可能是恶意攻击或业务设计缺陷
3. 解决方案
(1) 空值缓存(Null Caching)
public String getData(String key) {
String data = redis.get(key);
if (data != null) {
return "null".equals(data) ? null : data; // 处理空值
}
data = db.get(key);
if (data == null) {
redis.set(key, "null", 5 * 60); // 缓存空值5分钟
return null;
}
redis.set(key, data, 30 * 60); // 缓存正常数据30分钟
return data;
}
(2) 布隆过滤器(Bloom Filter)
from pybloom_live import ScalableBloomFilter
bf = ScalableBloomFilter(initial_capacity=100000, error_rate=0.001)
# 预热阶段:将所有有效key加入布隆过滤器
for key in db.all_keys():
bf.add(key)
def get_data(key):
if not key in bf: # 先检查布隆过滤器
return None
data = redis.get(key)
if data is not None:
return data
data = db.get(key)
if data is not None:
redis.set(key, data)
return data
三、缓存雪崩(Cache Avalanche)
1. 定义与场景
缓存雪崩是指大量key同时过期或Redis服务宕机,导致所有请求直接打到数据库,可能引发数据库崩溃。
2. 核心特征
- 大量key同时失效
- 可能是自然过期或服务故障
3. 解决方案
(1) 随机过期时间
public void setData(String key, String value) {
int randomTime = new Random().nextInt(300); // 0-300秒随机值
redis.set(key, value, 1800 + randomTime); // 30分钟基础+随机偏移
}
(2) 多级缓存架构
客户端 → CDN → 本地缓存 → Redis集群 → 数据库
(3) 熔断降级
// 使用Hystrix实现熔断
@HystrixCommand(fallbackMethod = "getDataFallback")
public String getData(String key) {
return redis.get(key);
}
public String getDataFallback(String key) {
return "系统繁忙,请稍后再试"; // 降级响应
}
四、布隆过滤器(Bloom Filter)深度解析
1. 数据结构原理
- 位数组:长度为m的二进制向量
- k个哈希函数:每个函数将元素映射到位数组的k个不同位置
2. 核心特性
| 特性 | 说明 |
|---|---|
| 空间效率 | 使用位数组,空间复杂度O(m) |
| 查询时间复杂度 | O(k) |
| 误判率 | 可能存在假阳性(判断存在实际不存在),但不会假阴性 |
| 不可删除 | 传统布隆过滤器不支持删除操作(可通过Counting Bloom Filter变种实现) |
3. Redis实现方案
(1) 原生Redis Module
# 安装RedisBloom模块
git clone --recursive https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom && make
# Redis配置加载模块
loadmodule /path/to/redisbloom.so
(2) 基本命令
# 创建容量100万,误判率0.1%的过滤器
BF.RESERVE myfilter 0.001 1000000
# 添加元素
BF.ADD myfilter item1
# 检查元素
BF.EXISTS myfilter item1 # 返回1表示可能存在,0表示肯定不存在
# 批量操作
BF.MADD myfilter item2 item3 item4
BF.MEXISTS myfilter item2 item5
4. 应用场景
(1) 缓存穿透防护
def get_user(user_id):
if not bloom_filter.exists(user_id):
return None # 肯定不存在,直接返回
user = cache.get(user_id)
if user is None:
user = db.get(user_id)
if user:
cache.set(user_id, user)
return user
(2) 爬虫URL去重
def should_crawl(url):
if not bloom_filter.exists(url):
bloom_filter.add(url)
return True
return False
(3) 垃圾邮件过滤
def is_spam(email):
return bloom_filter.exists(email)
5. 参数设计与性能优化
(1) 最优哈希函数数量
k = \frac{m}{n} \ln 2 \approx 0.7 \times \frac{m}{n}
(2) 最优位数组大小
m = -\frac{n \ln p}{(\ln 2)^2}
其中:
- n: 预期元素数量
- p: 可接受的误判率
(3) 实际案例
- 预期存储1亿个元素,允许0.1%误判率:
- 位数组大小m ≈ 958,505,853 bits ≈ 114MB
- 哈希函数数量k ≈ 7
五、综合解决方案对比
| 问题类型 | 解决方案 | 优点 | 缺点 |
|---|---|---|---|
| 击穿 | 互斥锁 | 保证数据一致性 | 可能造成线程阻塞 |
| 逻辑过期 | 非阻塞 | 实现复杂 | |
| 穿透 | 空值缓存 | 实现简单 | 可能被大量无效key占用空间 |
| 布隆过滤器 | 内存效率高 | 存在误判率 | |
| 雪崩 | 随机过期时间 | 实现简单 | 不能应对Redis宕机 |
| 多级缓存 | 系统健壮性强 | 架构复杂 |
六、最佳实践建议
- 热点key监控:使用Redis的
hotkeys命令或monitor分析热点数据 - 压测验证:模拟极端场景验证解决方案有效性
- 动态调整:根据业务变化调整布隆过滤器参数
- 监控告警:对缓存命中率、数据库QPS设置阈值告警
通过合理组合这些技术方案,可以构建出高可用、高性能的缓存系统,有效应对各种缓存异常场景。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120839

浙公网安备 33010602011771号