redis从入门到熟练 (四) 缓存读写一致性问题

150 阅读6分钟

redis的缓存更新策略

1.Cache Aside Pattern(旁路缓存模式)

这个模式是我们平常用的比较多的模式,比较适合读请求比较多的情况

写操作
1.先更新数据库
2.然后直接删除cache
图片

读操作
1.从 cache 中读取数据,读取到就直接返回
2.cache 中读取不到的话,就从 db 中读取数据返回
3.再把数据放到 cache 中。

疑问点 1.在写数据的过程中,可以先删除 cache ,后更新 db 么? ·

不可以!很有可能发生缓存不一致的问题

请求 1 先把 cache 中的 A 数据删除 -> 请求 2 从 db 中读取数据-> 请求 1 再把 db 中的 A 数据更新

请求1先删除A的缓存,此时数据还没更新,这时候请求2从db中读取数据(此时请求1的数据库还没更新),这个时候cache就会有数据了
然后请求1中的db更新了,但是缓存已经存在了。就会导致redis的缓存是未更新的数据,mysql的数据是更新后的数据。

2.在写数据的过程中,先更新db,再删除redis的缓存,就一定没有问题吗?

请求 1 从 db 读数据 A-> 请求 2 更新 db 中的数据 A(此时缓存中无数据 A ,故不用执行删除缓存操作 ) 
-> 请求 1 将数据 A 写入 cache

目前在redis的cache中没有缓存,请求1从db中读数据,然后在redis写数据(在写数据之前mysql的db进行了更新,而且因为此时缓存中没有A,也不用删除),这样的话,用的缓存数据就是更新前的数据了

但这种情况几乎不存在,因为reids在内存中写,会比mysql快很多。

缺陷 1:首次请求数据一定不在 cache 的问题解决办法:可以将热点数据可以提前放入 cache 中。
缺陷 2:写操作比较频繁的话导致 cache 中的数据会被频繁被删除,这样会影响缓存命中率 。 解决办法:数据库和缓存数据强一致场景:更新 db 的时候同样更新 cache,不过我们需要加一个锁/分布式锁来保证更新 cache 的时候不存在线程安全问题。 可以短暂地允许数据库和缓存数据不一致的场景:更新 db 的时候同样更新 cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。

2.Read/Write Through Pattern(读写穿透)

Read/Write Through 是一种将缓存作为主要数据访问层的设计模式,应用程序只与缓存交互,缓存系统自身负责与底层数据库的数据同步。

1.读穿透 (Read Through)

应用程序直接向缓存请求数据
如果缓存命中,直接返回数据
如果缓存未命中:
缓存系统自动从数据库加载数据
将数据存入缓存
返回给应用程序

2.写穿透 (Write Through)

应用程序向缓存写入数据
缓存系统先更新自身数据
缓存系统同步将数据写入数据库
返回写入结果给应用程序

3.架构图

 应用程序 → [ 缓存 ] ↔ 数据库
              ↖______ ↙

4.优点分析

简化应用逻辑:应用不需要关心缓存与数据库的同步问题
数据一致性:写操作保证缓存和数据库同步更新

当应用程序执行 写操作(如更新数据) 时,缓存层(如 Redis)不会立即返回成功,而是会 同步等待数据库(如 MySQL)也更新完成,确保缓存和数据库的数据完全一致后,才向应用程序返回成功响应。 减少代码重复:避免在每个业务逻辑中重复缓存处理代码

缺点与挑战 实现复杂度:需要封装缓存层,对现有架构改造较大
写性能:每次写操作都需要等待数据库IO完成
缓存系统依赖:缓存系统需要了解数据库schema

适用场景
读多写少的系统 需要强一致性的业务场景 希望简化应用层代码的项目

3.Write Behind Pattern(异步缓存写入)

Write Behind Caching 是一种以缓存为中心的高性能写入策略,其核心哲学是:"先快速响应,后异步持久化"。这种模式将缓存视为事实上的数据源,而数据库则作为备份存储,通过异步方式保持最终一致性。

[应用程序][缓存层][异步队列][数据库]

写入路径:应用 → 缓存 → 确认响应 → (异步) → 数据库

读取路径:应用 ← 缓存 (始终从缓存读取最新数据)

写入过程
1.应用发起写请求
2.系统立即更新缓存数据
3.记录变更到写缓冲区/队列
4.立即返回成功响应
5.后台线程定期批量处理缓冲区数据:
6.合并相同key的多次更新
7.批量写入数据库
8.清理已处理的缓冲区条目

读取过程
1.应用发起读请求
2.直接从缓存返回最新数据(无论是否已持久化到数据库)

潜在挑战
数据丢失风险:系统崩溃时未持久化的数据会丢失
一致性妥协:无法保证数据库实时反映最新状态
复杂度增加:需要处理故障恢复和缓冲区管理
监控难度:需要额外监控缓存-数据库延迟

适用场景
写密集型系统:如点击流、日志收集
允许数据丢失的场景:如实时统计、行为分析
突发写入高峰:需要缓冲消峰的场景
非关键业务数据:如社交媒体的点赞、浏览计数

redis的最终一致性

(1) 延迟双删策略

// 1. 先删除缓存
redis.del(key);
// 2. 更新数据库
db.update(data);
// 3. 休眠一段时间(如500ms)后再次删除缓存
Thread.sleep(500);
redis.del(key);

(2) 基于消息队列的最终一致性

数据库更新后发送消息到MQ
消费者接收消息删除缓存
失败时可重试

(3) 基于binlog的同步

使用Canal等工具监听数据库binlog
解析变更日志后删除/更新缓存

选择策略的建议

强一致性要求:使用分布式锁+同步双写,但性能较低
最终一致性可接受:Cache Aside + 延迟双删或消息队列
读多写少:Read Through模式
写多读少:Write Behind模式