Redis 缓存和 MySQL 数据不一致怎么办?双写一致性一次讲透

先说大前提:

面试官问"Redis 作为缓存,MySQL 的数据怎么和 Redis 同步",本质就是在考双写一致性

但这道题最忌讳的一点,就是上来直接背标准答案。

答题第一步,必须先交代业务背景。

你的业务到底是要强一致,还是能容忍延迟一致?

这两个前提对应的解法完全是两码事。

所以下面我按"强一致"和"延迟一致"两条线,把方案拆开讲清楚。

一、先搞懂什么是双写一致性

一句话定义:

数据库里的数据改了,缓存里的数据也得跟着改,两边不能对不上。

读数据的标准链路是这样的:

先查 Redis,命中就直接返回;

没命中才去查数据库,顺手把查到的数据写回 Redis,再返回结果。

这就是正常的读流程。

写数据场景里,常见方案之一是延时双删

延时双删具体分几步?

很简单:

第一步删缓存,第二步改数据库,第三步停一小会,第四步再删一次缓存。

那问题来了------为什么搞得这么绕?

核心是两个疑点:

更新时到底先删缓存还是先改库?

为什么要删两次而不是一次?

下面逐个分析。

二、先删缓存,还是先改库?

结论先摆上:

这两种顺序,单独看哪一种都会产生脏数据。

顺序 A:先删缓存,再改数据库。

正常情况下没毛病。

但只要有线程穿插就会翻车:

线程 1 先把缓存删了,还没来得及改库;

线程 2 这时来读,发现缓存空了,跑去数据库读出旧值 10 写回缓存;

等线程 1 把库更新成 20,局面就变成数据库是 20、缓存还是 10,数据对不上了。

顺序 B:先改数据库,再删缓存。

正常情况也没问题,但如果和缓存回填操作并发,也可能产生脏数据。

例如线程 1 查询缓存未命中,从数据库读取旧值 10,准备写入缓存;

此时线程 2 更新数据库为 20,并删除缓存;

随后线程 1 将之前读取的旧值 10 写入缓存,最终导致数据库是 20,缓存却是 10。

所以顺序本身不是解药,脏数据是两种顺序共有的隐患。

三、延时双删:为什么删两次、为什么要等

既然无论先删还是先改都可能脏,那就用双删去兜底:

数据库改完之后,再来一刀删缓存,把可能残留的旧值清掉,脏数据出现的概率就大幅降下来了。

那中间这一步"延时"又是图什么?

因为真实的数据库通常是主从架构、读写分离

你写的是主库,缓存服务读的是从库,主库得先把数据同步给从库。

延时的意义,就是等这波主从同步完成之后,再执行第二次删除。

但得说实话,这个延时时长没人能拍胸脯定准。

延短了,同步没完成,脏数据照样冒头;

延长了,性能又拉胯。

所以延时双删只是大幅压低风险,做不到绝对强一致

四、要强一致,就上读写锁

那怎么才能彻底强一致?

最朴素的做法是加一把分布式互斥锁

读和写都加锁,同一时刻只放一个线程进去,可以保证同一时刻只有一个线程修改数据,从而避免并发导致的数据不一致,但性能成本较高。

能不能优化?

能。

因为凡是放进缓存的数据,基本都是读多写少

既然读远多于写,就没必要读也加锁排队。

于是用读写锁,两把锁各管一摊:

锁类型 加锁后其他线程能否读 加锁后其他线程能否写
共享锁(读锁) 不能
排他锁(写锁) 不能 不能

用法很直观:

读操作加共享锁,大家都能一起读,只是不许写;

写操作加排他锁,读和写全给你堵住。

因为我们是读多写少,只有写的时候才阻塞,读完全不受影响。

对比读写都加锁的互斥方案,性能明显上来了,强一致也保住了。

代码层面,可以借助 Redisson 等客户端实现 Redis 分布式读写锁。

读方法里获取读锁、业务逻辑跑完释放;

写方法里获取同一把锁的写锁(锁的 key 必须对上,否则控不住并发),写完释放。

一句话记牢------读共享、写排他。

需要注意,锁只能解决并发访问导致的不一致问题,并不能解决所有分布式场景下的数据同步问题。

五、能接受延迟一致:异步通知

现实里,大多数业务其实能容忍短暂不一致,这也是主流做法,实现还简单。

方案 1:MQ 异步通知。

改完 MySQL,发一条消息给消息队列;

缓存服务监听这个队列,收到消息就把缓存更新掉。

中间有延迟,但能保证最终一致,可靠性全看 MQ 本身是否靠谱。

方案 2:Canal 监听 binlog。

Canal 是阿里开源的中间件,伪装成 MySQL 的从节点,盯着 binlog(记录 DDL、DML 这类变更的日志)。

一旦库里数据变更,Canal 会捕获 binlog 事件,通知下游服务更新缓存,中间存在一定同步延迟。

它最大的卖点是对业务代码零侵入------前面那些方案多少都要动业务代码,它完全不用。

只要业务能接受短暂延时,Canal 是很香的选择。

六、回到面试现场怎么答

千万别一上来就背方案,先把你简历里真实用到的缓存业务摆出来:

比如热点文章数据进缓存,实时性要求没那么高 → 用异步方案(MQ 或 Canal)做同步。

比如支付订单状态、账户余额等对一致性要求高的数据,可以考虑锁机制保证一致性。

你抛出了哪种业务,面试官就顺着追问哪种方案。

答的是异步,就展开 MQ 和 Canal;

答的是强一致,就展开共享锁与排他锁。

相关推荐
掘金者阿豪8 分钟前
Codex 怎么突然变慢了?一个需求跑几十分钟,我才发现它的工作方式已经变了
前端·后端
Zadig13 分钟前
Zadig 全面支持 CRD,至此所有 K8s 资源类型均可一键发布!
后端·devops
得物技术21 分钟前
EP-Harness:从个人 AI Coding 到团队级 Agent 工作流|得物技术
后端·程序员·架构
用户1257585243625 分钟前
对象存储 URL 为什么别到处拼:后台附件预览要验这一层
后端·go·ai编程
步行cgn31 分钟前
MyBatis 一对多关联映射详解
java·后端
阿弱34 分钟前
pi 扩展机制:加载、执行与能力
后端·llm·agent
省长2 小时前
Sa-Token v1.46.0 发布 🚀,来看看有没有令你心动的功能!
java·后端·开源
tech_zjf2 小时前
当 AI 把 Next.js Route 越写越快:我为什么做了 next-route-kit
前端·后端
魔兽大山哥3 小时前
提示词注入很热闹:NL2SQL 里它打得穿 Prompt,打不穿执行层
后端
智能架构人生3 小时前
Java开发者技能如何迁移到Kotlin
后端