如何保证缓存与数据库一致性?
📌 如何保证缓存和DB的一致性?
在高并发系统设计中,缓存(Cache)和数据库(DB)几乎是所有互联网系统绕不开的话题。 缓存负责提升访问性能,数据库负责数据持久化存储。 然而随着系统规模扩大,一个核心问题逐渐浮现:缓存与数据库如何保持一致?
1️⃣ 问题背景
在单体系统时代,业务通常直接访问数据库,虽然性能一般,但数据一致性天然得到保障。
随着用户量增长,大量请求直接打到数据库会导致:
- 数据库CPU飙升
- 磁盘IO成为瓶颈
- 连接池耗尽
- 响应时间持续增加
因此大多数系统都会引入Redis作为缓存层。
用户请求
↓
Redis缓存
↓(未命中)
MySQL数据库
当数据发生修改时,如果缓存与数据库更新顺序不当,就会产生脏数据问题。
2️⃣ 核心原理
缓存一致性的本质问题其实非常简单:
- 数据库中的数据
- 缓存中的数据
只要数据被修改,就必须保证两个副本最终保持一致。
根据CAP理论和分布式系统特点,我们通常追求的是:
而非绝对强一致性。
因为缓存本身就是性能优化组件,如果为了绝对一致性而引入复杂分布式事务,反而得不偿失。
3️⃣ 数据结构分析
以商品信息为例:
id = 1001
name = iPhone
price = 6999
Redis缓存结构:
Value:{
"id":1001,
"name":"iPhone",
"price":6999
}
此时数据库和缓存保存的是同一份业务数据。
如果数据库修改为7999元,而缓存仍然保存6999元,那么用户读取到的数据就出现不一致。
4️⃣ 算法分析
方案一:先更新缓存,再更新数据库
执行流程:
↓
更新MySQL
如果Redis更新成功,而数据库更新失败,则缓存与数据库永久不一致。
因此这种方案几乎不会在生产环境使用。
方案二:先更新数据库,再更新缓存
执行流程:
↓
更新Redis
看起来合理,但依然存在问题。
如果数据库更新成功,而Redis更新失败,则缓存仍保存旧数据。
方案三:更新数据库后删除缓存
这是互联网公司最常见方案。
执行流程:
↓
删除Redis缓存
下一次查询时:
↓(不存在)
查询MySQL
↓
写入Redis
这就是经典的延迟加载机制。
5️⃣ 执行流程
下面分析一个高并发场景。
线程B:查询商品价格
执行顺序如下:
↓
线程A删除缓存
↓
线程B读取缓存失败
↓
线程B查询数据库
↓
线程B写入新缓存
最终缓存与数据库重新保持一致。
因此大部分互联网系统采用:
而不是更新缓存。
6️⃣ 实际案例
双写不一致问题
假设存在如下场景:
↓
线程B读取缓存失败
↓
线程B读取旧DB数据
↓
线程A删除缓存
↓
线程B写入旧缓存
最终旧数据再次进入缓存。
这就是经典缓存双写不一致问题。
延迟双删策略
解决方案:
update(mysql)
sleep(500ms)
delete(redis)
第一次删除避免旧缓存存在。
第二次删除用于清理并发期间产生的脏缓存。
虽然不能保证100%一致,但能显著降低脏数据概率。
Binlog异步同步方案
大型互联网公司更倾向于采用Binlog驱动缓存更新。
执行流程:
↓
MySQL产生Binlog
↓
Canal监听Binlog
↓
MQ消息队列
↓
删除Redis缓存
典型代表:阿里Canal架构。
数据库变成唯一数据源(Source Of Truth)。
所有缓存变更全部由Binlog驱动完成。
7️⃣ 优缺点分析
| 方案 | 优点 | 缺点 |
|---|---|---|
| 先更新缓存 | 速度快 | 极易不一致 |
| 更新DB更新缓存 | 逻辑简单 | 双写风险 |
| 更新DB删除缓存 | 实现简单 | 存在极端并发问题 |
| 延迟双删 | 一致性更高 | 实现复杂 |
| Binlog+MQ | 企业级方案 | 架构复杂 |
8️⃣ 面试常见问题
Q1:为什么删除缓存而不是更新缓存?
更新缓存需要知道缓存结构,同时存在更新失败问题。 删除缓存成本更低,后续查询自动重建缓存。
Q2:先删缓存还是先更新数据库?
标准答案是先更新数据库,再删除缓存。
Q3:延迟双删为什么有效?
能够清理并发读写期间重新写入的旧缓存。
Q4:缓存一致性能做到100%吗?
理论上可以,但成本极高。 实际系统通常追求最终一致性。
Q5:大型互联网公司如何做?
通常采用Canal + Binlog + MQ + Redis实现最终一致性。
9️⃣ 总结
🎯 缓存一致性的本质是解决数据库副本与缓存副本的数据同步问题。
🎯 最常见方案是 Cache Aside Pattern,即更新数据库后删除缓存。
🎯 在高并发场景下,可通过延迟双删降低脏数据概率。
🎯 企业级架构通常采用 Binlog + Canal + MQ 驱动缓存失效。
🎯 分布式系统中几乎不存在绝对强一致缓存,真正可落地的是最终一致性。
🚀 面试中如果被问到“如何保证缓存和数据库一致性”,最佳回答路径通常为: Cache Aside → 双写不一致 → 延迟双删 → Binlog异步同步 → 最终一致性。
上一篇:SQL的执行流程
相关文章
-
SQL语句
表B:id,name,code,detail四个字段均有值;表A id,name,code,detail其中id,name有值并且与表B相对应,code,detail值为null。现在将B表中的code和detail值更新到A表中。
NEW个对象 2024-12-25
-
SQL基础知识
SQL基础知识
NEW个对象 2025-01-13
-
聚簇索引与非聚簇索引的区别
聚簇索引:将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据。 非聚簇索引:将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置。
NEW个对象 2024-12-25