缓存三大问题
缓存三大问题
缓存穿透:
- 请求查询一个数据库中根本就不存在的数据(例如查询 id = -1 的用户)。
- 缓存层和数据库层都查不到该数据,因此每次请求都会绕过缓存直接访问数据库。
- 恶意攻击或大量无效查询会导致数据库压力剧增,甚至被打垮。
##### 解决方案
| 方案 | 实现原理 | 注意事项 |
| :----------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| 缓存空对象 | 当数据库查询结果为空时,也将这个空结果(例如 null)缓存起来,并设置较短的过期时间(如 30~60 秒)。 | 会占用少量内存,且需区分“真实空数据”与“不存在的数据”。 |
| 布隆过滤器(Bloom Filter) | 在缓存前加一层布隆过滤器,存储所有可能存在的 key。请求先经过布隆过滤器,如果判断 key 不存在,则直接拒绝访问 DB。 | 存在极低概率的误判(认为存在实际不存在),但不会漏判;适合数据量大且相对静态的场景。 |
| 参数校验 | 在业务层对请求参数进行合法性校验(如 ID 不能为负、长度限制等),拦截明显不合法的请求。 | 简单有效,但不能解决所有穿透问题。 |
缓存击穿:热点key过期
症状
- 某个热点 key(例如秒杀商品的库存、微博热搜)在缓存中恰好过期。
- 此时有大量并发请求同时访问该 key,所有请求都发现缓存未命中,于是同时涌向数据库去查询并重建缓存。
- 数据库瞬间承受巨大压力,可能导致服务响应变慢或崩溃。
解决方案
| 方案 | 实现原理 | 优缺点 |
| :----------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| 互斥锁(分布式锁) | 当缓存失效时,只允许一个线程去查询数据库并重建缓存,其他线程等待(或短暂自旋)。可用 Redis 的 SETNX 实现。 | 保证只有一个请求打到 DB,但会阻塞其他请求,影响响应时间。 |
| 逻辑过期(异步重建) | 缓存中不设置物理过期时间,而是存储一个逻辑过期字段。当读取时发现逻辑已过期,则立即返回旧值并异步启动一个线程去更新缓存。 | 不阻塞请求,用户体验好,但实现稍复杂,需保证异步更新的一致性。 |
| 永不过期 + 后台刷新 | 对热点 key 不设置过期时间(物理不过期),由后台定时任务或“被动访问时检查版本号”来更新缓存。 | 简单可靠,但需要额外的更新机制。 |
缓存雪崩:大量key同时过期
症状
- 大量 key 同时过期,或 Redis 实例宕机,导致大量请求在短时间内全部穿透到数据库。
- 数据库无法承受如此高的并发量,造成服务雪崩效应(数据库崩溃 → 应用响应慢 → 上游请求堆积 → 整体故障)。
解决方案
| 方案 | 实现原理 | 说明 |
| :--------------------------- | :----------------------------------------------------------- | :--------------------------------------------- |
| 过期时间打散(加随机值) | 在同一批次加载缓存时,为每条数据的过期时间增加一个随机偏移量(如原时间 ± 5 分钟)。 | 避免大量 key 在同一时刻集体失效。 |
| Redis 高可用集群 | 使用 Redis 主从 + 哨兵,或者 Redis Cluster,避免因单点故障导致整个缓存层不可用。 | 解决 Redis 宕机引起的雪崩。 |
| 多级缓存 | 本地缓存(如 Caffeine) + Redis 分布式缓存。即使 Redis 失效,部分请求仍可被本地缓存命中。 | 需要处理本地缓存与 Redis 的数据一致性问题。 |
| 限流 & 降级 | 在应用层对访问数据库的请求进行限流(如令牌桶、漏桶),当请求超过阈值时直接返回友好提示或默认值。 | 保护后端数据库,牺牲部分可用性换取整体稳定性。 |
| 提前预热 + 后台刷新 | 系统启动时预加载热点数据,并设置定时任务在缓存即将过期前主动刷新。 | 避免“自然过期”导致的雪崩 |

浙公网安备 33010602011771号