Interview AiBoxInterview AiBox 实时 AI 助手,让你自信应答每一场面试
请解释缓存击穿、雪崩和穿透的概念以及如何解决这些问题
题型摘要
缓存击穿、雪崩和穿透是缓存系统中的三个常见问题。缓存击穿指热点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. 缓存集群高可用
使用主从复制、哨兵或集群模式,确保缓存服务的高可用。
3. 多级缓存
使用多级缓存策略,如本地缓存 + 分布式缓存,当分布式缓存失效时,本地缓存仍能提供服务。
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+");
}
四、综合解决方案
在实际应用中,通常会结合多种策略来解决缓存问题。下面是一个综合解决方案的示例:
五、总结
缓存击穿、雪崩和穿透是缓存系统中的三个常见问题,它们各有特点:
- 缓存击穿:热点key失效导致大量请求直接访问数据库
- 缓存雪崩:大量key同时失效导致数据库压力剧增
- 缓存穿透:查询不存在的数据导致请求绕过缓存直接访问数据库
解决这些问题需要综合运用多种策略,如互斥锁、热点数据永不过期、过期时间随机化、多级缓存、熔断降级、缓存空对象、布隆过滤器和请求限流等。通过合理的缓存设计和优化,可以有效提高系统性能和稳定性。
参考文档
思维导图
Interview AiBoxInterview AiBox — 面试搭档
不只是准备,更是实时陪练
Interview AiBox 在面试过程中提供实时屏幕提示、AI 模拟面试和智能复盘,让你每一次回答都更有信心。
AI 助读
一键发送到常用 AI
缓存击穿、雪崩和穿透是缓存系统中的三个常见问题。缓存击穿指热点key失效导致大量请求直接访问数据库;缓存雪崩指大量key同时失效导致数据库压力剧增;缓存穿透指查询不存在的数据导致请求绕过缓存直接访问数据库。解决这些问题需要综合运用多种策略,如互斥锁、热点数据永不过期、过期时间随机化、多级缓存、熔断降级、缓存空对象、布隆过滤器和请求限流等。通过合理的缓存设计和优化,可以有效提高系统性能和稳定性。
智能总结
深度解读
考点定位
思路启发
相关题目
请做一个自我介绍
自我介绍是面试的开场环节,需简洁有力地展示个人背景、技能经验与岗位匹配度。有效结构包括:开场问候、核心经历、技能展示、成就亮点、岗位认知、职业规划、公司了解和得体收尾。针对运维岗位,应突出Linux管理、网络配置、自动化部署等技术能力,并结合具体案例和量化成果。表达要真诚自然,时间控制在2-3分钟,展现自信和对公司的了解。
请详细介绍一下你参与的项目
项目经验介绍应包括项目背景、个人角色、技术栈、工作内容、挑战与解决方案、成果收获以及与岗位的关联。通过具体案例展示技术能力和问题解决能力,突出与运维岗位相关的经验和技能,如系统部署、监控、故障排查、自动化运维等。同时体现团队协作和持续学习的态度。
请介绍一下你的项目经验
在面试中介绍项目经验时,应选择与运维岗位最相关的项目,按"项目背景→个人职责→技术栈→难点与解决方案→项目成果"的结构进行介绍。重点突出自己在项目中的技术贡献、解决问题的能力以及与运维岗位相关的经验。通过具体案例展示自己的技术实力、学习能力和团队协作精神,并将项目经验与应聘岗位联系起来,展示自己的匹配度和价值。
请进行自我介绍并详细介绍你参与过的项目
自我介绍和项目经验是面试的重要环节。优秀的自我介绍应简洁明了地展示个人背景、专业技能和职业规划;项目经验介绍则应选择与岗位相关的项目,详细说明项目背景、个人职责、使用技术、解决方案和项目成果。回答时应突出与岗位相关的技能和经验,展现专业能力和解决问题的能力,同时保持自信和真诚的态度。
请详细介绍你简历中提到的项目,包括实现细节和遇到的问题
面试中介绍项目经验时,应选择与运维岗位最相关的项目,按照"项目背景-个人职责-技术实现-遇到问题-解决方案-项目成果"的结构进行介绍。重点突出个人贡献、技术细节和解决问题的能力,用数据量化项目成果。示例包括校园服务器集群自动化运维平台和基于Kubernetes的微服务部署与运维两个项目,展示了监控模块设计、CI/CD流水线构建、故障排查等运维核心能力。