一、缓存穿透
- 定义: 请求查询 不存在的数据 ,缓存和数据库均无此数据,导致请求直接穿透到数据库
- 举例(来自小豆):想象你经营着一家咖啡店,为了提高效率,你在吧台设置了一个 "常见点单记录本"(相当于缓存),上面记着顾客常点的咖啡(比如拿铁、美式)和价格。 正常流程是这样的:当顾客进来点单时,你先看一眼记录本(查缓存),如果有就直接报价;如果记录本上没有,你再去查后台的价目表(查数据库),同时把这个新咖啡信息记到记录本上,方便下次查询。但如果遇到这样的顾客:他每次来都点一种根本不存在的咖啡,比如 "宇宙无敌超级冰美式"。这时候你会先看记录本(缓存没命中),然后去查价目表(数据库也没有)。更麻烦的是,因为这种咖啡根本不存在,你没法写到记录本上。如果有 100 个这样的顾客同时来,都点这种不存在的咖啡,你就得跑 100 次去查价目表,本来能轻松应对的正常顾客,反而因为你太忙而得不到服务了。 这就是缓存穿透:
简单说,缓存穿透就是:查询的数据在缓存和数据库中都不存在,导致每次请求都要穿透缓存直接访问数据库,大量这种请求会拖垮数据库。
解决办法也很容易理解:
- 在门口放个保安(参数校验),直接拦下点 "明显不存在的咖啡" 的顾客
- 即使遇到不存在的咖啡,也在记录本上记一笔 "没有这种咖啡"(缓存空值),避免重复查询
- 把所有真实存在的咖啡名列个清单(布隆过滤器),先对照清单看有没有,没有就直接拒绝
这样就能保证大部分请求被拦截在缓存层,数据库不会被无效请求拖垮。
但是根据现实中的有些实际情况来看的话,上面的举例中有个不一样的地方就是:现实中,一般人在进入店中可以根据墙上的菜单或者纸质的菜单去进行点单,这就与小豆举的例子中顾客点单的时候不去看那个“清单”不否,但是按照程序及组件的作用仔细思考一下,程序中的布隆过滤器属于服务端的一个组件,也就是我们后端程序员开发时接触的东西,而真正的现实中的消费者是没有办法看见这个“过滤器”的,所以也就是为什么在举例中顾客不会去看“清单”的原因。
这里给出当时小豆的回答:www.doubao.com/thread/w039…
二、缓存击穿
- 定义:热点数据的缓存 突然失效(如过期) ,瞬间大量请求冲击数据库
- 举例(来自小豆):假设你的咖啡店有款爆火的咖啡叫 "招牌云朵拿铁",每天能卖 1000 杯。为了快速报价,你在吧台的 "常见点单记录本"(缓存)上特意记了它的价格,还标注了 "今天下班前有效"(设置了过期时间)。正常情况下,顾客点这款咖啡时,你直接看记录本报价,根本不用去查后台价目表(数据库),效率很高。但问题出在 "过期时间" 上:到了下班前的最后一分钟,突然一下子来了 20 个顾客,都要点 "招牌云朵拿铁"。你习惯性地看记录本,发现上面的记录刚好过期被擦掉了(缓存失效)。这时候你慌了,20 个顾客都在催,你只能赶紧跑去查后台价目表。可价目表在仓库里,你一次只能查一次,其他 19 个顾客只能等着。结果就是:
后面的顾客等得不耐烦,可能直接走了
你来回跑着查价目表,手忙脚乱,连其他正常点单的顾客也被耽误了
解决方法:
- 方案 1:热点 key 永不过期:对核心热点 key(如秒杀商品、热点新闻),不设置过期时间,通过后台异步任务定期更新缓存(如每 30 分钟从 DB 拉取最新数据更新缓存)。
- 方案 2:热点 key 预热:业务上线前,通过脚本或后台任务将热点数据提前加载到缓存(如秒杀商品上架前,主动查 DB 并写入缓存),避免上线后缓存冷启动。
三、缓存雪崩
- 定义:缓存层 大面积失效 (如集中过期、服务宕机),所有请求同步涌向数据库
- 举例(来自小豆):假设你经营的不是小咖啡店,而是一家连锁咖啡品牌,全国有 100 家门店。为了统一管理,总部给所有门店发了一本一模一样的 “点单记录本”(相当于分布式缓存,比如 Redis 集群),并且规定:“所有记录本上的咖啡价格,每天凌晨 2 点整统一清空,重新手写更新”(这就是大量缓存 key 设置了相同的过期时间)。
结果到了某天凌晨 2 点:
刚好赶上周末,很多门店凌晨 2 点还在营业(比如酒吧街的门店);
记录本一到点全清空了(缓存集体失效);
这时候每个门店都涌来顾客点单 —— 有人要拿铁、有人要美式、有人要卡布奇诺,总共上千个点单请求;
因为记录本是空的,所有门店的店员都得同时跑去查后台的 “总价目表”(数据库);
但总部的总价目表服务器只有 2 台,根本扛不住 100 家门店同时查,直接崩溃了(数据库宕机);
最后所有门店都没法报价,只能停业,整个品牌的生意都停了
上述的场景这就是 “雪崩”。
再换个更端的场景:如果总部的 “记录本系统”(整个缓存集群)突然断电了(缓存服务宕机),不管记录有没有过期,所有门店的记录本都用不了,同样会导致所有点单请求直接冲去查总价目表,最终引发一样的崩溃。
所以,缓存雪崩的本质就是:缓存层 “大面积失效”(要么大量 key 集中过期,要么缓存服务整体宕机),导致原本被缓存拦截的 “全量请求”,瞬间全部涌向数据库,像雪崩一样把数据库压垮,甚至引发整个服务链路瘫痪。
解决方法:(通过咖啡店的例子)
- 方法 1:给过期时间加 “随机偏移”(就像让各门店错峰更新记录本) 总部不用规定 “所有门店凌晨 2 点统一清记录本”,而是改成:“记录本的过期时间是‘24 小时 ± 1 小时’”。比如:
A 门店的记录本凌晨 1 点 50 分过期;
B 门店的凌晨 2 点 15 分过期;
C 门店的凌晨 1 点 40 分过期;这样 100 家门店的记录本失效时间分散在 “凌晨 1 点到 3 点” 之间,不会在同一瞬间全空,查价目表(数据库)的请求也会分散开,总部服务器就扛得住了.
对应到技术上:给缓存 key 设置过期时间时,在基础时间(比如 24 小时)上,加上一个随机值(比如 ±3600 秒)。
- 方法 2:对 “核心热点数据” 取消过期时间(就像把最常点的咖啡钉在记录本上):总部可以规定:“拿铁、美式、卡布奇诺这 3 款卖得最好的咖啡,记录本上永远不擦除(永不过期),每天凌晨 3 点由总部派专人上门更新价格”。这样即使其他咖啡的记录过期了,这 3 款核心咖啡的请求还是能被记录本(缓存)拦截,不会全冲去查价目表。 对应到技术上:对秒杀商品、首页推荐等核心热点 key,不设置过期时间,通过 后台异步任务 定期更新缓存(比如每小时从数据库拉一次最新数据,覆盖旧缓存),避免主动失效。
四、“缓存击穿” 和 “缓存雪崩” 的区别:
- 击穿是 “单点热点失效”(比如只有 “招牌云朵拿铁” 的缓存没了),影响范围小;
- 雪崩是 “全局缓存失效”(要么所有商品缓存都没了,要么整个缓存服务挂了),影响范围是 “整个系统”,后果更严重。
总结:
| 对比维度 | 缓存穿透(Cache Penetration) | 缓存击穿(Cache Breakdown) | 缓存雪崩(Cache Avalanche) |
|---|---|---|---|
| 核心定义 | 请求查询不存在的数据,缓存和数据库均无此数据,导致请求直接穿透到数据库 | 热点数据的缓存突然失效(如过期),瞬间大量请求冲击数据库 | 缓存层大面积失效(如集中过期、服务宕机),所有请求同步涌向数据库 |
| 触发条件 | 1. 恶意查询不存在的 key(如用户 ID=-1);2. 业务初期数据未加载到缓存 | 1. 热点 key 的缓存过期时间设置过短;2. 手动删除热点 key | 1. 大量 key 设置相同过期时间(如凌晨 0 点);2. 缓存服务集群宕机(如 Redis 集群故障) |
| 数据特征 | 目标数据不存在(缓存和 DB 均无) | 目标数据存在(DB 有,但缓存临时失效) | 目标数据普遍存在(DB 有,但缓存集体失效) |
| 影响范围 | 单点请求重复穿透(若未防护,会持续消耗 DB 资源) | 单点热点 key 的请求集中冲击 DB(影响范围较小) | 全量请求冲击 DB(可能导致 DB 宕机,服务雪崩) |