Redis 缓存穿透、击穿、雪崩的 出现场景 与 解决方案

发布于:2025-03-06 ⋅ 阅读:(20) ⋅ 点赞:(0)
一、缓存穿透(Cache Penetration)
问题描述

请求 数据库中不存在的数据(如非法ID),导致请求绕过缓存直接击穿到数据库。
典型场景

  • 恶意攻击:频繁请求 id=-1 或随机不存在的用户ID。

  • 业务逻辑缺陷:未校验参数合法性(如非数字ID查询)。

解决方案
  • 空值缓存

    将查询结果为 null 的请求也缓存,设置较短的过期时间(如 5分钟)。
public Object getData(String key) {
    Object value = redis.get(key);
    if (value != null) {
        return value;  // 命中缓存
    }
    // 查数据库
    value = db.query(key);
    if (value == null) {
        redis.setex(key, 300, "NULL");  // 缓存空值
    } else {
        redis.setex(key, 3600, value);  // 缓存真实值
    }
    return value;
}

布隆过滤器(Bloom Filter)

  • 在缓存层前加布隆过滤器,判断请求的 key 是否可能存在:

    • 存在 → 查缓存或数据库。

    • 不存在 → 直接返回,避免查库。

接口层校验

  • 对请求参数进行合法性校验(如ID范围、格式)。
二、缓存击穿(Cache Breakdown)
问题描述

热点数据过期瞬间,大量并发请求直接涌入数据库。
典型场景

  • 明星爆款商品详情页缓存过期。

  • 秒杀活动中核心商品信息缓存失效。

解决方案
  1. 互斥锁(分布式锁)

    • 只允许一个线程重建缓存,其他线程等待。

public Object getData(String key) {
    Object value = redis.get(key);
    if (value == null) {
        String lockKey = key + "_LOCK";
        if (redis.setnx(lockKey, "1", 10)) {  // 获取锁
            try {
                value = db.query(key);        // 查数据库
                redis.setex(key, 3600, value); 
            } finally {
                redis.del(lockKey);           // 释放锁
            }
        } else {
            Thread.sleep(100);                // 等待后重试
            return getData(key);              // 递归调用
        }
    }
    return value;
}

逻辑过期时间(永不过期 + 异步更新)

  • 缓存不设置物理过期时间,但存储逻辑过期字段。

  • 后台线程定期检测并更新缓存。

// 缓存数据结构
class CacheData {
    Object data;
    long expireTime;  // 逻辑过期时间
}

public Object getData(String key) {
    CacheData cacheData = redis.get(key);
    if (cacheData == null) {
        return db.query(key);  // 首次加载
    }
    if (System.currentTimeMillis() > cacheData.expireTime) {
        // 提交异步任务更新缓存
        executor.submit(() -> {
            Object newData = db.query(key);
            redis.set(key, new CacheData(newData, System.currentTimeMillis() + 3600000));
        });
    }
    return cacheData.data;
}

热点数据永不过期

  • 对极热点数据不设置过期时间,通过异步线程定期更新。

三、缓存雪崩(Cache Avalanche)
问题描述

大量缓存同时过期 或 缓存服务宕机,导致请求全部涌入数据库。
典型场景

  • 缓存服务器重启后所有数据丢失。

  • 批量缓存设置相同过期时间(如每日零点统一过期)。

解决方案
  • 随机过期时间

        在基础过期时间上增加随机值(如 基础时间 + 随机0~300秒)。

int baseExpire = 3600;
int randomExpire = baseExpire + new Random().nextInt(300);
redis.setex(key, randomExpire, value);
  • 集群高可用

    • 使用 Redis 集群(Cluster)或哨兵模式(Sentinel)避免单点故障。

  • 熔断降级

    • 当数据库压力过大时,启用熔断机制(如返回默认值、限流)。

// 使用 Hystrix 实现熔断
@HystrixCommand(fallbackMethod = "fallbackGetData")
public Object getData(String key) {
    return redis.get(key);
}

public Object fallbackGetData(String key) {
    return "系统繁忙,请稍后重试";  // 降级响应
}
  • 多级缓存

    • 本地缓存(如 Caffeine) + Redis 缓存,降低 Redis 压力。

对比总结

问题类型 触发条件 核心解决思路 典型方案
穿透 查询不存在的数据 拦截非法请求、缓存空值 布隆过滤器、空值缓存
击穿 热点数据过期 限制并发重建、异步更新 互斥锁、逻辑过期时间
雪崩 大量缓存同时失效或服务宕机 分散过期时间、服务高可用 随机过期、集群部署、熔断降级

实战建议

  1. 组合使用方案

    • 布隆过滤器 + 空值缓存 → 解决穿透。

    • 互斥锁 + 随机过期 → 解决击穿和雪崩。

  2. 监控与预警

    • 监控缓存命中率、数据库 QPS,及时发现问题。

  3. 压测验证

    • 模拟高并发场景,验证方案的可靠性。

通过合理设计缓存策略,可显著提升系统在高并发场景下的稳定性和性能。