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;

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

相关推荐
YuePeng1 小时前
别再让 AI 直接写 SQL 了:一个注解搞定十亿行数据的语义层
后端·github
半夜里咳嗽的狼1 小时前
Go 1.25 的 WaitGroup.Go 省了两行代码,也补不上这三个并发边界
后端·go
Lihua奏1 小时前
身份验证:登录之后,服务器怎么一直认得你?
后端
程序员天天困1 小时前
Arthas ognl 表达式从入门到实战:掌握在线调试最强的表达式引擎
java·jvm·后端
Escape2 小时前
为什么你的 AI 越聊越傻?从 Token 到 Agent,彻底搞懂 AI Agent的秘密㊙️
前端·人工智能·后端
用户40966601317512 小时前
Lombok 你用对了吗?@Data 之外的 6 个隐藏神器
java·后端·代码规范
董员外3 小时前
RAG 系统进化论(二):Naive RAG,检索增强生成的最小闭环
前端·人工智能·后端
掘金一周3 小时前
看看大家每月的成本有多少 | 沸点周刊 7.30
前端·人工智能·后端
aramae3 小时前
C++ IO流完全指南:从C标准库到C++流式编程
服务器·c语言·开发语言·c++·后端