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

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

基于 Redis 7.0 的 图解 + 实战代码 完整方案

让你 10 分钟 搞懂 三大缓存杀手 及其 克星


目录

  1. 导语:三大杀手初印象
  2. [缓存穿透(Cache Penetration)](#缓存穿透(Cache Penetration) "#%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F")
  3. [缓存击穿(Cache Breakdown)](#缓存击穿(Cache Breakdown) "#%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF")
  4. [缓存雪崩(Cache Avalanche)](#缓存雪崩(Cache Avalanche) "#%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9")
  5. 对比表:一眼看懂区别
  6. 实战:Spring Boot + Redis 代码实现
  7. 压测与调优建议
  8. 总结:口诀记忆

导语:三大杀手初印象

场景 描述 危害
穿透 查询 不存在 的数据,缓存 不命中,DB 被拖垮 QPS 暴涨,DB 宕机
击穿 热点 突然失效大量并发 打到 DB DB 连接耗尽
雪崩 大量 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. 解决方案

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

3. 布隆过滤器(RedisBloom)

java 复制代码
// 初始化
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. 分布式锁实现

java 复制代码
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 高可用 主从 + 哨兵 / Cluster spring.redis.cluster.nodes=node1:6379,node2:6379

3. 随机过期时间实现

java 复制代码
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. 依赖

xml 复制代码
<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. 配置类

java 复制代码
@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. 统一缓存模板(防穿透 + 防击穿)

java 复制代码
@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.size 32 KB 提高吞吐
linger.ms 5-20 ms 延迟换吞吐
compression.type lz4 压缩比 + CPU 平衡
fetch.min.bytes 1 KB 减少空拉取
num.network.threads CPU 核数 网络 I/O 线程

总结:口诀记忆

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

掌握 "布隆锁 + 随机散 + 高可用" 三件套,

你就拥有了 抗住百万并发终极缓存方案

相关推荐
睡美人的小仙女1278 小时前
Threejs加载环境贴图报错Bad File Format: bad initial token
开发语言·javascript·redis
徐同保9 小时前
解决 Vue 3 项目 TypeScript 编译错误:@types/lodash 类型定义不兼容
redis·网络协议·https
he___H11 小时前
Redis高级数据类型
数据库·redis·缓存
笨手笨脚の11 小时前
Redis: Thread limit exceeded replacing blocked worker
java·redis·forkjoin·thread limit
惊讶的猫13 小时前
Redis双写一致性
数据库·redis·缓存
老虎062714 小时前
Redis入门,配置,常见面试题总结
数据库·redis·缓存
J&Lu14 小时前
[DDD大营销-Redis]
数据库·redis·缓存
TracyCoder12316 小时前
解读华为云Redis Proxy集群规格:架构、规格与带宽性能
redis·架构·华为云
陌上丨17 小时前
如何保证Redis缓存和数据库数据的一致性?
数据库·redis·缓存
晓131318 小时前
第八章:Redis底层原理深度详细解析
数据库·redis·缓存