最近在排查线上一个偶发问题:用户下单后详情页价格偶尔会回退到上一秒的状态,刷新几次又正常。翻代码、查日志、看Redis和MySQL里的数据,最终定位到的是缓存与数据库一致性没做到位。这个问题的本质不复杂,但想在生产环境里稳定解决,坑比预想的多。
这篇文章就围绕“缓存与数据库一致性”这个主题,把我自己从Cache Aside到延迟双删,再到binlog异步同步的完整演进过程,以及线上踩过的坑、调过的参数、最终沉淀的解决方案,一起写出来。适合正在做电商、资讯、交易链路,或者自己维护缓存系统的后端开发者参考。内容以实际项目可落地为标准,不写空中楼阁的理论。
1. 先泼盆冷水:你读过的很多一致性方案,在生产里根本跑不起来
我见过太多文章把一致性问题简化成一套固定流程:先更新数据库,再删除缓存,然后延迟双删,就完事了。但当你真正在系统里落地时,会发现一堆隐藏问题:并发请求把旧数据写回缓存怎么办、延迟双删的时间到底设多少、删除失败怎么重试、多实例部署时的误删问题怎么办。这些细节不处理好,方案形同虚设。
1.1 大多数系统要的不是强一致,而是窗口可控的最终一致
明确一件事:对于绝大部分业务系统,你根本不需要强一致,你需要的是“最终一致”加上“可控的脏数据窗口”。这个窗口通常控制在几百毫秒到几秒之间,用户完全感知不到。
为什么?因为缓存和数据之间要达成强一致,最直接的做法是引入分布式事务或全局锁。但分布式事务的代价很高,吞吐量骤降,对于高并发的读多写少场景得不偿失。而且,很多业务本身允许短暂的不一致。
举个例子:用户下单后,订单状态从“待支付”变成“已支付”,如果缓存里短时间内还是“待支付”,最多是用户刷新页面才看到新状态,并不会造成资损。但如果商品库存因为缓存旧数据而多卖了一件,那就是大事故。所以,一致性方案必须先按业务场景分级,而不是一刀切追求强一致。
我在项目里的做法是:把数据分成三类。核心交易数据(余额、库存、订单状态),这是底线,宁可牺牲一点性能也要保证正确;展示类数据(商品详情、用户昵称、文章内容),允许秒级延迟;统计类数据(浏览量、点赞数),允许更长时间的不一致。
针对不同级别,采用不同的缓存策略和兜底机制。这个分类在你动手写代码之前就应该完成,否则后面容易顾此失彼。
1.2 先分清三种业务流量:读多写少、写多读少、读写均衡
很多一致性方案失效,不是因为方案本身有问题,而是它根本不适用你的流量模型。我把常见业务分为三类:
读多写少,典型如商品详情页、文章详情。缓存命中率极高,写入是低频操作,方案重点在于“更新数据库后如何让缓存失效或更新”。
写多读少,典型如秒杀库存、点赞计数。每次写入都伴随缓存更新,如果直接删除缓存,很容易造成缓存击穿;如果更新缓存,又面临频繁写Redis的压力。这类场景通常需要后端异步合并,而不是每笔写入都同步更新缓存。
读写均衡,典型如会话信息、用户配置。读写频率接近,适合用带过期时间的Cache Aside,再加一层消息订阅做兜底。
这不是随便分类的,它直接影响你选择“删缓存”还是“更新缓存”,以及要不要引入消息队列。很多团队把一套方案用在所有业务上,出问题是迟早的事。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从Cache Aside到延迟双删:不同阶段我为什么换方案
业内最常提到的就是Cache Aside,也就是旁路缓存策略。但很多人只知其一,不知其二。在实际项
