1. 问题是怎么发生的:先看懂缓存不一致的根源
后端开发做到一定阶段,几乎都会撞上这个坑——我们一边依赖 Redis 的高速读写扛住高并发,一边又必须接受一个事实:多级存储之间永远存在一致性的风险。最近帮一位电商平台的后端负责人排查线上问题,现象很典型:用户改完收货地址,页面刷新还是旧地址,过了十几秒才恢复正常。查了一圈,MySQL 里数据其实已经更新了,但 Redis 里的缓存 key 一直没被清掉,用户读到的全是旧数据。
这个问题之所以反复出现,根源在于应用层把 Redis 当成了 MySQL 前面的“加速层”,但这两套存储系统各自独立,事务控制、数据复制、故障恢复机制都不一样,不存在原生的强一致约束。要解决它,不能靠运气,得从读写流程和并发时序上把每个窗口期都掰开看。
1.1 正常读写流程里藏着哪些更新窗口
先走一遍最常规的缓存读写路径。读请求来了,应用先查 Redis,如果命中就直接返回,这一步的性能是毫秒级的;如果没有命中,再去查 MySQL,查到数据后回填到 Redis,同时设置一个过期时间,最后把结果返回给前端。这套“旁路缓存”方案在多数业务里都被验证过是高效的。
写请求的路径就要小心了。业务上修改了数据库记录,那么 Redis 里的旧值如何处理?常见的做法有两种,一种是直接更新 Redis 里的值,另一种是删除 Redis 里对应的 key,等下一次读请求未命中时再回填新值。两种做法看起来都是“更新缓存”,但后果差别很大,后面我会专门展开。
问题的核心在于,读写请求是并发的,数据库更新和缓存更新之间会有一段天然的时间差。假设商品库存数据库值是 100,线程 A 把库存改成 80,线程 B 把库存改成 60,两个线程都在修改数据库和缓存。如果线程 A 先更新数据库,然后线程 B 再更新数据库,但线程 B 比线程 A 先写缓存,最后缓存里是 80(线程 A 的值),数据库里是 60(线程 B 的值)。用户查到的库存和真实库存就对不上了。
这里每个人都能看到,只要数据库更新和缓存操作不是原子的,就会有不一致的窗口。问题的关键不是要不要用缓存,而是怎么设计缓存更新策略,把不一致的概率压到业务可接受的范围内。
1.2 为什么“先更新数据库再更新缓存”容易翻车
很多刚接触缓存的同学会想当然:写请求来了,把数据库更新了,顺手把 Redis 也更新了,数据不就一致了吗?实际上一旦并发上来,这种做法最容易出乱子。
我举个例子。商品价格字段初始是 100 元。请求 A 要把价格改成 80 元,请求 B 要把价格改成 60 元。
- 线程 A 执行 update MySQL set price=80 where goods_id=1,数据库变成 80。
- 线程 B 执行 update MySQL set price=60 where goods_id=1,数据库变成 60。
- 线程 B 接着执行 Redis set goods_price_1 60,缓存变成 60。
- 线程 A 接着执行 Redis set goods_price_1 80,缓存变成 80。
最终数据库里是 60,缓存里是 80,用户看到的商品价格比真实价格贵了 20 元。这类错乱在并发稍有规模时非常容易复现,而且很难排查,因为代码逻辑本身是“先更新库再更新缓存”,方向上没错,但没考虑并发时序。
有人可能会说,那把顺序反一反,先更新缓存再更新数据库呢?问题更严重。缓存更新成功、数据库更新失败的话,缓存里就是永远无法落盘的数据,一旦 Redis 重启或者缓存过期,数据直接丢失,业务上完全不可接受。数据库作为唯一事实来源,必须在事务中保证最终数据可靠,缓存只能作为它的投影存在。
1.3 Cache Aside 模式为什么是多数团队的基础选择
业界最常用的缓存策略是 Cache Aside,也叫旁路缓存。它的核心逻辑是:读请求未命中缓存时由应用回填,写请求先更新数据库,再删除缓存,而不是更新缓存。
“删除缓存”而不是“更新缓存”,这个细节很多新手不理解。我来解释一下。更新缓存是直接把新值写进去,一旦后续有其他线程把旧值写回,新值就被覆盖了;删除缓存则是把旧值彻底清掉,即使某个读线程临时回填了旧值,下次读请求也会因为缓存中仍然存在旧值而拿到过期数据,最终只能等过期时间兜底或者下一次更新触发删除。
理论上 Cache Aside 也做不到绝对强一致。经典的竞态场景是:读线程查 MySQL 拿到旧值,还没回填 Redis,这时候写线程更新了数据库并删除了缓存,接下来读线程把旧值写回 Redis,缓存里就长期存着旧数据。解决这个窗口期,就要用到后面讲的延迟双删、版本控制等手段。但先别急着上复杂方案,把 Cache Aside 的“先更新库再删缓存”这条路走扎实,配合合理过期时间,已经能覆盖绝大多数业务了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流缓存更新策略的对比与选型
服务端缓存的策略不止 Cache Aside 一种,很多技术方案讨论里还会提到 Read Through、Write
