缓存一致问题及常见解决方案

缓存是数据的副本。只要同一份数据存在两处(DB + Cache),写入时就会有一个时间窗口,期间两处不一致。所以"缓存一致性"不是"消灭不一致"

所有缓存一致性方案,本质都是回答同一个问题:

数据变更后,如何让 Redis 的旧副本失效?

主要考虑 更新顺序 动作选择 失败兜底

主流答案有这些:

  1. 先写 MySQL,再写 Redis

  2. 先写 Redis,再写 MySQL

  3. 先删除 Redis,再写 MySQL

  4. 先写 MySQL,再删除 Redis

  5. 先删 Redis,再写 MySQL,再删 Redis(延时双删)

  6. 先写 MySQL,通过 Binlog 异步更新 Redis(最终一致性,高可用架构)

123 不行 456 行

1.先写 MySQL,再写 Redis

上图为 先写 MySQL 再 Redis,在高并发情况下 如果 A 写的时候 B 已经完成了数据的更新 那么 A 又改回去了就会出现上面的情况

这里需要读请求不更新 Redis

2. 先写 Redis 再写 MySQL

原理同图 1

3. 先删 Redis,再写 MySQL

此处的请求 A 为更新请求 但是 B 为读请求 会回写 Redis

请求 A 删除缓存的时候 更新比较慢,请求B 先进行了两步查询

4. 延迟双删

对与 4 再加了一层删除 网络上特别多人使用这个

但是我问 AI 各种都是不推荐 因为缓存等待时间无法掌握 只是把小窗口换成了更小的窗口而已

当 Redis 网络抖动、Redis 宕机、业务线程池满导致延迟任务被丢弃、连接超时,可能会出现双删失败,这是用线程池提交延迟任务,任务内部自带重试

TTL 兜底的话双删失败也可以保证不永久丢数据

这里不多置喙

为什么要删第一次

如果没有第一次删除,从"从写 DB"到"延迟任务执行"这 500ms 内 所有读请求都会命中旧缓存

第一次删除负责"及时性",第二次删除负责"兜底并发窗口

更推荐进行消息队列异步串行化删除。

异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。

5.先写 MySQL,再删除 Redis

先删缓存的话,删完到 DB 提交完成之间,一个读请求会把旧值 重新加载进缓存 ------ 这就是著名的 "先删缓存"不一致 。反过来,先写 DB 再删缓存,读请求最多把尚未提交的旧值装进去,而这个窗口极短,且下一次写会再删一次。

对于第一次查询的情况,如果不是强一致性要求的业务,可以容忍

比如帖子,公告,短视频点赞数,用户头像,签名等等。

这种失败情况要同时满足

  1. 缓存刚好已失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间),要比读数据库 + 写缓存时间短

这明显不是很可能,因为写数据一般都加锁,绝大多数情况不可能比读数据库时间短

延迟双删变体-更删删

这部分既能解决主从复制的等待问题 也比延迟双删窗口更小 我觉得是当前最好的情况

先更新数据库 → 事务提交后删除缓存 → 等待一段时间 → 再次删除缓存

6. 先写 MySQL,通过 Binlog,异步更新 Redis

监听 MySQL 的 Binlog,然后通过异步的方式,将数据更新到 Redis,这种方案有个前提,查询的请求,不会回写 Redis。

订阅 MySQL 变更日志,只要这行数据发生更新,自动去删缓存。 哪怕业务代码里面根本没执行 DEL、代码 bug、消息丢了,binlog 一捕获变更,就去删缓存。很多大厂核心缓存用这个做终极兜底。

如果中途请求 B 需要查询数据,如果缓存无数据,就直接查 DB;如果缓存有数据,查询的数据也会存在不一致的情况。

7. 分布式读写锁(Redisson RReadWriteLock)

这是完全解决缓存一致问题的答案,缺点是吞吐量下降

写时阻塞读,读之间不冲突

几种方案比较

123 直接忽略

4.可行 但是复杂 还要引入消息队列 删更删的好处就算脏数据产生后有大概率情调脏 key 不需要 TTL 兜底

5.比较推荐 删除如果失败可以多试几次

此方案实时性最好,并发量也高 我觉得这的变体非常好比如(更删删策略)

6.对于多服务共同修改等,建议会用这种方式,纯高并发不使用 1

总结:

  • 实时一致性方案:采用"先写 MySQL,再删除 Redis"的策略。

有主从 推荐用 更删删 策略

  • 最终一致性方案 :采用"先写 MySQL,通过 Binlog,异步更新 Redis",可以通过 Binlog,结合消息队列异步更新 Redis,是最终一致性的最优解 。
相关推荐
聚美智数1 小时前
文档转换 API‑文档转 PDF‑PDF 转文档‑异步文档处理‑文件格式迁移‑转换结果回调推送 API 接口介绍
android·java·pdf
从零数据分析1 小时前
零基础学SQL 23:和上月比总对不上?LAG/LEAD 一步取出上一次(附 4 个模板)
java·数据库·sql·mysql·oracle·sqlite·database
码士集团小青1 小时前
Redis 缓存击穿(失效)、缓存穿透、缓存雪崩怎么解决?
数据库·redis·缓存
yuzhiboyouye2 小时前
com.fasterxml.jackson.annotation 里最常用高频的注解,可以按“控制字段、控制格式、控制结构、控制流程”来记
java·开发语言
码士集团小青2 小时前
硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战
数据库·redis·缓存
苜蓿muxu2 小时前
简述 ConcurrentHashMap 和 CopyOnWriteArrayList 的区别以及各自适用场景
java·编程语言
9527!到!2 小时前
Springboot @value读取配置参数踩坑&推荐解决方案
java·spring boot·spring
十年Java程序媛2 小时前
Java 面试常问 this、super:构造方法调用顺序避坑
java·开发语言·spring boot·面试·个人开发
337802 小时前
5分钟搞懂Spring的IoC和Bean管理
java