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

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

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

相关推荐
摇滚侠10 小时前
阿里云安装的 Redis 在什么位置,如何找到 Redis 的安装位置
redis·阿里云·云计算
啦啦啦_999911 小时前
Redis-2-queryFormat()方法
数据库·redis·缓存
forestsea13 小时前
深入理解Redisson RLocalCachedMap:本地缓存过期策略全解析
redis·缓存·redisson
佛祖让我来巡山13 小时前
Redis 为什么这么快?——「极速快递站」的故事
redis·redis为什么快?
啦啦啦_999915 小时前
Redis-0-业务逻辑
数据库·redis·缓存
自不量力的A同学16 小时前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
fengxin_rou16 小时前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
是阿楷啊17 小时前
Java大厂面试场景:音视频场景中的Spring Boot与微服务实战
spring boot·redis·spring cloud·微服务·grafana·prometheus·java面试
笨蛋不要掉眼泪17 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
ALex_zry1 天前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存