缓存击穿和缓存穿透是两个截然不同的概念,它们发生的场景、原因和解决方案都有本质区别。
核心结论
- 缓存穿透:访问一个根本不存在的数据。相当于你要找一个不存在的人,查了通讯录(缓存)没找到,又去翻了全市户口本(数据库)还是没找到。这是“数据存在性”问题。
- 缓存击穿:访问一个存在但刚好过期的热点数据。相当于你要找市长,但市长刚好在开会(缓存过期),导致所有电话都打到会议室(数据库)。这是“热点数据并发”问题。
第一层:缓存穿透的“庖丁解牛”
攻击原理
攻击者故意频繁请求一个数据库中根本不存在的数据。
攻击流程
- 请求缓存:
cache.get('user_9999999')->缓存未命中 - 请求数据库:
SELECT * FROM users WHERE id = 9999999->数据库查询为空 - 由于数据不存在,无法写入缓存(或者只能缓存一个短暂的空值)
- 下一个相同请求到来,重复步骤1-3
造成危害
- 数据库压力:恶意请求会绕过缓存,直接冲击数据库,可能导致数据库宕机。
- 资源浪费:大量无效查询消耗系统资源。
解决方案
- 缓存空对象:即使数据库查询为空,也在缓存中存储一个空值(如
NULL)并设置一个较短的过期时间(如1-5分钟)。$user=$cache->get('user_'.$id);if($user===null){$user=$db->query("SELECT * FROM users WHERE id =$id");if(!$user){// 数据库也没有,缓存一个空对象,避免频繁查询数据库$cache->set('user_'.$id,false,300);// 缓存5分钟}else{$cache->set('user_'.$id,$user,3600);}}return$user!==false?$user:null; - 布隆过滤器:在缓存前加一层布隆过滤器,快速判断数据是否可能存在。如果布隆过滤器说数据不存在,则直接返回,不查询缓存和数据库。
第二层:缓存击穿的“庖丁解牛”
发生场景
某个热点数据(如热门商品信息、明星微博)在缓存中过期瞬间,有大量并发请求同时到来。
击穿流程
- 热点Key缓存过期:
cache.get('hot_product_123')->缓存过期 - 瞬间有数万个并发请求同时发现缓存失效
- 数万个请求同时去查询数据库:
SELECT * FROM products WHERE id = 123 - 数据库瞬间承受巨大压力,可能被打挂
造成危害
- 数据库雪崩:热点数据的并发查询可能导致数据库连接池耗尽、CPU飙高。
- 系统瘫痪:整个服务因一个热点Key的失效而不可用。
解决方案
- 互斥锁:第一个发现缓存过期的请求获取分布式锁,然后查询数据库并重建缓存,其他请求等待。
publicfunctiongetProduct($id){$key='product_'.$id;$product=$cache->get($key);if($product===null){// 缓存失效$lockKey='lock:'.$key;if($cache->add($lockKey,1,10)){// 尝试获取锁// 获取锁成功,查询数据库$product=$db->query("SELECT * FROM products WHERE id =$id");$cache->set($key,$product,3600);$cache->delete($lockKey);// 释放锁}else{// 没获取到锁,等待重试或返回默认值usleep(100000);// 等待100msreturn$this->getProduct($id);// 重试}}return$product;} - 永不过期+逻辑过期:缓存不设置过期时间,但值中包含逻辑过期时间。后台任务定期更新缓存。
- 热点数据预加载:在缓存过期前,提前异步刷新缓存。
第三层:对比总结表
| 特征 | 缓存穿透 | 缓存击穿 |
|---|---|---|
| 攻击目标 | 不存在的数据 | 存在但过期的高并发数据 |
| 请求性质 | 恶意攻击或错误请求 | 正常的用户请求 |
| 并发量 | 可能不高,但持续不断 | 瞬间高并发 |
| 根本问题 | 数据不存在性判断 | 热点数据并发重建 |
| 解决方案 | 1. 缓存空值 2. 布隆过滤器 | 1. 互斥锁 2. 逻辑过期 |
第四层:实战场景示例
缓存穿透场景
场景:攻击者用脚本遍历用户ID:/api/user/1,/api/user/2, …,/api/user/1000000
结果:大部分ID都不存在,每次请求都穿透到数据库。
缓存击穿场景
场景:双十一零点,数万用户同时点击"秒杀iPhone"商品页面,该商品缓存刚好在11:59:59过期。
结果:零点瞬间数万请求同时打向数据库查询同一商品信息。
第五层:还有一个"缓存雪崩"
为了完整起见,我们提一下相关的第三个概念:
缓存雪崩:大量的缓存Key在同一时间点或时间段内集中过期,导致所有请求都落到数据库上。
- 与击穿的区别:击穿是单个热点Key,雪崩是大量Key同时失效。
- 解决方案:给缓存过期时间加上随机值,避免同时过期。
终极总结
- 穿透:查无此数据→ 用布隆过滤器或缓存空值解决
- 击穿:查热点数据但缓存刚好失效 → 用互斥锁解决
- 雪崩:大量数据缓存同时失效 → 用随机过期时间解决
理解这三者的区别,是设计高可用缓存架构的基础。它们虽然都导致数据库压力,但病因不同,治疗方案也完全不同。