缓存三大问题与解决方案
理解缓存穿透、击穿、雪崩的定义
掌握每种问题的解决方案
能在业务中设计合理的缓存方案
核心讲解
(1)缓存穿透
问题:查询一个数据库和缓存都没有的数据,请求直接打到数据库,大量这样的请求会压垮数据库。
原因:数据不存在,缓存不命中,每次都查库
解决方案:
布隆过滤器:把所有存在的 key 存布隆过滤器,不存在的直接拦截,过滤掉绝大多数
缓存空值:查询不存在的数据,也缓存一个空值(短过期时间),下次直接返回空
参数校验:接口层做参数校验,非法参数直接拦截
(2)缓存击穿
问题:某个热点 key 过期的瞬间,大量并发请求过来,都打到数据库,压垮数据库。
原因:热点 key 过期,同时大量请求
解决方案:
互斥锁:缓存失效时,只有一个请求去查库并更新缓存,其他等待,避免并发查库
热点数据永不过期:特别热的 key 不设置过期时间,后台异步更新
软过期:逻辑过期,值里存过期时间,后台异步更新,前台还是返回旧值
(3)缓存雪崩
问题:大量 key 同一时间集体过期,或者 Redis 宕机,大量请求全部打到数据库,压垮数据库。
原因 1:大量 key 设置了相同的过期时间,同时失效
原因 2:Redis 整体故障,所有请求都走库
解决方案:
过期时间加随机值:每个 key 的过期时间加一个随机偏移,避免同时过期
多级缓存:本地缓存 + Redis 缓存,一层没了还有下一层
服务降级:缓存挂了,部分接口降级,返回默认值,保护核心业务
高可用集群:哨兵、集群,避免单点故障
限流:超过阈值直接限流,保护数据库不被打垮
代码示例
# Python 简单互斥锁解决击穿
import redis
import time
r = redis.Redis(decode_responses=True)
def get_product(pid):
# 先查缓存
cache = r.get(f"product:{pid}")
if cache:
return cache
# 缓存没有,加锁查库
lock_key = f"lock:product:{pid}"
# 尝试加锁,SETNX
if r.set(lock_key, 1, ex=10, nx=True):
try:
# 模拟查数据库
print("查数据库")
data = f"商品{pid}的数据"
# 回写缓存
r.setex(f"product:{pid}", 300, data)
return data
finally:
# 释放锁
r.delete(lock_key)
else:
# 没拿到锁,等一下重试
time.sleep(0.1)
return get_product(pid)
常见误区
三个问题是一回事:穿透是查不存在的,击穿是热点 key 过期,雪崩是大量 key 同时挂,不一样
布隆过滤器 100% 准确:布隆过滤器有误判率,说存在的可能不存在,说不存在的一定不存在
加了缓存就一定快:缓存设计不好,击穿、雪崩的时候反而更慢
缓存永远比数据库好:命中率低的缓存,还不如直接查库,要评估命中率
课后练习
分别说明缓存穿透、击穿、雪崩的定义
分别说明三种问题的至少两种解决方案
用伪代码实现互斥锁解决缓存击穿
思考:实际项目中如何设计缓存策略?

