Interview AiBox logo

Interview AiBox 实时 AI 助手,让你自信应答每一场面试

download免费下载
3local_fire_department5 次面试更新于 2025-09-03account_tree思维导图

请解释缓存击穿、雪崩和穿透的概念以及如何解决这些问题

lightbulb

题型摘要

缓存击穿、雪崩和穿透是缓存系统中的三个常见问题。缓存击穿指热点key失效导致大量请求直接访问数据库;缓存雪崩指大量key同时失效导致数据库压力剧增;缓存穿透指查询不存在的数据导致请求绕过缓存直接访问数据库。解决这些问题需要综合运用多种策略,如互斥锁、热点数据永不过期、过期时间随机化、多级缓存、熔断降级、缓存空对象、布隆过滤器和请求限流等。通过合理的缓存设计和优化,可以有效提高系统性能和稳定性。

缓存击穿、雪崩和穿透的概念及解决方案

在缓存系统中,缓存击穿、雪崩和穿透是三个常见且重要的问题,它们都会对系统性能和稳定性产生负面影响。下面我将详细解释这些概念及其解决方案。

一、缓存击穿

概念

缓存击穿指的是一个热点key(访问非常频繁的key)在某一刻失效,此时大量的并发请求直接访问数据库,导致数据库压力瞬间增大,甚至可能压垮数据库。

原因

  • 热点key过期
  • 热点key被意外删除

影响

  • 数据库负载突然增加
  • 系统响应时间变长
  • 可能导致数据库宕机

解决方案

1. 互斥锁(Mutex Lock)

当缓存失效时,使用互斥锁或分布式锁,只允许一个线程查询数据库并重建缓存,其他线程等待并尝试从缓存获取数据。

public String getWithMutex(String key) {
    String value = redis.get(key);
    if (value == null) {
        try {
            // 获取锁
            if (lock.tryLock()) {
                // 双重检查,防止其他线程已经重建了缓存
                value = redis.get(key);
                if (value == null) {
                    // 查询数据库
                    value = db.query(key);
                    // 重建缓存
                    redis.set(key, value, expireTime);
                }
            } else {
                // 等待并重试
                Thread.sleep(50);
                return getWithMutex(key);
            }
        } finally {
            lock.unlock();
        }
    }
    return value;
}

2. 热点数据永不过期

对于热点数据,可以设置较长的过期时间,甚至永不过期,通过后台任务定期更新这些数据。

public String getWithHotspot(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        // 设置较长的过期时间或永不过期
        redis.set(key, value, 24 * 60 * 60); // 24小时
    }
    return value;
}

3. 提前刷新缓存

在缓存过期前,主动刷新缓存,可以通过定时任务或后台线程实现。

@Scheduled(fixedRate = 60 * 1000) // 每分钟执行一次
public void refreshHotKeys() {
    List<String> hotKeys = getHotKeys(); // 获取热点key列表
    for (String key : hotKeys) {
        Long ttl = redis.ttl(key);
        if (ttl != null && ttl < 300) { // 如果剩余时间小于5分钟
            String value = db.query(key);
            redis.set(key, value, expireTime);
        }
    }
}

二、缓存雪崩

概念

缓存雪崩指的是在某一时刻大量的key同时失效,导致大量的请求直接访问数据库,引起数据库压力剧增,甚至导致数据库宕机。

原因

  • 大量key设置了相同的过期时间
  • 缓存服务器宕机
  • 缓存服务发生网络分区

影响

  • 数据库负载急剧增加
  • 系统响应时间变长
  • 可能导致级联故障,整个系统不可用

解决方案

1. 过期时间随机化

为不同的key设置随机的过期时间,避免同时失效。

public String getWithRandomExpire(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        // 设置基础过期时间
        int baseExpire = 60 * 60; // 1小时
        // 添加随机时间(例如0-30分钟)
        int randomExpire = (int)(Math.random() * 30 * 60);
        // 设置最终过期时间
        redis.set(key, value, baseExpire + randomExpire);
    }
    return value;
}

2. 缓存集群高可用

使用主从复制、哨兵或集群模式,确保缓存服务的高可用。

--- title: Redis高可用架构 --- graph TD A[应用服务器] --> B[Redis哨兵] A --> C[Redis主节点] A --> D[Redis从节点1] A --> E[Redis从节点2] B --> C B --> D B --> E C --> D C --> E

3. 多级缓存

使用多级缓存策略,如本地缓存 + 分布式缓存,当分布式缓存失效时,本地缓存仍能提供服务。

