返回首页'%2F%3E%3Ctext%20x%3D'50%25'%20y%3D'50%25'%20dy%3D'.35em'%20text-anchor%3D'middle'%20font-family%3D'sans-serif'%20font-size%3D'34'%20fill%3D'%23ffffff'%3E%E5%B0%8F%3C%2Ftext%3E%3C%2Fsvg%3E)
缓存策略学习笔记:从 Cache-Aside 到多级缓存
为什么需要缓存
在系统设计中,性能瓶颈往往出现在 I/O 上。数据库查询是毫秒级的,而内存访问是纳秒级的——两者相差 10^5 倍。缓存就是在两者之间架起一座桥梁。
常见缓存策略
1. Cache-Aside(旁路缓存)
最常用的缓存模式。应用程序先查缓存,不命中再查数据库,然后写入缓存。
读取:
1. 查缓存 → 命中则返回
2. 未命中 → 查数据库 → 写入缓存 → 返回
写入:
1. 更新数据库
2. 删除缓存(而非更新缓存)
def get_user(user_id):
# 1. 查缓存
user = cache.get(f"user:{user_id}")
if user:
return user
# 2. 查数据库
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
# 3. 写入缓存
cache.set(f"user:{user_id}", user, ttl=3600)
return user
为什么删除而不是更新缓存?
- 避免并发写入导致的数据不一致
- 懒加载:只在需要时才计算缓存值
- 有些缓存值计算成本高,频繁更新浪费资源
2. Write-Through(穿透写入)
写入时同时更新缓存和数据库。
写入:
1. 写缓存
2. 写数据库
优点:缓存始终是最新的。 缺点:写入延迟增加。
3. Write-Behind(异步写入)
写入时只更新缓存,异步批量写入数据库。
写入:
1. 写缓存
2. 异步队列 → 批量写数据库
优点:写入性能极高。 缺点:缓存宕机可能丢数据,实现复杂。
缓存一致性
经典问题:先更新数据库还是先删缓存?
| 操作顺序 | 并发风险 | |---------|---------| | 先删缓存,后更新DB | 另一个线程读到旧数据并写入缓存 | | 先更新DB,后删缓存 | 删缓存失败导致缓存残留旧数据 |
推荐方案:延迟双删
def update_user(user_id, data):
# 1. 先删缓存
cache.delete(f"user:{user_id}")
# 2. 更新数据库
db.update("users", data, where={"id": user_id})
# 3. 延迟再删一次(防止并发读写入旧值)
schedule_delayed_task(
lambda: cache.delete(f"user:{user_id}"),
delay=0.5 # 500ms后执行
)
缓存常见问题
1. 缓存穿透
查询一个不存在的数据,缓存永远不命中,请求直达数据库。
解决方案:
- 缓存空值(设置短 TTL)
- 布隆过滤器(快速判断数据是否存在)
def get_user(user_id):
user = cache.get(f"user:{user_id}")
if user is not None:
return user if user != "NULL" else None
user = db.query("SELECT * FROM users WHERE id = ?", user_id)
if user:
cache.set(f"user:{user_id}", user, ttl=3600)
else:
# 缓存空值,短TTL
cache.set(f"user:{user_id}", "NULL", ttl=60)
return user
2. 缓存击穿
热点数据过期瞬间,大量请求直达数据库。
解决方案:
- 互斥锁(只让一个请求查数据库)
- 热点数据不过期
3. 缓存雪崩
大量缓存同时过期,或缓存服务宕机。
解决方案:
- TTL 加随机偏移
- 多级缓存
- 缓存高可用(Redis 集群)
多级缓存架构
请求 → 本地缓存 → 分布式缓存 → 数据库
(Caffeine) (Redis) (MySQL)
# 多级缓存实现
def get_data(key):
# L1: 本地缓存 (纳秒级)
val = local_cache.get(key)
if val is not None:
return val
# L2: 分布式缓存 (毫秒级)
val = redis.get(key)
if val is not None:
local_cache.set(key, val, ttl=60)
return val
# L3: 数据库 (十毫秒级)
val = db.query(key)
if val:
redis.set(key, val, ttl=3600)
local_cache.set(key, val, ttl=60)
return val
缓存淘汰策略
| 策略 | 说明 | 适用场景 | |------|------|---------| | LRU | 最近最少使用 | 通用场景 | | LFU | 最少使用频率 | 热点数据明显 | | FIFO | 先进先出 | 简单场景 | | TTL | 按过期时间 | 时效性数据 | | Random | 随机淘汰 | 无明显访问模式 |
学习总结
缓存设计是系统设计中性价比最高的一环。核心思路:
- 先用 Cache-Aside 模式 — 简单有效
- 注意一致性 — 延迟双删解决大部分问题
- 预防三大问题 — 穿透、击穿、雪崩
- 多级缓存兜底 — 本地缓存 + 分布式缓存
缓存不是银弹。每加一层缓存,就多一层复杂度。衡量收益和成本,才是好的架构决策。
小楼春雨
@站长