
1) 【一句话结论】:针对课程信息高频访问(QPS约5000)、低更新频率(每天1-2次)的场景,采用**多级缓存(本地+分布式)**结合分布式锁,通过预加载/互斥锁处理缓存击穿,随机过期/热备份应对缓存雪崩,用Redis SETNX+EXPIRE实现分布式锁,确保高并发下数据一致性。
2) 【原理/概念讲解】:
SETNX(设置键值,若键不存在则返回1)结合EXPIRE(设置过期时间),确保锁的原子性和时效性。3) 【对比与适用场景】:
| 对比项 | 缓存击穿 | 缓存雪崩 | 分布式锁 vs 本地锁 |
|---|---|---|---|
| 定义 | 热点数据缓存过期,大量请求访问数据库 | 大量缓存同时失效,请求全部去数据库 | 分布式系统资源互斥访问 |
| 处理方式 | 预加载、互斥锁 | 随机过期、热备份 | Redis SETNX+EXPIRE |
| 适用场景 | 热点数据,更新频率低 | 缓存集群,避免集中失效 | 分布式系统,跨进程/节点 |
| 注意点 | 需优化锁粒度(如粗粒度锁或本地缓存) | 需热备份或随机过期 | 锁值唯一性、过期时间动态调整 |
4) 【示例】(伪代码):
public Course getCourseInfo(String courseId) {
// 1. 本地缓存优先
Course course = localCache.get(courseId);
if (course != null) return course;
// 2. 分布式缓存
course = redisCache.get(courseId);
if (course != null) {
localCache.put(courseId, course); // 回写本地缓存
return course;
}
// 3. 缓存未命中,加分布式锁
String lockKey = "course:" + courseId + ":lock";
String lockValue = UUID.randomUUID().toString();
try {
// 尝试加锁
if (redis.setnx(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
// 加锁成功,查数据库
course = db.queryCourse(courseId);
if (course != null) {
// 更新缓存
redis.set(courseId, course, 3600); // 1小时过期
localCache.put(courseId, course);
}
} else {
// 加锁失败,等待重试
Thread.sleep(100);
return getCourseInfo(courseId);
}
} catch (Exception e) {
throw new RuntimeException("获取课程信息失败", e);
} finally {
// 释放锁
if (redis.exists(lockKey) && redis.get(lockKey).equals(lockValue)) {
redis.del(lockKey);
}
}
return course;
}
5) 【面试口播版答案】:面试官您好,针对课程信息高频访问(QPS约5000)、低更新频率(每天1-2次)的场景,我会设计如下缓存策略:首先,采用多级缓存(本地缓存+分布式缓存),本地缓存(如ConcurrentHashMap)用于高频访问,分布式缓存(如Redis)用于数据持久化,减少数据库压力。针对缓存击穿(热点数据缓存过期,大量请求访问数据库),通过预加载(如每天凌晨更新缓存)或互斥锁(加分布式锁,确保同一时间仅一个线程查数据库并更新缓存)处理;针对缓存雪崩(大量缓存同时失效,请求全部去数据库),用随机过期时间(避免集中失效)或热点数据热备份(多个节点缓存同一数据)。分布式锁用Redis的SETNX+EXPIRE实现,保证加锁的互斥性,并设置10秒过期时间防止死锁。具体流程:获取课程信息时,先查本地缓存,再查分布式缓存,缓存未命中时加分布式锁,查数据库后更新缓存。这样既能保证高并发下的性能,又能避免缓存失效导致的服务雪崩。
6) 【追问清单】:
7) 【常见坑/雷区】: