Redis 缓存怎么更新?—— 四种模型与一次“迟到的删除”
一、为什么“更新缓存”值得单独聊
缓存位于内存,数据库位于磁盘,两者天生有时差;再加上并发请求可能交错读写,如果不设计好更新路径,就会出现“缓存里一直是老数据”的现象。下面按“一致性强度”从高到低,梳理四种常见模型,并补充一个工程上常用的“迟到删除”技巧。

二、Write-Through(读写穿)—— 缓存代理一切
流程
读 miss:缓存自己去数据库加载,再返回
写:缓存同步写数据库,两边都成功才返回客户端
特点
业务代码最简洁;缓存与数据库强一致
写延迟 = 数据库延迟;缓存层要实现 Loader/Writer 接口
适合对一致性要求极高、写量可控的系统,如计费、银行核心账务
三、Write-Behind(写回)—— 先写内存,再异步刷盘
流程
写请求只到达缓存立即返回
缓存按“时间窗口”或“队列长度”批量回写数据库
特点
写操作极快,可轻松抗住十万级 TPS
数据库压力被批量平滑
宕机窗口内可能丢失数据;读也可能短暂读到脏数据
适用场景:计数器、日志、秒杀库存(允许少量超卖后补偿)
四、Cache-Aside(旁路缓存)—— 业务自己掌握读写
流程
读:缓存 miss → 查数据库 → 把结果写回缓存
写:先更新数据库 → 然后删除缓存(注意是删除,而不是修改)
为什么写后选择“删除”
缓存可能是聚合对象(JSON/Hash),局部字段更新成本高
删除是幂等操作,无需考虑并发写顺序
下一次读请求会重新加载最新数据,天然保持最终一致
不足
数据库提交与缓存删除之间仍有微小时间窗,极端并发下可能把旧值重新载入缓存。要缩小这个窗口,就出现了“延迟双删”。
五、延迟双删——把窗口再压短一点
思路
立即删除:挡住大多数并发读
延迟一段时间(通常 0.5–2 秒)后再删除一次:把“刚被回填的旧值”清掉
实现示例
@Transactional
public void updateUser(User u) {userDao.update(u); // 1. 写库redis.del("user:" + u.getId()); // 2. 立即删// 3. 延迟删(线程池、MQ、Redisson 延迟队列均可)scheduler.schedule(() -> redis.del("user:" + u.getId()), 1, TimeUnit.SECONDS);
}关键参数
延迟时长 ≈ 主从复制延迟 + 业务 RT 的 99 分位
延迟任务需保证“至少一次”投递,失败要有重试或监控
延迟双删并未突破 CAP,只是把“最终一致”的“最终”两字压到亚秒级,成本低,落地简单。
六、MQ 双写——用消息流解耦缓存
流程
业务只负责写数据库并提交事务
事务提交后发送 binlog 或自定义消息
下游消费者异步重建缓存
特点
写链路零侵入,缓存与业务彻底解耦
可削峰、可重试、可批量聚合
秒级延迟;需要保证消息不丢、不乱序
适合订单、物流等读远大于写,且能接受秒级滞后的系统
七、如何选型
强一致 + 写量可控 → Write-Through
写极高并发 + 可丢少量数据 → Write-Behind
读多写少 + 亚秒级可接受 → Cache-Aside,配合延迟双删
读多写少 + 可接受秒级延迟 → MQ 双写
先按“一致性”要求划定范围,再根据“写入吞吐量”微调,就能落到唯一象限。
八、结语
缓存更新没有万能答案,只有在“一致性—性能—复杂度”三角形里最适合自己的点。
理解每种模型的前提与代价,才能在真实场景里做出可靠的选择。祝你落地顺利,少踩坑。
