05 · Redis 与缓存模式:Cache-Aside / Read-Through
Redis 不只是"快一点的 Map"。本篇讲清缓存放在数据访问链路的哪里、四种读写模式怎么选、以及最常见的"缓存与数据库不一致"怎么发生、怎么兜底。
速览卡
| 项 | 内容 |
|---|---|
| 一句话定位 | 缓存的落地模式与一致性,从"会用 Redis 命令"到"会设计缓存链路" |
| 核心内容 | Cache-Aside / Read-Through / Write-Through / Write-Behind、过期策略、一致性兜底 |
| 读完能回答 | 为什么读走缓存、写要删缓存?先删缓存还是先写库? |
| 相关板块 | DB06 缓存三大问题 | DB02 索引 | C13 缓存与 Redis |
常用命令与结构(String/Hash/List/Set/ZSet、过期策略)见 C13;本篇讲读写模式与一致性。
一、缓存放在哪:数据访问链路的两种形态
text
形态 A:应用自己管缓存(Cache-Aside 最常见)
App → Redis → 未命中 → MySQL → 写回 Redis
形态 B:框架帮你管缓存(Read-Through)
App → 缓存中间件 → 未命中 → 自动回源 DB → 自动回填| 模式 | 谁负责回填 | 适用 |
|---|---|---|
| Cache-Aside | 应用 | 默认选择,控制力强 |
| Read-Through | 缓存层/框架 | 希望调用方无感知时 |
二、Cache-Aside:标准四步
python
# 读
def get_user(user_id):
user = redis.get(f"user:{user_id}")
if user:
return user
user = db.query(User).get(user_id) # 回源
if user:
redis.set(f"user:{user_id}", user, ex=3600) # 回填
return user
# 写
def update_user(user_id, data):
db.query(User).filter(User.id == user_id).update(data) # 1. 先写库
redis.delete(f"user:{user_id}") # 2. 再删缓存为什么写要"删缓存"而不是"更新缓存":更新需要算最终值、还要防并发写覆盖;删掉让下次读重建,简单且不容易错。
三、先删缓存还是先写库:一个经典顺序问题
| 顺序 | 风险 | 结论 |
|---|---|---|
| 先写库后删缓存 | 删缓存失败 → 旧数据多活一会 | 更常用,配合延迟双删/对账 |
| 先删缓存后写库 | 写库前有读回填旧值 → 旧数据停留更久 | 更糟 |
兜底组合(延迟双删):
text
1. 先删缓存
2. 写库
3. 短延迟(几百 ms)后再删一次缓存目的:覆盖"删完缓存 → 并发读回填旧值 → 写库完成"的窗口。 双删不是银弹,极端一致要上版本号/订阅 binlog 对账(见 C13)。
四、三种更新型缓存模式:何时才值得
| 模式 | 机制 | 适用 | 代价 |
|---|---|---|---|
| Write-Through | 写库时同步写缓存 | 读远多于写、要缓存总是新 | 每次写多一次缓存写 |
| Write-Behind | 先写缓存,后台批量落库 | 极高写、能容忍短暂丢失 | 丢失风险、复杂度 |
| Cache-Aside(删) | 写库、删缓存 | 默认 | 有回源尖峰(见 DB06) |
判断框架:绝大多数系统 Cache-Aside 就够。Write-Behind 带来的丢失风险通常不值得,除非写量已经大到必须批处理。
五、过期策略与键设计
| 项 | 建议 |
|---|---|
| TTL | 必须设置,否则数据永久是旧的、内存也失控 |
| 键命名 | 业务:实体:主键(user:123),可加版本前缀做发布兜底 |
| 不设 TTL 的高频热键 | 用"逻辑过期"(值里带过期时间)配合主动刷新 |
| 大 key | 别把整个列表塞一个 key,考虑分片或只缓存摘要 |
TTL 语义:缓存不是"永远对",它是有界的"最近一致"。能接受多久的旧数据,就设多短的 TTL。
六、自检清单
- [ ] 默认用 Cache-Aside,知道读写各四步
- [ ] 写操作用"删缓存"而不是"更新缓存"
- [ ] 知道先删后写与先写后删的窗口差异,会用延迟双删兜底
- [ ] 所有缓存都设 TTL
- [ ] 键命名规范、避免大 key
- [ ] 知道 Read-Through / Write-Through / Write-Behind 何时才值得
七、常见误区
| 误区 | 事实 |
|---|---|
| "更新缓存比删缓存好" | 更新要算最终值且防并发覆盖,删更简单 |
| "先删缓存就安全" | 先删后写反而更容易留下旧值 |
| "缓存不用设 TTL" | 不设 TTL 会导致永久旧数据与内存失控 |
| "Redis 能当唯一数据源" | Redis 是易失的,业务数据要落库 |
| "缓存一致性能做到绝对" | 网络/并发下只能做到"尽快一致",要对账兜底 |
| "写多也走缓存" | 写多场景缓存的收益有限,代价不小 |
八、动手练习
练习 A:设计用户资料缓存
读多写少、允许 1 分钟旧数据。设计:键、TTL、读流程、写流程。写出关键代码。
练习 B:复现并发不一致
两个线程同时对同一用户执行"更新用户"和"读取用户",模拟先删缓存后写库的窗口,观察旧值回填。再改成延迟双删重试。
练习 C:判断模式
三个场景(商品详情、实时库存、埋点日志),分别选缓存模式并说明理由。
相关文档
本区
- 06 · 缓存三大问题 —— 缓存失效时的三把尖峰刀
- 04 · Schema 设计与范式反范式 —— 冗余的另一种形态是缓存
其他域
- 13 · 缓存与 Redis —— 常用命令与过期策略
- 07 · 服务端性能与容量 —— 缓存对容量的作用