一、缓存击穿 (Cache Breakdown)
原理:
某个热点 Key 突然过期,同时大量并发请求该 Key,导致请求直接穿透缓存击穿到数据库。
解决方案:
- 互斥锁 (Mutex Lock)
当缓存失效时,仅允许一个线程重建缓存,其他线程等待。 - 逻辑过期
不设置物理过期时间,在 Value 中存储过期时间戳,异步更新缓存。
Python 实现 (互斥锁方案):
import redis
import time
import threading
r = redis.Redis()
def get_data(key):
# 1. 尝试从缓存读取
data = r.get(key)
if data:
return data
# 2. 尝试获取锁 (SETNX + EXPIRE)
lock_key = f"lock:{key}"
if r.set(lock_key, "locked", nx=True, ex=5): # 设置5秒锁过期
try:
# 3. 模拟数据库查询
data_from_db = "Database Result" # 真实场景需查DB
# 4. 写入缓存
r.setex(key, 60, data_from_db) # 缓存60秒
return data_from_db
finally:
r.delete(lock_key)
else:
# 5. 未获锁则短暂等待重试
time.sleep(0.1)
return get_data(key) # 递归重试
# 测试并发
def test_breakdown():
key = "hot_key"
threads = []
for i in range(10): # 模拟10个并发请求
t = threading.Thread(target=lambda: print(get_data(key)))
threads.append(t)
t.start()
for t in threads:
t.join()
test_breakdown()
二、缓存穿透 (Cache Penetration)
原理:
请求访问数据库中不存在的数据(如非法ID),导致每次请求都穿透到数据库。
解决方案:
- 布隆过滤器 (Bloom Filter)
快速判断 Key 是否可能存在数据库中。 - 缓存空对象
对不存在的数据也进行缓存(设置短过期时间)。
Python 实现 (缓存空对象):
def get_data_penetration(key):
# 1. 尝试从缓存读取
data = r.get(key)
if data is not None:
return None if data == b'NULL' else data # 处理空值
# 2. 查询数据库 (模拟不存在)
data_from_db = None # 假设数据库返回空
if data_from_db is None:
# 3. 缓存空对象 (5分钟过期)
r.setex(key, 300, 'NULL') # 特殊值标记
return None
else:
r.setex(key, 60, data_from_db)
return data_from_db
三、缓存雪崩 (Cache Avalanche)
原理:
大量 Key 同时过期,导致所有请求穿透到数据库,引发连锁故障。
解决方案:
- 随机过期时间
基础过期时间 + 随机值,分散 Key 过期时间。 - 热点数据永不过期
后台异步更新缓存。 - 多级缓存架构
本地缓存 + Redis 缓存。
Python 实现 (随机过期时间):
import random
def set_data(key, value):
# 基础过期时间 + 随机偏移 (0-300秒)
expire = 3600 + random.randint(0, 300)
r.setex(key, expire, value)
# 批量设置缓存时使用
set_data("key1", "value1")
set_data("key2", "value2")
四、缓存预热 (Cache Warm-up)
原理:
系统上线前预先加载热点数据到缓存,避免冷启动时大量请求穿透到数据库。
实现方式:
- 手动触发脚本加载数据
- 定时任务预热
- 启动时自动加载
Python 实现:
def cache_warm_up():
hot_keys = ["news:1", "product:100", "top:list"] # 预定义热点Key
for key in hot_keys:
if not r.exists(key):
# 从数据库加载数据
data = f"Data for {key}" # 真实场景需查DB
r.setex(key, 3600, data) # 缓存1小时
print(f"Preloaded: {key}")
# 系统启动时执行
cache_warm_up()
总结对比表
问题 | 核心原因 | 解决方案 | 关键实现技术 |
---|---|---|---|
缓存击穿 | 热点 Key 突然失效 | 互斥锁、逻辑过期 | SETNX + EXPIRE |
缓存穿透 | 查询不存在的数据 | 布隆过滤器、缓存空对象 | 特殊值标记 (如’NULL’) |
缓存雪崩 | 大量 Key 同时过期 | 随机过期时间、多级缓存 | 基础过期时间 + 随机值 |
缓存预热 | 冷启动时无缓存 | 启动时加载热点数据 | 初始化脚本加载 |
最佳实践:生产环境中建议组合使用多种方案(如布隆过滤器+空对象+互斥锁+随机过期时间),并配合监控系统实时检测缓存命中率。