缓存出问题时,数据库往往是第一个扛不住的。常见的三类问题容易被混在一起:
本文给出每一类对应的工程做法和示例代码。
请求的 id 在库里不存在,缓存里自然也没有,攻击或脏数据会让数据库持续承受无效查询。
NULL_MARK = "__NULL__"
def get_user(uid):
v = redis.get(f"user:{uid}")
if v is not None:
return None if v == NULL_MARK else json.loads(v)
row = db.query_user(uid)
if row is None:
redis.set(f"user:{uid}", NULL_MARK, ex=60) # 空值短 TTL
return None
redis.set(f"user:{uid}", json.dumps(row), ex=3600)
return row热点 key 过期时,只允许一个请求去重建缓存,其余请求等待或返回旧值。用 SET NX EX 实现简单的互斥锁:
def get_hot(key, loader, ttl=600):
v = redis.get(key)
if v is not None:
return json.loads(v)
lock = f"lock:{key}"
token = uuid.uuid4().hex
if redis.set(lock, token, nx=True, ex=10):
try:
data = loader()
redis.set(key, json.dumps(data), ex=ttl)
return data
finally:
# 只释放自己持有的锁
if redis.get(lock) == token.encode():
redis.delete(lock)
time.sleep(0.05)
return get_hot(key, loader, ttl) # 实际使用要限制重试次数注意事项:
另一种做法是逻辑过期:缓存里存数据和一个逻辑过期时间,过期后仍返回旧值,同时由单个后台任务异步刷新。适合对短暂不一致可以容忍的热点数据。
批量预热的 key 如果使用相同的 TTL,会在同一刻集中过期。给 TTL 加上随机偏移:
import random
ttl = 3600 + random.randint(0, 600)
redis.set(key, value, ex=ttl)此外:
问题 | 核心手段 |
|---|---|
穿透 | 参数校验、空值缓存、布隆过滤器 |
击穿 | 互斥重建、逻辑过期 |
雪崩 | TTL 随机抖动、限流降级、多级缓存、高可用 |
防护要分层做,单靠任何一种都不够,并且需要用压测和监控验证。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。