旁路缓存怎么工作
旁路缓存由应用负责读穿与写后失效:读未命中就回源并回填带过期时间的值;写路径先落库再删除缓存键。缓存不参与数据库事务,一致性靠"删除加过期时间兜底"逐步收敛。
为什么是删除而不是更新
更新缓存要处理并发双写:两个写请求可能以相反顺序落到缓存与数据库,把旧值覆盖回去。删除是幂等操作,删错最多多一次回源,因此更安全。删除失败要进补偿队列重试,过期时间作为最终防线。
基本信息
| 读路径 | 未命中回源并回填,过期时间加随机抖动防雪崩 |
|---|---|
| 写路径 | 先写数据库,再删除缓存键 |
| 删除失败 | 进补偿队列重试,过期时间兜底 |
| 替代策略 | write-through 同步写缓存与库,write-behind 异步刷库 |
| 适用范围 | 缓存模式通用思路,Redis 等均适用 |
读写流程(伪代码)
读写流程(伪代码)
# 读:未命中回源并回填,过期时间带随机抖动避免同时失效
def get_user(uid):
v = cache.get(key(uid))
if v is not None:
return v
u = db.query(uid)
cache.set(key(uid), u, ttl=300 + rand(60))
return u
# 写:先落库,再删缓存;删除失败进入补偿队列
def update_user(u):
db.update(u)
if not cache.delete(key(u.id)):
retry_queue.push(key(u.id))典型使用场景
- 热点读接口:用缓存挡住大部分读请求
- 列表与详情:只缓存必要字段组合,控制缓存体积
- 防雪崩:过期时间加随机抖动,避免大批键同时失效
- 防击穿:热点键回源加互斥,避免并发同时打到数据库
- 防穿透:对不存在的键做参数校验或布隆过滤
写入策略对比
| 策略 | 行为 | 风险 |
|---|---|---|
| cache-aside | 写库后删缓存,读时回填 | 删除失败出现旧值窗口 |
| write-through | 写缓存同时写库(同步) | 写延迟变高 |
| write-behind | 先写缓存,异步刷库 | 进程崩溃可能丢写 |
| 只读缓存 | 读回填,写只落库 | 简单可靠,适合读多写少 |
优先用删除而不是更新缓存:并发双写下更新可能把旧值写回;删除天然幂等,配合过期时间最终收敛。
主从延迟期间从库读到的旧值可能被回填进缓存,读路径应固定读主库,或在延迟窗口内不复用从库结果,否则旧值会被长时间缓存。