文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

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宕机
多级缓存系统健壮性强架构复杂

六、最佳实践建议

  1. 热点key监控:使用Redis的hotkeys命令或monitor分析热点数据
  2. 压测验证:模拟极端场景验证解决方案有效性
  3. 动态调整:根据业务变化调整布隆过滤器参数
  4. 监控告警:对缓存命中率、数据库QPS设置阈值告警

通过合理组合这些技术方案,可以构建出高可用、高性能的缓存系统,有效应对各种缓存异常场景。

posted @ 2025-08-27 12:48  NeoLshu  阅读(4)  评论(0)    收藏  举报  来源