Redis,什么是缓存穿透/击穿/雪崩,如何解决它们

248 阅读3分钟

Redis,什么是缓存穿透/击穿/雪崩,如何解决它们

基于 Redis 7.0 的 图解 + 实战代码 完整方案
让你 10 分钟 搞懂 三大缓存杀手 及其 克星


目录

  1. 导语:三大杀手初印象
  2. 缓存穿透(Cache Penetration)
  3. 缓存击穿(Cache Breakdown)
  4. 缓存雪崩(Cache Avalanche)
  5. 对比表:一眼看懂区别
  6. 实战:Spring Boot + Redis 代码实现
  7. 压测与调优建议
  8. 总结:口诀记忆

导语:三大杀手初印象

场景描述危害
穿透查询 不存在 的数据,缓存 不命中,DB 被拖垮QPS 暴涨,DB 宕机
击穿热点 突然失效大量并发 打到 DBDB 连接耗尽
雪崩大量 key 同时过期Redis 宕机整个系统崩溃

缓存穿透(Cache Penetration)

1. 流程图

sequenceDiagram
    participant App
    participant Redis
    participant DB
    App->>Redis: get user:9999
    Redis->>App: nil
    App->>DB: select * from user where id=9999
    DB->>App: null
    App->>Redis: set user:9999 null

2. 解决方案

方案说明代码片段
布隆过滤器拦截 不存在 的 keyBloomFilter.put("user:9999")
空值缓存缓存 NULL 并设置 短过期时间redis.setex("user:9999", 30, "NULL")

3. 布隆过滤器(RedisBloom)

// 初始化
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("user-filter");
bloomFilter.tryInit(1000000, 0.03);
// 放入
bloomFilter.add("user:1");
// 判断
if (!bloomFilter.contains("user:9999")) {
    return null; // 直接返回,不查 DB
}

缓存击穿(Cache Breakdown)

1. 场景

  • 热点数据(如秒杀商品)过期瞬间万级并发 打到 DB。

2. 解决方案

方案说明代码片段
互斥锁(分布式锁)一个线程 查 DB,其余等待RLock lock = redisson.getLock("lock:item:1")
逻辑过期(永不过期)过期时间放在 value 里,后台异步刷新value = {data:xxx, expire:1719999999}

3. 分布式锁实现

public Item getItem(Long id) {
    String key = "item:" + id;
    Item item = redis.get(key);
    if (item != null) {
        return item; // 命中缓存
    }
    // 未命中,加锁
    RLock lock = redisson.getLock("lock:item:" + id);
    try {
        if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
            item = redis.get(key); // 双重检查
            if (item != null) return item;
            item = db.findById(id);
            redis.setex(key, 300, item); // 5 分钟过期
            return item;
        }
        // 等待后重试
        Thread.sleep(50);
        return getItem(id);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        lock.unlock();
    }
}

缓存雪崩(Cache Avalanche)

1. 场景

  • 大量 key 同时过期(如凌晨全量失效)→ DB 被打爆
  • Redis 宕机所有流量打到 DB

2. 解决方案

方案说明代码片段
随机过期时间打散失效时间expire = base + random(0, 600)
永不过期 + 后台刷新逻辑过期,异步更新value = {data:xxx, expire:xxx}
Redis 高可用主从 + 哨兵 / Clusterspring.redis.cluster.nodes=node1:6379,node2:6379

3. 随机过期时间实现

public void setWithRandomExpire(String key, Object value) {
    int base = 3600; // 1小时
    int random = new Random().nextInt(600); // 0-600秒
    redis.setex(key, base + random, value);
}

对比表:一眼看懂区别

问题触发条件现象解决方案
穿透查询 不存在 数据DB 被 不存在 查询打爆布隆过滤器空值缓存
击穿热点 突然失效DB 被 同一 key 打爆分布式锁逻辑过期
雪崩大量 key 同时失效DB 被 所有 key 打爆随机过期永不过期

实战:Spring Boot + Redis 代码实现

1. 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.4</version>
</dependency>

2. 配置类

@Configuration
public class RedisConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}

3. 统一缓存模板(防穿透 + 防击穿)

@Service
public class CacheService {

    @Autowired
    private RedissonClient redisson;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public <T> T get(String key, Supplier<T> dbFallback, Class<T> clazz) {
        T value = (T) redisTemplate.opsForValue().get(key);
        if (value != null) return value;

        // 布隆过滤器防穿透
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("bloom");
        bloomFilter.tryInit(1000000, 0.03);
        if (!bloomFilter.contains(key)) {
            return null;
        }

        // 分布式锁防击穿
        RLock lock = redisson.getLock("lock:" + key);
        try {
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                value = (T) redisTemplate.opsForValue().get(key);
                if (value != null) return value;
                value = dbFallback.get();
                if (value == null) {
                    redisTemplate.opsForValue().set(key, "NULL", 30, TimeUnit.SECONDS);
                } else {
                    redisTemplate.opsForValue().set(key, value, 3600 + new Random().nextInt(600), TimeUnit.SECONDS);
                }
                return value;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
        return null;
    }
}

压测与调优建议

参数建议值说明
batch.size32 KB提高吞吐
linger.ms5-20 ms延迟换吞吐
compression.typelz4压缩比 + CPU 平衡
fetch.min.bytes1 KB减少空拉取
num.network.threadsCPU 核数网络 I/O 线程

总结:口诀记忆

穿透布隆拦
击穿锁来扛
雪崩随机散
Redis 宕机高可用上

掌握 “布隆锁 + 随机散 + 高可用” 三件套,
你就拥有了 抗住百万并发终极缓存方案