本文目录导读:

这是一个非常经典的面试题,也是高并发系统设计中必须考虑的问题。
核心理解:
- 缓存穿透:指查询一个根本不存在的数据,由于缓存中不命中,请求直接打到数据库,而数据库里也没有这条记录,导致每次查询都穿透缓存直达数据库。
- 后果:如果有人恶意攻击,大量请求“不存在的数据”(比如不断请求
user_id=-1或随机ID),数据库会因承受巨大压力而崩溃。
缓存穿透的四大解决方案
以下是业界最常用的解决方案,按推荐程度和适用场景排序。
缓存空对象(最常见、最直接)
这是最基础也最容易实现的方法。
- 做法:当数据库查询结果为空时,仍然将这个“空结果”(如
null、、特殊标记)缓存起来,并设置一个较短的过期时间(30-60 秒)。 - 优点:实现简单,能有效防止大量相同“不存在 key”的请求穿透。
- 缺点:
- 需要存储大量空值,浪费缓存空间。
- 如果过期时间设置不当,可能导致一段时间内数据和数据库不一致(比如数据刚被写入数据库,但缓存中的空对象还没过期)。
代码示例(伪代码):
public User getUserById(String userId) {
// 1. 从缓存获取
User user = cache.get(userId);
if (user != null) {
return user;
}
// 2. 缓存未命中,查数据库
user = dao.getUserById(userId);
// 3. 关键步骤:无论数据库有没有,都写入缓存
if (user == null) {
// 缓存空对象,设置短过期时间(例如30秒)
cache.set(userId, new User(), 30);
} else {
cache.set(userId, user, 3600); // 正常数据缓存1小时
}
return user;
}
布隆过滤器(最优雅、最彻底)
在请求到达缓存层之前,先用布隆过滤器判断 key 是否可能存在。
- 做法:将数据库中所有存在的 key(如用户ID)预加载到布隆过滤器中,当一个请求来时,先检查过滤器。
- 如果布隆过滤器说“不存在”,则直接返回“数据不存在”,根本不去查询缓存或数据库。
- 如果布隆过滤器说“存在”,再去查询缓存或数据库。
- 优点:内存占用极低(几百万数据只占几十MB),查询速度极快(O(1)),能100%拦截不存在的 key 对后端的冲击。
- 缺点:
- 存在误判率(布隆过滤器说“存在”时可能不存在,但说“不存在”时一定不存在),业务上可以接受少量误判。
- 数据更新时需要同步更新布隆过滤器,维护成本略高(可以使用布谷鸟过滤器或分段布隆过滤器来解决)。
- 典型场景:新闻推荐、用户ID校验、防止重复爬虫。
接口校验(前端防御)
将非法请求拦截在业务逻辑之前。
- 做法:
- 对用户输入进行严格的合法性校验(如 ID 格式、长度、是否为负数等)。
- 对于敏感数据(如ID为0或负数),直接返回错误,不继续查询。
- 场景:简单有效,但无法防御所有攻击(比如请求一个根本不存在的合法格式的ID,如
user_id=999999)。
热点参数限流/降级
在高并发场景下作为兜底方案。
- 做法:监控数据库查询频率,如果发现某个 key 的数据库QPS(每秒查询率)异常升高,对该 key 的请求进行限流或熔断,返回默认值或降级信息。
- 场景:防止恶意攻击打垮数据库,但属于“治标不治本”,一般配合上述方案使用。
方案对比与选择
| 方案 | 适用场景 | 复杂度 | 内存占用 | 防御效果 | 主要缺点 |
|---|---|---|---|---|---|
| 缓存空对象 | 通用,小规模系统、数据量不大 | 低 | 高(存很多空key) | 拦截重复穿透 | 浪费内存,有短暂不一致 |
| 布隆过滤器 | 大规模系统、高并发、数据量级大 | 中 | 极低 | 彻底拦截(零漏网) | 有误判率,需数据同步 |
| 接口校验 | 任何系统,作为辅助 | 低 | 无 | 拦截明显恶意请求 | 无法防御合法格式的不存在ID |
| 限流/降级 | 兜底方案,保护数据库 | 高 | 低 | 被动防御 | 影响正常用户 |
面试回答建议
当面试官问这个问题时,建议按以下逻辑回答,展现你的思考深度:
- 先定义问题:“缓存穿透指的是查询一个不存在的数据,导致请求绕过缓存直接打到数据库,如果并发高,数据库可能瞬间被打爆。”
- 提出解决方案,按推荐顺序:
- “最直接的办法是缓存空对象,即使数据库没有,也缓存一个空结果并设置短过期时间,这能解决大部分问题,但会消耗缓存空间。”
- “更优的方案是使用布隆过滤器,在应用启动时,将数据库所有主键加载到布隆过滤器中,请求到达时先判断 key 是否在过滤器中,如果不存在,直接拒绝,这种方法内存占用极低,能从根本上拦截穿透。”
- “接口层做参数校验(比如ID格式、范围)是必须的,可以在布隆过滤器之前再拦截一层恶意攻击。”
- 结合业务场景说明:“如果业务数据量巨大(比如上亿用户ID),我会优先选择布隆过滤器;如果业务数据量较小或者团队想快速上线,可以先采用缓存空对象,后续再优化为布隆过滤器。”
这样的回答既能体现你对技术原理的掌握,也能展示你根据场景权衡利弊的工程思维。