场景:为什么会出现不一致?
前置知识: 无 难度: ⭐⭐ 预计时间: 30分钟 关键词: 缓存不一致, 并发, 竞态条件, 数据库, Redis
引入场景
你负责一个用户服务。数据存在MySQL中,用Redis做缓存加速读取。某天你收到反馈:“用户改了头像,但页面上看到的还是旧的。“你查了MySQL——数据已更新。查了Redis——缓存还是旧值。
为什么数据库和缓存会不一致?
这不是Bug,而是分布式系统的根本挑战:任何时候你把数据存了两份,它们就可能不一致。
对话探索
问题1:最简单的方案——更新数据库后立即更新缓存。这有什么问题?
💭 思考方向:
- 如果两个请求同时更新同一条数据?
- 更新数据库成功但更新缓存失败呢?
- 多个服务器时,谁先谁后?
📖 参考答案:
并发更新导致数据错乱:
时间线:
请求A(将价格改为100) 请求B(将价格改为200)
────────────────── ──────────────────
1. 更新DB: price=100
2. 更新DB: price=200
3. 更新缓存: price=200
4. 更新缓存: price=100
最终结果:DB=200, 缓存=100 ← 不一致!
请求A的缓存更新晚于请求B(虽然A先更新了数据库),导致缓存存了过时的值。
部分失败更糟糕:
更新DB成功 → 更新缓存失败(网络抖动)
结果:DB是新值,缓存是旧值
而且你可能根本不知道缓存更新失败了
问题2:那改成”更新数据库后删除缓存”呢?下次读取时重新从DB加载。
💭 思考方向:
- 删除缓存比更新缓存好在哪里?
- 还有并发问题吗?
- 先删缓存还是先更新DB?
📖 参考答案:
Cache-Aside模式(最常用):
写操作:更新DB → 删除缓存
读操作:查缓存 → 缓存没有 → 查DB → 写入缓存
这比”更新缓存”好,因为删除是幂等的(删多次也没关系),而更新不是。
但仍有并发问题:
请求A(读) 请求B(写)
───────────── ──────────────
1. 查缓存 → 缓存未命中
2. 查DB → 得到旧值
3. 更新DB为新值
4. 删除缓存
5. 将旧值写入缓存
结果:DB=新值, 缓存=旧值 ← 不一致!
请求A在步骤2查到旧值后,被请求B抢先更新了DB并删了缓存,然后A才把旧值写入缓存——产生了过时数据。
不过这个窗口期很短(需要”读DB”恰好在”写DB+删缓存”之间),实践中出现的概率较低。
问题3:那”先删缓存,再更新数据库”呢?会更好吗?
💭 思考方向:
- 删完缓存后、更新DB前,如果有读请求进来?
- 读请求会从DB读到旧值并写入缓存
- 这个时间窗口有多大?
📖 参考答案:
请求A(写) 请求B(读)
────────────── ─────────────
1. 删除缓存
2. 查缓存 → 未命中
3. 查DB → 旧值(DB还没更新!)
4. 将旧值写入缓存
5. 更新DB为新值
结果:DB=新值, 缓存=旧值 ← 不一致!而且持续到缓存过期
这比”先更新DB再删缓存”更糟——不一致的时间窗口更大(整个DB写操作期间),且这种序列更容易出现。
小结:
| 方案 | 出现不一致的概率 | 不一致持续时间 |
|---|---|---|
| 先更新缓存 | 高(并发乱序) | 直到下次更新 |
| 先更新DB,再删缓存 | 低(窗口极短) | 直到缓存过期 |
| 先删缓存,再更新DB | 中(窗口较大) | 直到缓存过期 |
问题4:有没有办法完全消除不一致?
💭 思考方向:
- 分布式事务?
- 性能代价?
- “完全一致”在分布式系统中可能吗?
📖 参考答案:
理论上:可以用分布式事务(2PC)保证DB和缓存强一致。 实际上:几乎没人这么做。原因:
- 性能代价太高:每次写操作都需要分布式锁/2PC → 延迟增加数倍
- 可用性降低:Redis或DB任一不可用,写操作就阻塞
- 本末倒置:引入缓存就是为了提升性能,如果为了一致性牺牲性能,不如直接查DB
现实做法:接受最终一致性,将不一致窗口缩到业务可接受的范围。
实际策略组合:
1. Cache-Aside + TTL(缓存过期时间)
→ 即使不一致,最多持续TTL时间
2. 延迟双删
→ 进一步缩小不一致窗口
3. 订阅数据库变更(binlog)
→ 数据库变了就通知清缓存
问题5:除了DB-缓存,还有哪些场景会出现不一致?
💭 思考方向:
- 微服务间的数据同步
- 搜索引擎索引和数据库
- CDN缓存和源站
📖 参考答案:
万物皆可不一致——只要数据存了两份以上:
| 场景 | 数据源A | 数据源B | 一致性手段 |
|---|---|---|---|
| DB+缓存 | MySQL | Redis | Cache-Aside + TTL |
| DB+搜索 | MySQL | Elasticsearch | Binlog订阅 / 定时同步 |
| DB+CDN | 源站 | CDN节点 | 主动清除 / TTL |
| 主从复制 | 主库 | 从库 | 复制滞后 → 读主库 |
| 微服务 | 订单服务 | 库存服务 | 事件驱动 / Saga |
核心认识:不一致不是Bug,而是分布式系统的固有属性。工程师的工作是选择合适的策略,将不一致控制在业务可接受的范围内。
这就引出了理论层的深入讨论——一致性模型。
小结
⭐ 核心要点:
- 数据一致性问题的根源:多份数据 + 并发修改 + 网络不可靠
- 没有完美方案:每种缓存策略都有不一致窗口,区别只是大小
- 工程思维:接受最终一致性,用多种手段(TTL、双删、binlog)将窗口缩到可接受范围
关联阅读
- 深入:Cache-Aside与延迟双删(具体的解决方案)
- 深入:写策略(Write-Through/Write-Back)
- 理论:一致性模型(为什么不一致是”必然”的)