Skip to content

缓存击穿、穿透、雪崩详解

一、缓存穿透(Cache Penetration)

1. 是什么?
缓存穿透是指查询不存在的数据,导致请求绕过缓存直接访问数据库。例如查询数据库不存在ID=-1的数据。

2. 解决什么问题
防止恶意攻击或异常请求频繁访问数据库,导致数据库压力过大甚至崩溃。

3. 应用场景

  • 用户输入非法ID(如负数、超长字符)
  • 爬虫恶意遍历数据ID

4. 解决方案

二、缓存击穿(Cache Breakdown)

1. 是什么?
缓存击穿是指热点数据过期瞬间,大量请求直接穿透到数据库,导致数据库瞬时压力激增。

2. 解决什么问题
避免热点数据失效时,高并发请求压垮数据库。

3. 应用场景

  • 明星离婚新闻的缓存过期
  • 电商秒杀商品信息缓存失效

4. 解决方案

三、缓存雪崩(Cache Avalanche)

1. 是什么?
缓存雪崩是指大量缓存同时过期缓存服务宕机,导致所有请求涌向数据库。

2. 解决什么问题
防止因大规模缓存失效引发的数据库级联故障。

3. 应用场景

  • 缓存集群宕机
  • 批量缓存设置相同过期时间

4. 解决方案


解决方案对比表

问题类型核心特征关键技术方案Java实现示例
穿透查询不存在的数据1. 布隆过滤器
2. 缓存空值
RedisBloom + setIfAbsent("null", 5min)
击穿热点数据失效1. 互斥锁
2. 逻辑过期
RedissonLock.lock() + CompletableFuture
雪崩批量缓存失效1. 随机过期时间
2. 服务熔断
Hystrix + ThreadPoolExecutor

Java代码示例(JDK8+)

1. 缓存穿透解决方案(布隆过滤器 + Guava)

java
// 布隆过滤器初始化
BloomFilter<String> bloomFilter = BloomFilter.create(
    Funnels.stringFunnel(), 1000000, 0.01);

// 查询逻辑
public Object getData(String key) {
    // 1. 布隆过滤器校验
    if (!bloomFilter.mightContain(key)) {
        return null; 
    }
    // 2. 查询缓存
    Object data = redis.get(key);
    if (data == null) {
        // 3. 双重检查锁
        synchronized (this) {
            data = redis.get(key);
            if (data == null) {
                data = db.query(key);
                redis.setex(key, 300, data); // 缓存5分钟
            }
        }
    }
    return data;
}

2. 缓存击穿解决方案(Redisson分布式锁)

java
public Object getHotData(String key) {
    Object data = redis.get(key);
    if (data == null) {
        RLock lock = redisson.getLock(key + "_LOCK");
        try {
            if (lock.tryLock(10, TimeUnit.SECONDS)) { // 尝试获取锁
                data = db.query(key); // 查数据库
                redis.setex(key, 60, data); // 重建缓存
            } else {
                Thread.sleep(100); // 未获锁则等待
                return getHotData(key); // 重试
            }
        } finally {
            lock.unlock();
        }
    }
    return data;
}

3. 缓存雪崩解决方案(Hystrix熔断 + 随机过期)

java
// 设置缓存时添加随机过期时间
public void setCache(String key, Object value) {
    int expire = 3600 + new Random().nextInt(600); // 1小时±10分钟
    redis.setex(key, expire, value);
}

// 使用Hystrix熔断保护数据库
@HystrixCommand(fallbackMethod = "fallbackQuery")
public Object queryWithProtection(String key) {
    return db.query(key);
}

public Object fallbackQuery(String key) {
    return "系统繁忙,请重试"; // 降级响应
}

重要注意事项

  1. 布隆过滤器需预热加载所有合法key,有1%误判率
  2. 分布式锁要设置超时时间,避免死锁(推荐Redisson看门狗机制)
  3. 空值缓存的TTL应较短(如2-5分钟),防止存储垃圾数据
  4. 熔断阈值需动态调整(如失败率>50%触发熔断)
  5. 缓存重建使用异步线程池(如CompletableFuture.supplyAsync()

总结

问题本质原因核心防御思路
穿透恶意访问不存在数据前置过滤 + 缓存空值
击穿热点数据瞬时失效并发控制 + 异步重建
雪崩批量缓存失效/服务宕机分散风险 + 熔断降级

最佳实践:结合Redis + 布隆过滤器 + 分布式锁 + 熔断器,并通过CompletableFuture实现异步处理。JDK8+的StampedLock可优化本地锁性能,ForkJoinPool适用于批量重建缓存场景。