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 宕机高可用上

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

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

相关推荐
蹦跑的蜗牛7 小时前
Spring Boot使用Redis实现消息队列
spring boot·redis·后端
馍馍菜8 小时前
Redis Insight黑屏,页面空白
redis·redis insight
你想考研啊12 小时前
二、redis集群部署(3主3从)
数据库·redis·缓存
顾漂亮12 小时前
Redis深度探索
java·redis·后端·spring·缓存
你想考研啊15 小时前
一、redis安装(单机)和使用
前端·数据库·redis
洲覆15 小时前
Redis 驱动适配 Reactor 模式
开发语言·网络·数据库·redis
缘友一世16 小时前
Redis未授权访问漏洞:从原理到高级利用
数据库·redis·缓存
三角叶蕨16 小时前
Redis极简入门 整合springboot
java·redis
_Johnny_1 天前
Redis 升级操作指南:单机与主从模式
数据库·redis·缓存