网站Logo Ilren 小记

Redis 缓存防御指南:穿透/击穿/雪崩原理与解决方案

jack
3
2023-12-24

在现代高并发系统中,Redis 被广泛用于数据缓存,大幅提高响应速度并减轻数据库压力。然而,随着业务复杂度的提升,简单的缓存逻辑已经无法满足系统稳定性的要求。

今天我们将深入剖析 Redis 三大常见问题:缓存穿透、缓存击穿、缓存雪崩,并给出可落地的应对策略,助你构建更健壮的缓存架构。

一、缓存穿透(Cache Penetration)

✅ 定义

缓存穿透是指请求的数据在缓存中查不到,数据库中也没有,导致每次请求都绕过缓存,直击数据库。

❗ 问题危害

  • 每次请求都访问数据库,等同于没使用缓存。

  • 大量恶意请求可能压垮数据库,形成“缓存绕过攻击”。

📌 典型场景

  • 请求非法 ID,如负数、UUID、空字符串等。

  • 请求的是已删除但未更新缓存的数据。

  • 攻击者构造大量不存在 key 的请求。

🛠 解决方案

方法

说明

缓存空对象

查询结果为空时,仍缓存一个标记对象,设置较短 TTL。

参数校验

在 API 层校验 ID 合法性,拦截明显无效请求。

布隆过滤器(推荐)

使用布隆过滤器预先拦截不存在的 key,避免命中缓存和 DB。

示例代码:缓存空值

if (cached == null) {
    Product product = db.get(id);
    redis.set("product:" + id, product == null ? NULL_MARKER : product, 5分钟);
}

二、缓存击穿(Cache Breakdown)

✅ 定义

缓存击穿是指:某个热点 key 过期或被清除的瞬间,瞬时大量请求打入数据库,造成数据源被压垮。

❗ 问题危害

  • 热点数据突然失效,大量请求并发访问数据库。

  • 数据库压力激增,可能引发雪崩连锁反应。

📌 典型场景

  • 秒杀系统中的商品详情页。

  • 热门博文或排行榜在短时间内访问量极高。

🛠 解决方案

方法

说明

设置热点数据永不过期

对极热点数据设置永不过期,或主动延迟清除。

使用互斥锁(互斥更新)

缓存失效时,通过加锁只允许一个线程查询数据库并重建缓存,其他线程等待。

异步预加载 / 定时刷新

定期后台更新热点数据,防止同时过期。

示例代码:互斥锁重建缓存

Product product = redis.get(key);
if (product == null) {
    if (tryLock(key)) {
        product = db.get(id);
        redis.set(key, product, 30分钟);
        releaseLock(key);
    } else {
        // 休眠重试
        Thread.sleep(50);
        return getProduct(id); // 递归尝试
    }
}

三、缓存雪崩(Cache Avalanche)

✅ 定义

缓存雪崩指:大量缓存同时失效,导致所有请求穿透缓存,瞬间压垮数据库。

❗ 问题危害

  • 数据库无法承受突如其来的高并发压力。

  • 服务响应缓慢甚至不可用,造成大规模故障。

📌 典型场景

  • 缓存集中过期(如同一时间重启服务,缓存全失效)。

  • 缓存设置统一的 TTL。

🛠 解决方案

方法

说明

随机过期时间

为缓存设置 TTL 时,加入随机偏移,避免集中失效。

缓存预热

启动时提前加载热点数据,避免系统刚启动时数据库压力暴增。

限流 + 降级保护

请求异常时,服务返回默认值或空数据,保护系统整体稳定性。

多级缓存(如本地 + Redis)

使用 Caffeine + Redis 双缓存层,增强容错能力。

示例代码:设置随机过期时间

int ttl = 30 + new Random().nextInt(10); // 30~40 分钟
redis.set("key", value, ttl, TimeUnit.MINUTES);

四、总结对比

问题类型

本质原因

典型表现

应对手段

穿透

请求的数据无效,缓存和 DB 都没有

请求绕过缓存,直接打数据库

缓存空值、参数校验、布隆过滤器

击穿

热点 key 失效瞬间,高并发访问

某一 key 突然过期,大量请求压数据库

互斥锁、永不过期、定时刷新

雪崩

大量缓存同时失效

整体系统压力骤增,数据库崩溃

随机 TTL、预热缓存、限流降级、多级缓存

五、最佳实践建议

  1. ✅ 热点数据使用长 TTL 或异步刷新。

  2. ✅ 数据为空时缓存空值,防止穿透。

  3. ✅ 所有 TTL 设置加入随机因子。

  4. ✅ 接口层做好参数校验。

  5. ✅ 引入布隆过滤器提升安全性和性能。

  6. ✅ 尽可能将非关键业务异步处理,避免主流程阻塞。

六、推荐工具 & 技术组件

工具/框架

说明

Redis Bloom

Redis 官方模块,支持布隆过滤器。

Guava

Java 工具库,支持本地布隆过滤器。

Caffeine

本地缓存框架,常用于二级缓存。

Sentinel

阿里开源限流熔断工具。

七、参考实现:Spring Boot 中防缓存穿透

@Service
public class UserService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private UserRepository userRepository;

    public User getUserById(Long id) {
        String key = "user:" + id;
        Object cached = redisTemplate.opsForValue().get(key);

        if (cached != null) {
            return cached instanceof NullUser ? null : (User) cached;
        }

        User user = userRepository.findById(id).orElse(null);
        redisTemplate.opsForValue().set(key, user == null ? new NullUser() : user, Duration.ofMinutes(5));
        return user;
    }

    private static class NullUser implements Serializable {}
}

八、结语

合理设计缓存策略,是构建高可用、高性能系统的基础保障。应对缓存穿透、击穿和雪崩问题,不能只靠一个方案,而是组合多种机制进行防护

动物装饰