在面试中,如果被问到 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
暂无评论内容