四大缓存模式和缓存一致性问题深度剖析

📅 2026/7/16 10:19:16
四大缓存模式和缓存一致性问题深度剖析
前言缓存与数据库双写一致性是Java后端面试高频重难点业界主流分为四大缓存设计模式Cache-Aside旁路缓存、Read Through读穿透、Write Through写穿透、Write Back异步回写。其中Cache-Aside旁路缓存是绝大多数互联网项目落地首选本文从线上业务踩坑场景切入逐个拆解四种缓存模型重点深挖旁路缓存的双写顺序、并发脏数据成因、生产级一致性兜底方案。更详细的文章见 认识 MySQL 和 Redis 的数据一致性问题一、缓存四大主流模式总览Cache-Aside Pattern旁路缓存客户端全权管控缓存与数据库读写逻辑国内项目最常用Read Through Cache Pattern读穿透缓存层封装读逻辑应用仅对接缓存缓存未命中自动查库回写Write Through Cache Pattern写穿透写入同步更新缓存数据库缓存组件接管双写逻辑Write Behind(Write Back异步回写)写入只更新缓存异步批量落地数据库极致写入性能。二、重点深挖Cache-Aside 旁路缓存模式业务落地首选2.1 Cache-Aside基础读写规范读策略查询逻辑固定客户端优先查询Redis缓存缓存命中直接返回数据缓存未命中时查询MySQL数据库将查询结果写入缓存后返回业务数据。写策略核心分歧更新数据库和缓存的执行顺序业界衍生四种执行方案其中前两种直接更新缓存的方案存在致命并发问题后两种删除缓存为生产主流选型方案1先更新数据库 → 再更新缓存方案2先更新缓存 → 再更新数据库方案3先删除缓存 → 再更新数据库方案4先更新数据库 → 再删除缓存生产环境通用最优解2.2 方案1、2直接更新缓存并发场景必现数据不一致场景1先更新DB再更新缓存假设当前数据库和缓存中的值均为V0写请求 A 要将值更新为V1写请求 B 要将值更新为V2。并发时序如下时间写请求 A更新为1写请求 B更新为2数据库值缓存值T1更新数据库为 110T2更新数据库为 220T3更新缓存为 222T4更新缓存为 1覆盖21脏数据最终结果数据库 2缓存 1数据出现永久不一致。根因两个写请求的“更新缓存”操作时序与“更新数据库”操作时序错乱A 的缓存更新晚于 B 的缓存更新导致 B 写入的最新值被 A 的旧值覆盖。场景2先更新缓存再更新DB同样假设当前数据库和缓存均为V0写请求 A 要更新为V1写请求 B 要更新为V2时间写请求 A更新为1写请求 B更新为2数据库值缓存值T1更新缓存为 101T2更新缓存为 202T3更新数据库为 222T4更新数据库为 1覆盖1脏数据2最终结果数据库 1缓存 2数据出现不一致。根因同样“更新数据库”操作时序与“更新缓存”操作时序错乱B 的数据库写入先于 A但 A 后执行的数据库写入覆盖了 B 的结果。结论两种顺序都无法规避并发写请求导致的“写覆盖”问题。并发更新场景下直接更新缓存的方案本质上是非原子操作时序无法保证普通业务禁止直接更新缓存。2.3 方案3先删缓存再更新DB读写并发漏洞搭配延迟双删优化漏洞场景请求A删除缓存后还未执行数据库更新读请求B查询数据缓存失效从DB读取旧值并回写到缓存A后续更新数据库为新值。最终数据库是新数据缓存留存旧数据。下面用一个具体数字模拟这个并发窗口假设当前 DB 和缓存中的值均为旧值V1写请求要将值更新为V2时间写请求 A更新为2读请求 B数据库值缓存值T1删除缓存1空T2查缓存为空去 DB 读旧值 11空T3更新数据库为 22新空T4将读到的旧值 1 回填缓存21脏数据最终数据库 2缓存 1数据出现不一致。这个脏数据会一直存在直到缓存过期或被人为清除。优化方案延迟双删第一次删除缓存 → 更新数据库 → 延迟几百ms~1s再次删除缓存把读请求回写的旧缓存清理掉规避脏数据。延续上例加入延迟双删后的时序如下假设读请求 B 的回填仍然发生在延迟窗口内时间写请求 A更新为2读请求 B数据库值缓存值T1第一次删除缓存1空T2查缓存为空读 DB 旧值 11空T3更新数据库为 22新空T4将旧值 1 回填缓存21短暂脏数据T5延迟 N 毫秒后第二次删除缓存2空脏数据被清除T6任意后续读请求查缓存为空读 DB 得到 2回填缓存22一致关键点T4 时刻缓存虽然短暂脏了但 T5 的第二次删除会将其立即清空延迟的目的等待可能存在的并发读请求完成“读旧值 → 回填缓存”这一动作再执行第二次删除延迟时间通常设为几百毫秒到 1 秒足以覆盖绝大多数并发窗口延迟双删的缺陷与兜底延迟双删也不是完美的仍有两个核心缺陷第二次删除也可能失败如 Redis 网络超时、进程异常一旦失败缓存仍会长期保存旧值延迟时间窗口内缓存一直是旧数据虽然窗口极短但在极高并发下仍可能读到短暂脏数据。因此生产环境的通用做法是延迟双删 缓存设置过期时间过期时间作为最终安全网。即使二次删除失败脏数据也会在过期后自动清除让缓存重新加载新值实现自愈。2.4 方案4先更新DB再删除缓存业务标准方案设计思路数据库更新完成后删除旧缓存下次查询触发缓存穿透自动从DB加载最新数据写入缓存。1、理论极小概率漏洞读请求A查缓存未命中→读取DB旧值写请求B更新DB删除缓存A再将旧值回写缓存出现缓存旧、DB新。下面用一个具体数字演示这个极低概率的并发窗口假设初始数据库1缓存为空时间读请求 A查询写请求 B更新为2数据库值缓存值T1查询缓存未命中1空T2读取数据库得到旧值 11空T3更新数据库为 22空T4删除缓存2空T5将旧值 1 回填缓存21脏数据2、实际落地几乎不会触发MySQL数据库写入耗时通常远大于缓存回填操作且这个并发窗口要求“缓存失效瞬间”同时发生读写概率极低。3、兜底策略全量缓存配置过期时间即便偶发脏数据到期自动失效自愈。2.5 衍生故障DB更新成功、删除缓存失败如何兜底故障描述数据库更新落地但Redis网络异常/宕机导致删缓存失败缓存永久留存旧数据数据库为新数据过期时间仅能临时缓解。生产两种成熟解决方案方案①消息队列MQ异步重试针对无并发请求的情况业务更新数据库成功后将待删除的Key投递至消息队列消费端消费消息执行缓存删除删除成功则ACK丢弃消息失败无限重试超限告警人工介入缺点需要改造原有业务代码对业务侵入性高。方案②Canal监听MySQL Binlog零业务代码侵入大厂首选针对有并发请求和无并发请求的情况Canal伪装成MySQL从节点实时订阅数据库binlog变更日志DB数据更新产生binlogCanal解析变更数据投递至MQ消费服务监听MQ消息异步删除对应Redis缓存删除成功后ACK消息。优点完全旁路监听数据库日志业务代码零改动缺点引入中间件提升运维成本。2.6 高命中率业务优化选用「更新DB更新缓存」方案部分热点业务不能频繁删除缓存避免大量缓存穿透放弃删缓存改用更新缓存通过两种手段解决并发覆盖问题分布式锁管控更新缓存前抢占分布式锁同一Key串行更新杜绝并发覆盖牺牲部分写入性能短过期兜底缓存设置较短过期时间即便并发写错数据短时间过期自动修复。三、剩余三种缓存模式简要解析3.1 Read Through读穿透模式应用只和缓存交互缓存组件内部封装查询逻辑缓存未命中自动查询数据库、回写缓存上层业务完全无感知DB。优缺点业务代码极简但缓存层封装复杂国内互联网项目落地极少。3.2 Write Through写穿透/同步双写写入数据时缓存组件同步更新缓存数据库双库全部写入成功才算提交完成数据强一致性。优缺点数据无不一致风险但写入需要两次IO性能差仅用于低并发金融类强一致场景。3.3 Write Back(Write Behind异步回写)写入仅落缓存直接返回缓存后台异步批量刷数据到数据库写入性能拉满。优缺点极致写入性能但缓存宕机会丢失未落地DB的数据一致性差多用于网站统计、榜单计数等非核心数据。四、业务场景选型指南业务场景推荐缓存方案电商用户、商品等通用业务Cache-Aside先更DB再删缓存过期兜底Canal/MQ保障删缓存可靠性首页热点配置高缓存命中率先更新DB更新缓存分布式锁短过期PV统计、榜单计数类数据Write Back异步回写优先性能容忍短时不一致金融账务低并发强一致场景Write Through同步双写保证数据绝对一致五、全文总结Cache-Aside旁路缓存是国内项目标配最优写策略先更新数据库后删除缓存缓存过期时间作为最终兜底删除缓存失败解决方案MQ异步重试改业务代码、CanalMQ监听binlog零代码侵入大厂方案先删缓存方案搭配延迟双删解决读写并发脏数据问题Read/Write/WriteBack三种模式根据业务对一致性、性能的需求灵活选型。补充过期兜底与Canal方案深度选型辨析不少同学会疑惑既然配置缓存过期时间就能杜绝永久脏数据为什么大厂还要额外部署Canal先明确结论过期时间属于被动兜底Canal是主动清理方案生产环境规范搭配Canal 缓存过期时间二者互补缺一不可。过期时间 最后一道保命保险Canal 优化用户体验的主动手段1、仅靠过期时间脏数据留存时间过长业务无法容忍举例设置缓存5分钟过期一旦主从同步延迟造成旧数据写入缓存在接下来5分钟内所有用户查询都会读取脏数据商品价格修改后5分钟内页面展示旧售价用户资料更新后短时页面仍加载旧信息订单状态变更短时间持续展示待付款。接入Canal后依靠binlog异步删缓存仅存在几十毫秒脏数据用户基本无感知数据变更后下次查询即可拿到最新值。2、过期时间无法解决删缓存失败问题业务同步删缓存场景下Redis宕机、网络抖动、代码异常都会导致删除动作失败脏缓存只能等待过期自动失效期间长期脏读。Canal结合MQ实现失败重试删除异常时消息不断重试Redis恢复后自动清理脏缓存不用被动等待过期。3、缩短过期时间会引发缓存命中率暴跌如果为了减少脏数据时长把过期时间压缩至10秒大量缓存频繁失效请求大面积穿透数据库拖垮DB性能。Canal架构可将缓存过期设置为30分钟~1小时保障命中率数据修改时主动删缓存兼顾命中率与数据一致性。小结缓存过期兜底方案杜绝永久脏数据Canal异步删除主动优化缩短脏数据窗口、规避删缓存失败、保障缓存命中率生产最优组合过期兜底 Canal主动删缓存。