Redis 缓存双写一致性全解析:原理、问题与最佳实践
Redis 缓存双写一致性 - 深度解析与实战策略
一、什么是缓存双写一致性问题?
缓存双写一致性(Cache-DB Consistency)问题指的是:
当我们对数据库中的数据进行了更新,缓存中对应的数据也必须及时同步更新,否则会出现数据不一致的情况,用户可能读取到过期的缓存数据。
二、问题场景重现

经典场景:
假设有如下流程:
- 用户请求更新某条商品信息
- 系统更新数据库
- 然后再更新 Redis 缓存
// 更新数据库
db.update("product", id, newData);
// 再更新缓存
redis.set("product:" + id, newData);
这时候如果两个操作之间 Redis 被访问了?缓存就会出现脏数据!
三、常见的缓存双写策略
✅ 方案一:先删除缓存,再更新数据库
redis.del("product:" + id); // 删除缓存
db.update("product", id, newData); // 更新数据库
❌ 缺点:
- 如果在删除缓存后、数据库更新前,有请求进来,会发生缓存穿透(因为缓存没了),此时读到了旧数据。
✅ 方案二:先更新数据库,再删除缓存
db.update("product", id, newData); // 先更新 DB
redis.del("product:" + id); // 然后删缓存
这个方案是 目前主流的做法,能较大概率避免读到旧值,但仍有并发写+读的问题。
四、更新策略对比表
| 操作顺序 | 一致性保证 | 存在问题 |
|---|---|---|
| 更新缓存 → 更新数据库 | ❌ 缓存易被覆盖 | 异常时缓存比数据库新 |
| 删除缓存 → 更新数据库 | ❌ 缓存提前消失 | 有可能读到旧数据 |
| ✅ 更新数据库 → 删除缓存 | ✅ 较稳妥 | 并发高时仍可能不一致 |
五、核心问题:并发情况下如何避免不一致?
假设流程是:
更新数据库 → 删除缓存,但在这之间有读请求进入,就会出现脏读。
场景模拟:
- T1:线程 A 更新数据库(执行完)
- T2:线程 B 读取缓存(命中旧数据)
- T3:线程 A 删除缓存
👉 B 拿到旧缓存,并继续用旧数据更新缓存,数据回滚!
六、解决方案详解
✅ 方案一:延迟双删策略(推荐)
db.update(id, newData);
redis.del("key:" + id);
Thread.sleep(500); // 延迟
redis.del("key:" + id);
在第一次删缓存后,等待一段时间(如 500ms)再次删一次,防止读操作并发写入了旧数据。
适合大部分中小项目,简单有效。
✅ 方案二:消息队列同步(强一致方案)
在更新数据库后,将缓存变更请求投递到消息队列(如 Kafka、RabbitMQ)中,后台异步处理:
db.update(...);
mq.send("product_update", id); // 通知缓存层刷新
消费者统一处理缓存刷新逻辑,避免多个线程争抢更新。
适合大型高并发项目。
✅ 方案三:基于版本号的缓存策略
在缓存中引入版本号或时间戳,更新时判断数据是否是最新的,避免“旧数据覆盖新数据”。
{
"value": "数据",
"version": "20250420123456"
}
更新缓存前判断版本号是否比当前大,否则丢弃。
✅ 方案四:加分布式锁(不推荐常用)
在更新时对缓存加锁,确保读和写不会同时发生。
lock("product:lock:" + id);
db.update(...);
redis.del(...);
unlock();
⚠️性能瓶颈明显,不推荐用于高并发场景。
七、实际项目中的做法推荐
| 项目规模 | 推荐策略 | 一致性等级 | 成本/复杂度 |
|---|---|---|---|
| 中小型 | 延迟双删 | 中上 | 低 |
| 大型 | MQ + 缓存刷新服务 | 高 | 高 |
| 极高并发 | MQ + 版本号机制 | 极高 | 高 |
| 全局热点 | 本地缓存 + 缓存一致性监听 | 高 | 中等 |
八、面试答题模板参考
缓存双写一致性是指数据库和缓存中的数据需要在写操作后保持同步。 主流方案为“更新数据库后删除缓存”,避免缓存先更新导致的数据被覆盖。 常见优化策略包括延迟双删、消息队列刷新缓存、基于版本号的乐观锁控制等。 在实际项目中,可以结合业务场景选择合适策略,比如中小项目使用延迟双删,大型系统使用 MQ 异步刷新缓存。
九、总结一句话:
缓存和数据库一致性的本质是对“数据并发修改和读取”冲突的控制,而控制的关键是顺序、延迟和隔离。


- 点赞
- 收藏
- 关注作者
评论(0)