--- title: 多级缓存架构 --- graph TD A[用户请求] --> B[应用服务器] B --> C[本地缓存] B --> D[分布式缓存] B --> E[数据库] C -->|缓存未命中| D D -->|缓存未命中| E

4. 熔断降级

当检测到缓存不可用或数据库压力过大时,启动熔断机制,返回默认值或错误信息,保护数据库。

@HystrixCommand(fallbackMethod = "getDefaultData",
        commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "100"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
        })
public String getDataWithCircuitBreaker(String key) {
    String value = redis.get(key);
    if (value == null) {
        value = db.query(key);
        redis.set(key, value, expireTime);
    }
    return value;
}

public String getDefaultData(String key) {
    return "系统繁忙,请稍后再试";
}

5. 缓存预热

系统启动或重启后,提前加载热点数据到缓存,避免系统刚启动时大量请求直接访问数据库。

@PostConstruct
public void warmUpCache() {
    List<String> hotKeys = getHotKeys(); // 获取热点key列表
    for (String key : hotKeys) {
        String value = db.query(key);
        redis.set(key, value, expireTime);
    }
}

三、缓存穿透

概念

缓存穿透指的是查询一个不存在的数据,由于缓存中没有,请求会直接访问数据库,但数据库中也没有这个数据,所以无法写入缓存。如果大量这样的请求出现,会直接穿透缓存访问数据库,导致数据库压力增大。

原因

  • 恶意攻击:故意查询不存在的数据
  • 业务逻辑错误:查询条件错误导致数据不存在
  • 爬虫或机器人:大量请求不存在的数据

影响

  • 数据库负载增加
  • 缓存命中率降低
  • 系统资源浪费

解决方案

1. 缓存空对象(Null Caching)

即使数据库中不存在数据,也将空结果缓存起来,设置较短的过期时间,避免占用过多缓存空间。

public String getWithNullCaching(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        // 即使数据库返回null,也缓存空值
        if (value == null) {
            redis.set(key, "", 60); // 缓存空值,设置较短的过期时间
            return null;
        }
        // 重建缓存
        redis.set(key, value, expireTime);
    } else if ("".equals(value)) {
        // 缓存中的空值
        return null;
    }
    return value;
}

2. 布隆过滤器(Bloom Filter)

使用布隆过滤器快速判断一个key是否可能存在,如果布隆过滤器说key不存在,直接返回,不访问数据库。

public String getWithBloomFilter(String key) {
    // 先检查布隆过滤器
    if (!bloomFilter.mightContain(key)) {
        return null; // key一定不存在
    }
    
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        if (value != null) {
            // 重建缓存
            redis.set(key, value, expireTime);
            // 添加到布隆过滤器
            bloomFilter.put(key);
        } else {
            // 缓存空值
            redis.set(key, "", 60);
        }
    } else if ("".equals(value)) {
        // 缓存中的空值
        return null;
    }
    return value;
}

3. 请求限流

对查询请求进行限流,防止大量恶意请求,可以使用令牌桶或漏桶算法。

@RateLimiter(value = 100) // 每秒最多100个请求
public String getWithRateLimiter(String key) {
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        if (value != null) {
            // 重建缓存
            redis.set(key, value, expireTime);
        } else {
            // 缓存空值
            redis.set(key, "", 60);
        }
    } else if ("".equals(value)) {
        // 缓存中的空值
        return null;
    }
    return value;
}

4. 参数校验

在查询前对请求参数进行校验,过滤掉非法请求,例如,检查ID格式、范围等。

public String getWithValidation(String key) {
    // 参数校验
    if (!isValidKey(key)) {
        return null; // 非法key,直接返回
    }
    
    String value = redis.get(key);
    if (value == null) {
        // 查询数据库
        value = db.query(key);
        if (value != null) {
            // 重建缓存
            redis.set(key, value, expireTime);
        } else {
            // 缓存空值
            redis.set(key, "", 60);
        }
    } else if ("".equals(value)) {
        // 缓存中的空值
        return null;
    }
    return value;
}

private boolean isValidKey(String key) {
    // 校验key格式,例如必须是数字
    return key != null && key.matches("\\d+");
}

四、综合解决方案

在实际应用中,通常会结合多种策略来解决缓存问题。下面是一个综合解决方案的示例:

--- title: 缓存问题综合解决方案 --- graph TD A[用户请求] --> B[参数校验] B -->|非法请求| C[直接返回] B -->|合法请求| D[布隆过滤器] D -->|key不存在| C D -->|key可能存在| E[请求限流] E -->|超过限制| F[返回错误] E -->|通过限制| G[本地缓存] G -->|命中| H[返回数据] G -->|未命中| I[分布式缓存] I -->|命中| H I -->|未命中| J[获取互斥锁] J -->|获取成功| K[查询数据库] J -->|获取失败| L[等待并重试] K -->|数据存在| M[更新缓存] K -->|数据不存在| N[缓存空值] M --> H N --> H

