Skip to content

13 · 缓存与 Redis ​

缓存是"用一致性换性能"。前端有 HTTP 缓存,后端有 Redis。本篇讲清缓存值不值得加、怎么加不出事、Redis 常用什么。

速览卡 ​

项内容
一句话定位Redis 是什么、缓存什么时候该加、三大缓存问题怎么防
核心内容缓存的价值与代价、Redis 常用结构、缓存穿透/击穿/雪崩、一致性策略、在 FastAPI 接入
读完能回答我该不该加缓存?缓存和数据库不一致怎么办?高并发下缓存为什么会崩?
相关板块C10 · 数据建模 | C04 · 一致性事务 | C07 · 性能与容量

一、先回答:该不该加缓存 ​

场景加缓存理由
热点数据高频读(配置、榜单、用户资料)✅命中率高,DB 压力骤降
复杂计算/聚合结果✅算一次存 N 次
只有低频读❌缓存本身有维护成本
写多读少❌缓存收益低,还容易不一致
数据一致性要求极高(账务)❌ 慎用缓存引入延迟不一致

判断顺序:先确认"读频率真的高"→ 再想"不一致的窗口可不可接受"→ 都成立才加。


二、Redis 常用结构(使用侧) ​

结构用途类比前端
String缓存值、计数器、限流计数一个 key-value
Hash对象字段读写一个对象(可只改某个字段)
List消息队列、最近列表数组(左进右出)
Set去重、集合运算Set
ZSet排行榜、延迟队列有序集合(score 排序)
python
import redis

r = redis.Redis.from_url("redis://localhost:6379/0")

r.set("user:42", '{"name": "张三"}', ex=3600)   # ex=过期秒数
value = r.get("user:42")

ex 过期是缓存必备:没有过期时间的缓存 = 永不失效的脏数据。


三、三大缓存问题 ​

3.1 穿透(查不存在的数据) ​

现象:请求的数据既不在缓存也不在库,每次都打 DB。

解法:

  • 布隆过滤器(前置拦截)
  • 缓存空值:不存在的 key 也缓存一个短 TTL 的空值
python
def get_user(uid):
    data = r.get(f"user:{uid}")
    if data is not None:
        return data
    row = db.get_user(uid)
    if row is None:
        r.set(f"user:{uid}", "", ex=60)      # 缓存空值,60s
        return None
    r.set(f"user:{uid}", row.to_json(), ex=3600)
    return row

3.2 击穿(热点 key 过期瞬间) ​

现象:一个热点 key 过期,大量请求同时打 DB。

解法:

  • 互斥锁:重建缓存时只让一个请求去查库
  • 逻辑过期 + 后台刷新(热点数据不真删)

3.3 雪崩(大量 key 同时过期) ​

现象:大量 key 同一时刻过期,DB 被洪峰打挂。

解法:

  • 过期时间加随机抖动(ex + random(0, 300))
  • 多级缓存(本地缓存兜底)

一句话记忆:穿透是"查了不存在的",击穿是"一个热点挂了",雪崩是"一群一起挂"。分别用空值缓存、互斥锁、随机过期防。


四、缓存一致性:删 or 更新 ​

策略做法问题
先更新 DB,再删缓存主流删除失败会短暂脏读(配合 TTL 兜底)
先更新 DB,再写缓存简单并发下可能写回旧值
先删缓存,再更新 DB❌ 别用中间读会重建旧缓存
python
def update_user(uid, data):
    db.update_user(uid, data)
    r.delete(f"user:{uid}")        # 更新后删缓存,让下次读重建

推荐默认:先更新 DB,再删缓存,依赖 TTL 兜底。追求强一致就别用缓存(见 C04)。


五、限流与分布式锁(同库附带) ​

Redis 常顺带承担两个任务:

python
# 限流:固定窗口
def allow(uid, limit=100, window=60):
    key = f"rate:{uid}"
    count = r.incr(key)
    if count == 1:
        r.expire(key, window)
    return count <= limit
python
# 分布式锁:注意设置过期,防止死锁
ok = r.set("lock:order:42", "1", nx=True, ex=10)

分布式锁只解决"跨实例的互斥",锁必须有过期时间,否则持有方挂了会死锁。


自检清单 ​

  • [ ] 能用"读频率 × 一致性要求"判断该不该加缓存
  • [ ] 能说清穿透/击穿/雪崩的区别与各自解法
  • [ ] 记得写缓存必带过期时间(ex)
  • [ ] 会"先更新 DB 再删缓存"的一致性写法
  • [ ] 会用 Redis 做简单限流和分布式锁,并给锁设过期

常见误区 ​

  • 误区:缓存越大越好。 命中率低的高频写缓存是负收益。
  • 误区:缓存永远一致。 缓存本质是"允许短暂不一致",要求强一致就别加。
  • 误区:分布式锁不设过期。 不设过期 = 进程一挂全系统锁死。
  • 误区:三个缓存问题分不清。 穿透查不存在、击穿热点单个、雪崩集体过期。

动手练习 ​

练习 A:热点缓存 ​

给用户详情接口加 Redis 缓存(含过期),并发压测对比 DB 直读与带缓存的 QPS。

练习 B:防空值 ​

给一个"查不存在的 id"的接口做空值缓存,验证穿透被拦截。

练习 C:限流 ​

给接口加固定窗口限流,超限返回 429。


相关文档 ​

本区 ​

其他域 ​