缓存击穿是什么
缓存击穿指某个热点键在过期瞬间,大量并发请求同时发现缓存为空,于是一起回源查库并重建缓存,数据库被同一个查询瞬间打满。它与雪崩(大面积同时失效)和穿透(查询本就不存在的数据)的区别在于对象:击穿针对的是单个高并发访问的热点键。常用对策是互斥重建与逻辑过期。
基本信息
| 触发条件 | 热点键过期、首次加载或缓存被主动删除 |
|---|---|
| 主要对策 | 互斥重建、逻辑过期、热点白名单、提前预热 |
| 互斥实现 | SET key NX PX 抢锁,拿到锁的请求负责回源 |
| 逻辑过期 | 值内嵌过期时间,过期后由一个请求异步刷新 |
| 关键风险 | 锁持有者异常未完成时,其他请求要有兜底路径 |
典型使用场景
- 首页热点商品:被高频访问且过期时间集中
- 秒杀商品详情:流量峰值集中在一个键上
- 大促配置数据:定时批量刷新导致集中失效
- 首屏接口:缓存预热不足时首个请求承担全部回源
- 热点识别:按访问频率统计出需要特殊处理的热点键
- 降级设计:确定缓存失效时返回旧值还是返回兜底内容
互斥重建示例
互斥重建示例
def get_hot(key):
v = redis.get(key)
if v is not None:
return v
# 抢锁重建:只放一个请求去查库
if redis.set(key + ':lock', '1', nx=True, px=3000):
try:
row = db.query_hot(key)
redis.set(key, row, ex=300)
return row
finally:
redis.delete(key + ':lock')
# 没抢到锁:短暂重试,超过上限就走兜底值
for _ in range(3):
time.sleep(0.05)
v = redis.get(key)
if v is not None:
return v
return fallback(key) # 兜底:旧值或默认内容,避免请求堆积对策对照
| 对策 | 原理 | 代价 |
|---|---|---|
| 互斥重建 | 只放一个请求回源,其余等待 | 等待期间延迟上升,锁需超时保护 |
| 逻辑过期 | 旧值先顶上,异步刷新 | 短时间读到旧数据 |
| 热点永不过期 | 运营后台主动刷新 | 需要热点维护机制,易漏刷 |
| 提前预热 | 失效前主动加载 | 需要预测访问模式 |
互斥重建要加自旋上限与超时:锁持有者超时未完成时,其他请求要有兜底路径,避免整体卡死。
抢锁失败的请求如果无上限自旋等待,一旦锁持有者异常,所有请求会堆在等待上,体验比直接回源更差;务必设置重试次数上限与降级返回。