五、总结

缓存击穿、雪崩和穿透是缓存系统中的三个常见问题,它们各有特点:

  • 缓存击穿:热点key失效导致大量请求直接访问数据库
  • 缓存雪崩:大量key同时失效导致数据库压力剧增
  • 缓存穿透:查询不存在的数据导致请求绕过缓存直接访问数据库

解决这些问题需要综合运用多种策略,如互斥锁、热点数据永不过期、过期时间随机化、多级缓存、熔断降级、缓存空对象、布隆过滤器和请求限流等。通过合理的缓存设计和优化,可以有效提高系统性能和稳定性。

参考文档

  1. Redis官方文档 - Redis作为缓存
  2. Martin Fowler - CacheStrategies
  3. 布隆过滤器 - 维基百科
  4. Hystrix官方文档
account_tree

思维导图

Interview AiBox logo

Interview AiBox — 面试搭档

不只是准备,更是实时陪练

Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。

AI 助读

一键发送到常用 AI

缓存击穿、雪崩和穿透是缓存系统中的三个常见问题。缓存击穿指热点key失效导致大量请求直接访问数据库;缓存雪崩指大量key同时失效导致数据库压力剧增;缓存穿透指查询不存在的数据导致请求绕过缓存直接访问数据库。解决这些问题需要综合运用多种策略,如互斥锁、热点数据永不过期、过期时间随机化、多级缓存、熔断降级、缓存空对象、布隆过滤器和请求限流等。通过合理的缓存设计和优化,可以有效提高系统性能和稳定性。

智能总结

深度解读

考点定位

思路启发

auto_awesome

相关题目

请做一个自我介绍

自我介绍是面试的开场环节,需简洁有力地展示个人背景、技能经验与岗位匹配度。有效结构包括:开场问候、核心经历、技能展示、成就亮点、岗位认知、职业规划、公司了解和得体收尾。针对运维岗位,应突出Linux管理、网络配置、自动化部署等技术能力,并结合具体案例和量化成果。表达要真诚自然,时间控制在2-3分钟,展现自信和对公司的了解。

arrow_forward

请详细介绍一下你参与的项目

项目经验介绍应包括项目背景、个人角色、技术栈、工作内容、挑战与解决方案、成果收获以及与岗位的关联。通过具体案例展示技术能力和问题解决能力,突出与运维岗位相关的经验和技能,如系统部署、监控、故障排查、自动化运维等。同时体现团队协作和持续学习的态度。

arrow_forward

请介绍一下你的项目经验

在面试中介绍项目经验时,应选择与运维岗位最相关的项目,按"项目背景→个人职责→技术栈→难点与解决方案→项目成果"的结构进行介绍。重点突出自己在项目中的技术贡献、解决问题的能力以及与运维岗位相关的经验。通过具体案例展示自己的技术实力、学习能力和团队协作精神,并将项目经验与应聘岗位联系起来,展示自己的匹配度和价值。

arrow_forward

请进行自我介绍并详细介绍你参与过的项目

自我介绍和项目经验是面试的重要环节。优秀的自我介绍应简洁明了地展示个人背景、专业技能和职业规划;项目经验介绍则应选择与岗位相关的项目,详细说明项目背景、个人职责、使用技术、解决方案和项目成果。回答时应突出与岗位相关的技能和经验,展现专业能力和解决问题的能力,同时保持自信和真诚的态度。

arrow_forward

请详细介绍你简历中提到的项目,包括实现细节和遇到的问题

面试中介绍项目经验时,应选择与运维岗位最相关的项目,按照"项目背景-个人职责-技术实现-遇到问题-解决方案-项目成果"的结构进行介绍。重点突出个人贡献、技术细节和解决问题的能力,用数据量化项目成果。示例包括校园服务器集群自动化运维平台和基于Kubernetes的微服务部署与运维两个项目,展示了监控模块设计、CI/CD流水线构建、故障排查等运维核心能力。

arrow_forward

阅读状态

阅读时长

9 分钟

阅读进度

6%

章节:18 · 已读:1

当前章节: 一、缓存击穿

最近更新:2025-09-03

本页目录

Interview AiBox logo

Interview AiBox

AI 面试实时助手

面试中屏幕实时显示参考回答,帮你打磨表达。

免费下载download

分享题目

复制链接,或一键分享到常用平台

外部分享