今天,我将用真实代码案例+通俗比喻,带你彻底看透这两个“缓存杀手”的本质区别,并分享一线大厂都在用的解决方案。
缓存穿透 vs 缓存击穿
核心定义(数据存在性决定一切)
- 缓存穿透:请求的数据既不在缓存,也不在数据库(例如恶意请求ID=-1)。
- 就像你问图书馆管理员找一本《如何饲养外星人》,管理员查遍系统发现这本书根本不存在,但每天有1000个人来问同样的问题。
- 缓存击穿:热点数据突然失效,大量请求瞬间穿透到数据库(例如爆款商品缓存过期)。
- 好比网红奶茶店每天下午3点补货,但今天店员忘记补货,门口排队1000人同时打电话问总部“还有奶茶吗?”
关键差异对照表
维度 | 缓存穿透 | 缓存击穿 |
数据存在性 | 数据库不存在 | 数据库存在 |
触发原因 | 非法请求、系统漏洞 | 热点数据集中失效 |
攻击性质 | 外部攻击(如黑客) | 内部设计缺陷 |
典型场景 | 请求负数ID、乱码参数 | 秒杀商品缓存过期 |
杀伤力 | 持续消耗数据库资源 | 瞬时压垮数据库 |
穿透篇:如何用“空气盾牌”防御不存在的数据?
布隆过滤器:给数据库装上“安检门”
- 原理:通过哈希算法生成数据指纹,用1%的内存挡住99%的非法请求
- 实战代码(Guava实现)
// 初始化100万容量,误判率0.1%
BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8), 1000000, 0.001);
// 预热合法数据
validIds.forEach(filter::put);
// 请求拦截
if (!filter.mightContain(requestId)) {
return "非法请求!";
}
- 致命缺陷:误判率存在(可通过增加哈希函数降低)
缓存空值:用“假数据”欺骗攻击者
- 策略:将null值缓存5分钟,设置特殊标记(如"NULL_FLAG")
- 注意事项:必须设置较短过期时间(防止存储垃圾数据)需定期清理历史空键(防止内存泄漏)
组合拳:过滤器+空值+限流
某支付系统采用三级防御:
- Nginx层过滤非法参数(正则匹配ID格式)
- 布隆过滤器拦截90%恶意请求
- 剩余请求若查无数据,缓存空值并触发报警
击穿篇:热点数据失效时,如何避免“万人挤独木桥”?
互斥锁(Mutex Lock):让请求排队的智慧
- Redisson分布式锁实现
RLock lock = redisson.getLock("product_lock_" + productId);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 双重检查锁定
data = cache.get(productId);
if (data == null) {
data = db.query(...);
cache.set(productId, data);
}
}
} finally {
lock.unlock();
}
- 代价:部分用户需等待(可配合进度条提升体验)
逻辑过期:给数据加上“保鲜膜”
- 方案对比
- 策略物理过期逻辑过期存储内容数据+过期时间数据+逻辑过期时间更新方式定时全量更新异步增量更新适用场景普通数据高频热点数据
阿里云最佳实践:二级缓存架构
- L1本地缓存(Caffeine):存储极热点数据,永不过期
- L2分布式缓存(Redis):设置随机过期时间(30分钟±5分钟)
- 异步线程监控热点数据,提前刷新缓存
综合防御:从代码到架构的全链路方案
1. 监控指标体系建设
- 核心指标:缓存穿透率 = 空查询次数 / 总查询次数击穿预警值 = 热点Key访问QPS / 数据库最大承载QPS
- Grafana监控看板示例

2. 压力测试实战指南
使用JMeter模拟两种场景:
- 穿透测试:构造10万条随机非法ID观察数据库CPU是否超过80%
- 击穿测试:选定一个热点Key,在缓存过期瞬间发起5000QPS验证是否触发熔断机制
3. 容灾兜底方案
- 分级降级策略:一级降级:返回默认静态页面二级降级:启用本地缓存数据三级降级:直接拒绝部分请求
在2025年的今天,随着硬件成本下降,有人质疑:“还需要抠这些缓存细节吗?”但根据最新测试数据:一套完善的缓存方案,仍能让系统性能提升8-12倍。
记住这两个关键公式:
系统稳定性 = 对穿透的防御强度 × 对击穿的响应速度
程序员的价值 = 预防问题的能力 + 快速止血的速度