缓存和数据库一致性
一句话缓存和数据库很难做到强一致主流做法是 Cache Aside读缓存未命中查库回写写数据库成功后删除缓存。它追求的是最终一致并通过重试、延迟双删、消息队列或 binlog 监听缩短不一致窗口。1. 为什么会不一致因为数据库和 Redis 是两个独立系统无法天然保证原子性写数据库成功删缓存失败 → 缓存里还是旧值 删缓存成功写数据库失败 → 缓存没了DB 还是旧值 并发读写交错 → 旧值可能被重新写回缓存所以面试里不要说“保证强一致”。大多数缓存场景只能保证最终一致。2. Cache Aside 模式读流程读请求 ↓ 查 Redis ↓ 命中直接返回 ↓ 未命中查 DB ↓ DB 有数据写入 Redis再返回 DB 没数据缓存空值或直接返回空写流程写请求 ↓ 更新数据库 ↓ 数据库更新成功 ↓ 删除 Redis 缓存 ↓ 下次读请求未命中缓存 ↓ 从数据库读取最新值并回写缓存核心口诀读先缓存再数据库最后回写缓存 写先数据库再删除缓存3. 为什么是删除缓存不是更新缓存① 缓存值可能是复杂聚合结果 比如商品详情 商品表 库存表 活动表 价格策略 更新数据库时同步更新缓存成本高容易漏字段 ② 高并发下直接更新缓存可能旧值覆盖新值 两个线程并发更新同步缓存的顺序和写 DB 的顺序不一定一致 ③ 写后不一定马上读 如果每次写都更新缓存但这个 key 后面没人读就是浪费资源所以删除缓存更符合懒加载思想谁读谁重建。4. 为什么先更新数据库再删除缓存推荐流程update DB success ↓ delete Redis cache ↓ next read: cache miss → read DB → rebuild cache如果反过来先删缓存再更新数据库会出现典型并发问题线程 A删除缓存 线程 B读请求发现缓存不存在 线程 B查询数据库读到旧值 线程 B把旧值写回缓存 线程 A更新数据库为新值 结果数据库是新值缓存是旧值这个旧缓存会一直存在到 TTL 过期期间用户都可能读到旧数据。5. 先更新 DB 再删缓存还有问题吗有但概率比“先删缓存再更新 DB”小。极端时序线程 A缓存未命中查 DB 读到旧值 线程 B更新 DB 为新值 线程 B删除缓存 线程 A把刚才查到的旧值写回缓存 结果旧值被回填到缓存这个场景要求线程 A 的读 DB 和回写缓存过程非常慢线程 B 在中间完成更新和删除。它不是不可能但出现概率相对低。6. 延迟双删流程更新数据库 ↓ 删除缓存 ↓ 延迟一小段时间 ↓ 再次删除缓存第二次删除的作用清理并发读线程可能回填进去的旧缓存。延迟多久要大于一次“读 DB 回写缓存”的耗时。 例如接口正常读 DB 写 Redis 是 100ms可以延迟 300-500ms。 具体要结合业务耗时和监控数据估算。注意延迟双删不是强一致只是降低旧值残留概率。7. 删除缓存失败怎么办如果更新 DB 成功但删除 Redis 失败必须有补偿机制方案一业务内重试 删除失败后重试几次 适合简单场景 方案二MQ 异步重试 DB 更新成功后发送缓存删除消息 消费失败就重试或进入死信队列 方案三监听 binlog 通过 Canal 等组件监听 MySQL binlog 捕获数据变更后删除或更新缓存 业务代码侵入较低面试回答里普通业务讲“更新 DB 后删缓存 失败重试”就够高并发核心链路再补充 MQ 或 binlog。8. 强一致场景怎么办缓存方案天然偏最终一致。如果业务必须强一致例如余额、库存扣减、支付状态不应把 Redis 缓存作为最终判断依据。强一致场景 以数据库事务为准 必要时加分布式锁 / 行锁 / 版本号 CAS 缓存只做查询加速 写路径不能依赖缓存判断最终状态一句话缓存可以加速读但不能替代数据库做强一致事实源。9. 项目表达Redis Caffeine 二级缓存在考试系统这种读多写少场景里我会用 Caffeine 做本地热点缓存 Redis 做分布式共享缓存数据库作为最终事实源。 读流程是先查 Caffeine再查 Redis最后查 DB。DB 查到后先写 Redis 再写本地缓存。写流程是先更新 DB再删除 Redis并通过消息通知或版本号 让各节点本地缓存失效。本地缓存也会设置较短 TTL即使通知失败 也能依靠过期时间实现最终一致。面试回答模板“缓存和数据库一般不能承诺强一致主流是 Cache Aside 模式保证最终一致。 读的时候先查 Redis命中直接返回未命中再查数据库查到后回写缓存。 写的时候先更新数据库数据库成功后删除缓存而不是直接更新缓存。 删除缓存的原因是缓存可能是复杂聚合结果更新成本高并且高并发下直接 更新缓存可能出现旧值覆盖新值。先更新 DB 再删除缓存是为了避免先删缓存 后更新 DB 时并发读把旧值重新写回缓存。 如果担心极端并发下旧值回填可以做延迟双删更新 DB 后删一次缓存 延迟一小段时间再删一次。删除缓存失败则要有重试、MQ 补偿或 binlog 监听。 对于余额、库存这类强一致场景数据库才是事实源缓存只能做查询加速。”