面试题:Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么?

在面试中,如果被问到 Redis 中的缓存击穿、缓存穿透和缓存雪崩,可以从以下几个方面进行回答:


1. 缓存击穿(Cache Breakdown)

定义

缓存击穿是指 某个热点数据在缓存中过期 的同时,有大量并发请求同时访问这个数据,导致请求直接打到数据库上,造成数据库压力骤增。

原因

  • 热点数据在缓存中过期。
  • 大量并发请求同时访问这个数据。

解决方案

  • 设置热点数据永不过期:对于某些热点数据,可以设置其永不过期,或者定期异步更新缓存。
  • 加分布式锁:当缓存失效时,使用分布式锁(如 Redis 的 SETNX 命令)保证只有一个请求去加载数据,其他请求等待并重试。
  • 缓存预热:在系统启动或低峰期,提前加载热点数据到缓存中。

2. 缓存穿透(Cache Penetration)

定义

缓存穿透是指 查询一个不存在的数据,由于缓存中不存在,请求直接打到数据库上。如果大量请求查询不存在的数据,会导致数据库压力过大。

原因

  • 查询的数据在数据库和缓存中都不存在。
  • 恶意攻击或非法请求,故意查询不存在的数据。

解决方案

  • 缓存空值:对于查询结果为空的请求,将空值也缓存起来,并设置一个较短的过期时间(如 5 分钟),避免重复查询数据库。
  • 布隆过滤器(Bloom Filter):在缓存层之前加一个布隆过滤器,用于快速判断数据是否存在。如果布隆过滤器判断数据不存在,则直接返回,避免查询数据库。
  • 参数校验:对请求参数进行合法性校验,过滤掉非法请求。

3. 缓存雪崩(Cache Avalanche)

定义

缓存雪崩是指 大量缓存数据在同一时间过期,导致大量请求直接打到数据库上,造成数据库压力骤增,甚至崩溃。

原因

  • 大量缓存数据设置了相同的过期时间。
  • 缓存服务宕机,导致所有请求直接打到数据库。

解决方案

  • 设置不同的过期时间:为缓存数据设置随机的过期时间,避免大量数据同时过期。
  • 缓存高可用:使用 Redis 集群或主从复制,保证缓存服务的高可用性,避免单点故障。
  • 限流降级:在缓存失效时,使用限流机制(如令牌桶、漏桶算法)控制请求量,或者直接返回降级数据,避免数据库压力过大。
  • 缓存预热:在系统启动或低峰期,提前加载数据到缓存中,并设置合理的过期时间。

4. 对比总结

问题定义原因解决方案
缓存击穿热点数据过期,大量请求直接访问数据库。热点数据过期 + 高并发请求。设置热点数据永不过期、加分布式锁、缓存预热。
缓存穿透查询不存在的数据,请求直接访问数据库。恶意攻击或非法请求,查询不存在的数据。缓存空值、布隆过滤器、参数校验。
缓存雪崩大量缓存数据同时过期,导致大量请求直接访问数据库。大量缓存数据设置相同的过期时间,或缓存服务宕机。设置不同的过期时间、缓存高可用、限流降级、缓存预热。

5. 示例代码

缓存击穿解决方案:分布式锁

public String getDataWithLock(String key) {
    // 1. 先读缓存
    String value = jedis.get(key);
    if (value != null) {
        return value;
    }

    // 2. 缓存未命中,尝试加锁
    String lockKey = "lock:" + key;
    String lockValue = UUID.randomUUID().toString();
    if (jedis.setnx(lockKey, lockValue) == 1) {
        try {
            // 3. 加锁成功,读数据库
            value = database.get(key);
            if (value != null) {
                // 4. 将数据写入缓存
                jedis.set(key, value);
            } else {
                // 5. 缓存空值
                jedis.set(key, "", "NX", "EX", 300); // 空值缓存 5 分钟
            }
        } finally {
            // 6. 释放锁
            if (lockValue.equals(jedis.get(lockKey))) {
                jedis.del(lockKey);
            }
        }
    } else {
        // 7. 加锁失败,等待并重试
        try {
            Thread.sleep(100); // 等待 100ms
            return getDataWithLock(key); // 重试
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    return value;
}

缓存穿透解决方案:布隆过滤器

public String getDataWithBloomFilter(String key) {
    // 1. 先检查布隆过滤器
    if (!bloomFilter.mightContain(key)) {
        return null; // 数据不存在,直接返回
    }

    // 2. 读缓存
    String value = jedis.get(key);
    if (value != null) {
        return value;
    }

    // 3. 缓存未命中,读数据库
    value = database.get(key);
    if (value != null) {
        // 4. 将数据写入缓存
        jedis.set(key, value);
    } else {
        // 5. 缓存空值
        jedis.set(key, "", "NX", "EX", 300); // 空值缓存 5 分钟
    }
    return value;
}

6. 总结

  • 缓存击穿:热点数据过期 + 高并发请求,解决方案包括设置热点数据永不过期、加分布式锁、缓存预热。
  • 缓存穿透:查询不存在的数据,解决方案包括缓存空值、布隆过滤器、参数校验。
  • 缓存雪崩:大量缓存同时过期,解决方案包括设置不同的过期时间、缓存高可用、限流降级、缓存预热。

通过理解这三种问题的定义、原因和解决方案,可以更好地应对 Redis 缓存相关的面试问题。

THE END
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容