【redis】CacheAside的数据不一致性问题

缓存的合理使用确提升了系统的吞吐量和稳定性,然而这是有代价的,这个代价便是缓存和数据库的一致性带来了挑战。

新增数据时,数据直接写入数据库,缓存中不存在对应记录。首次查询请求会触发缓存回填,即从数据库读取新数据并写入缓存,后续请求可直接命中缓存,所以新增数据时不会有数据不一致性问题。

当一条数据同时存在数据库、缓存,现在你要更新此数据,你会怎么更新?先更新数据库?还是先更新缓存?

有以下四种更新方式:

  • 先更新数据库后更新缓存
  • 先更新缓存后更新数据库
  • 先更新数据库后删除缓存
  • 先删除缓存后更新数据

接下来会逐一分析这四种更新方式带来的数据不一致性问题并解决。

先更新数据库后更新缓存

一种常见的操作是,设置一个过期时间,让写请求以数据库为准,过期后,读请求同步数据库中的最新数据给缓存。那么在加入了过期时间后,是否就不会有问题了呢?并不是这样。

时间线程A(写请求)线程B(写请求)问题
T1更新数据库为99
T2更新数据库为88
T3更新缓存为88
T4更新缓存为99
T5此时缓存的值为99,但是实际上数据库的值为88,数据不一致

数据不一致产生的场景:

  1. 并发写冲突:就是上面表格中的情况,线程A和线程B同时更新同一数据,线程A先完成数据库的更新,线程B然后完成数据库和缓存更新,最后线程A更新缓存,会导致缓存最终存储线程A的旧数据。
  2. 缓存更新失败:若数据库更新成功但缓存更新失败,后续请求会读取到旧缓存数据,直至缓存过期。

先更新缓存后更新数据库

先更新数据库后更新缓存会导致数据不一致,那你可能会想,这是否表示,我应该先让缓存更新,之后再去更新数据库呢?

时间线程A(写请求)线程B(写请求)问题
T1更新缓存为99
T2更新缓存为88
T3更新数据库为88
T4更新数据库为99
T5此时缓存的值为88,但是实际上数据库的值为99,数据不一致

数据不一致产生的场景:

  1. 并发写冲突:就是上面表格中的情况,线程A和线程B同时更新同一数据,线程A先完成缓存的更新,线程B然后完成缓存和数据库更新,最后线程A更新数据库,会导致缓存最终存储线程B的旧数据。
  2. 缓存成功但数据库失败:缓存存储新数据,数据库仍为旧值,后续请求直接命中缓存错误数据。
  3. 主从延迟问题:若数据库为主从架构,缓存更新后从库未同步完成,读请求可能从从库获取旧数据并覆盖缓存。

先删除缓存后更新数据库

既然更新数据库前后更新缓存都会导致数据不一致,那如果采取删除缓存的策略呢?也就是说我们在更新数据库的时候失效对应的缓存,让缓存在下次触发读请求时进行更新,是否会更好呢?

时间线程A(写请求)线程B(读请求)问题
T1删除缓存
T2从数据库中读取值为100,设置缓存中的值为100
T3更新数据库为99
T4此时缓存的值为100,但是实际上数据库的值为99,数据不一致

数据不一致产生的场景:

  1. 读写并发冲突:就是上面表格中的情况,线程A先删除缓存,线程B然后从数据库读取旧的值并更新缓存,最后线程A更新数据库,会导致缓存最终存储数据库的旧数据。

针对这种场景,有个做法是所谓的“延迟双删策略”,就是说,既然可能因为读请求把一个旧的值又写回去,那么我在写请求处理完之后,等到差不多的时间延迟再重新删除这个缓存值。

时间线程A(写请求)线程B(读请求)线程C(读请求)问题
T1删除缓存
T2从数据库中读取值为100,设置缓存中的值为100读到脏数据
T3更新数据库为99读到脏数据
T4sleep(N)读到脏数据
T5删除缓存
T6从数据库中读取值为99,设置缓存中的值为99

这种解决思路的关键在于对N的时间的判断,如果N时间太短,线程A第二次删除缓存的时间依旧早于线程B把脏数据写回缓存的时间,那么相当于做了无用功。而N如果设置得太长,那么在触发双删之前,新请求看到的都是脏数据。

先更新数据库后删除缓存

那如果我们把更新数据库放在删除缓存之前呢,问题是否解决?我们继续从读写并发的场景看下去,有没有类似的问题。

时间线程A(写请求)线程B(读请求)线程C(读请求)问题
T1更新数据库为99
T2从数据库中读取值为100,设置缓存中的值为100读到脏数据
T3删除缓存
T4从数据库中读取值为99,设置缓存中的值为99

可以看到,大体上,采取先更新数据库再删除缓存的策略是没有问题的,仅在更新数据库成功到缓存删除之间的时间差内——[T2,T3)的窗口,可能会被别的线程读取到老值,但是这个时间窗口非常的短

但是真实场景下,还是会有一个情况存在不一致的可能性,这个场景是读线程发现缓存不存在,于是读写并发时,读线程回写进去老值。并发情况如下:

时间线程A(写请求)线程B(读请求)问题
T1查询缓存,缓存缺失,查询数据库得到当前值100
T2更新数据库为99
T3删除缓存
T4将100写入缓存
T5此时缓存的值为100,但是实际上数据库的值为99,数据不一致

总的来说,这个不一致场景出现条件非常严格,因为并发量很大时,缓存不太可能不存在;如果并发很大,而缓存真的不存在,那么很可能是这时的写场景很多,因为写场景会删除缓存。

数据不一致性的根本原因

为什么我们几乎没办法做到缓存和数据库之间的强一致呢?

理想情况下,我们需要在数据库更新完后把对应的最新数据同步到缓存中,以便在读请求的时候能读到新的数据而不是旧的数据(脏数据)。但是很可惜,由于数据库和Redis之间是没有事务保证的,更新数据库和更新(删除)缓存不是一个原子操作,所以我们无法确保写入数据库成功后,写入Redis也是一定成功的;即便Redis写入能成功,在数据库写入成功后到Redis写入成功前的这段时间里,Redis数据也肯定是和MySQL不一致的。

所以说这个时间窗口是没办法完全消灭的,除非我们付出极大的代价,使用分布式事务等各种手段去维持强一致,但是这样会使得系统的整体性能大幅度下降,甚至比不用缓存还慢,这样不就与我们使用缓存的目标背道而驰了吗?

不过虽然无法做到强一致,但是我们能做到的是缓存与数据库达到最终一致,而且不一致的时间窗口我们能做到尽可能短,要是数据库更新完毕后,删除缓存失败了咋办?

对于这种情况,一种常见的解决方案就是使用消息中间件来实现删除的重试。大家知道,MQ一般都自带消费失败重试的机制,当我们要删除缓存的时候,就往MQ中扔一条消息,缓存服务读取该消息并尝试删除缓存,删除失败了就会自动重试。

异步延迟双删

延迟双删:先执行缓存清除操作,再执行数据库更新操作,延迟N秒之后再执行一次缓存清除操作,这样就不用担心缓存中的数据和数据库中的数据不一致了。

那么这个延迟N秒,N是多大比较合适呢?一般来说,N要大于一次写操作的时间,如果延迟时间小于写入缓存的时间,会导致请求A 已经延迟清除了缓存,但是此时请求B缓存还未写入,具体是多少,就要结合自己的业务来统计这个数值了。

通过订阅MySQL binlog的方式处理缓存

上面讲到的MQ处理方式需要业务代码里面显式地发送MQ消息。还有一种优雅的方式便是订阅MySQL的binlog,监听数据的真实变化情况以处理相关的缓存。

目前业界类似的产品有Canal,具体的操作图如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/83092.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DA14585墨水屏学习

一、do_min_word void do_min_work(void) {timer_used_min app_easy_timer(APP_PERIPHERAL_CTRL_TIMER_DELAY_MINUTES, do_min_work);current_unix_time time_offset;time_offset 60;// if (isconnected 1)// {// GPIO_SetActive(GPIO_LED_PORT, GPIO_LED_PIN);// …

微服务调试问题总结

本地环境调试。 启动本地微服务,使用公共nacos配置。利用如apifox进行本地代码调试解决调试问题。除必要的业务微服务依赖包需要下载到本地。使用mvn clean install -DskipTests进行安装启动前选择好profile环境进行启动,启动前记得mvn clean清理项目。…

C#学习第22天:网络编程

网络编程的核心概念 1. 套接字(Sockets) 定义:套接字是网络通信的基本单元,提供了在网络中进行数据交换的端点。用途:用于TCP/UDP网络通信,支持低级别的网络数据传输。 2.协议 TCP(Transmiss…

TWASandGWAS中GBS filtering and GWAS(1)

F:\文章代码\TWASandGWAS\GBS filtering and GWAS README.TXT 请检查幻灯片“Vitamaize_update_Gorelab_Ames_GBS_filtering_20191122.pptx”中关于阿姆斯(Ames)ID处理流程的详细信息。 文件夹“Ames_ID_processing”包含了用于处理阿姆斯ID的文件和R…

图像处理篇---opencv实现坐姿检测

文章目录 前言一、方法概述使用OpenCV和MediaPipe关键点检测角度计算姿态评估 二、完整代码实现三、代码说明PostureDetector类find_pose()get_landmarks()cakculate_angle()evaluate_posture() 坐姿评估标准(可进行参数调整):可视化功能&…

.Net HttpClient 使用代理功能

HttpClient 使用代理功能 实际开发中,HttpClient 通过代理访问目标服务器是常见的需求。 本文将全面介绍如何在 .NET 中配置 HttpClient 使用代理(Proxy)功能,包括基础使用方式、代码示例、以及与依赖注入结合的最佳实践。 注意…

【学习路线】 游戏客户端开发入门到进阶

目录 游戏客户端开发入门到进阶:系统学习路线与推荐书单一、学习总原则:从底层出发,项目驱动,持续迭代二、推荐学习路线图(初学者→进阶)第一阶段:语言基础与编程思维第二阶段:游戏开…

精益数据分析(57/126):创业移情阶段的核心要点与实践方法

精益数据分析(57/126):创业移情阶段的核心要点与实践方法 在创业的浩瀚征程中,每一个阶段都承载着独特的使命与挑战。今天,我们继续秉持共同进步的理念,深入研读《精益数据分析》,聚焦创业的首…

015枚举之滑动窗口——算法备赛

滑动窗口 最大子数组和 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 原题链接 思路分析 见代码注解 代码 int maxSubArray(vector<int>& num…

微软系统 红帽系统 网络故障排查:ping、traceroute、netstat

在微软&#xff08;Windows&#xff09;和红帽&#xff08;Red Hat Enterprise Linux&#xff0c;RHEL&#xff09;等系统中&#xff0c;网络故障排查是确保系统正常运行的重要环节。 ping、traceroute&#xff08;在Windows中为tracert&#xff09;和netstat是三个常用的网络…

解构认知边界:论万能方法的本体论批判与方法论重构——基于跨学科视阈的哲学-科学辩证

一、哲学维度的本体论批判 &#xff08;1&#xff09;理性主义的坍缩&#xff1a;从笛卡尔幻想到哥德尔陷阱 笛卡尔在《方法论》中构建的理性主义范式&#xff0c;企图通过"普遍怀疑-数学演绎"双重机制确立绝对方法体系。然而哥德尔不完备定理&#xff08;Gdel, 19…

【网络入侵检测】基于源码分析Suricata的IP分片重组

【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全&#xff0c;欢迎关注与评论。 目录 目录 1.概要 2. 配置信息 2.1 名词介绍 2.2 defrag 配置 3. 代码实现 3.1 配置解析 3.1.1 defrag配置 3.1.2 主机系统策略 3.2 分片重组模块 3.2.1…

二分查找的边界问题

前言 二分查找(Binary Search)是一种高效的查找算法&#xff0c;时间复杂度为O(log n)。它适用于已排序的数组或列表。本文将详细介绍二分查找的两种常见写法&#xff1a;闭区间写法和左闭右开区间写法。 一、二分查找基本思想 二分查找的核心思想是"分而治之"&am…

重庆医科大学附属第二医院外科楼外挡墙自动化监测

1.项目概述 重庆医科大学附属第二医院&#xff0c;重医附二院&#xff0c;是集医疗、教学、科研、预防保健为一体的国家三级甲等综合医院。前身为始建于1892年的“重庆宽仁医院”。医院现有开放床位 1380张&#xff0c;年门诊量超过百万人次&#xff0c;年收治住院病人4.5万人…

【Redis实战篇】秒杀优化

1. 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单…

【idea】调试篇 idea调试技巧合集

前言&#xff1a;之前博主写过一篇idea技巧合集的文章&#xff0c;由于技巧过于多了&#xff0c;文章很庞大&#xff0c;所以特地将调试相关的技巧单独成章, 调试和我们日常开发是息息相关的&#xff0c;用好调试可以事半功倍 文章目录 1. idea调试异步线程2. idea调试stream流…

postman 用法 LTS

postman 用法 LTS File ---- View ---- Show Postman Console

MySQL 数据库故障排查指南

MySQL 数据库故障排查指南 本指南旨在帮助您识别和解决常见的 MySQL 数据库故障。我们将从问题识别开始&#xff0c;逐步深入到具体的故障类型和排查步骤。 1. 问题识别与信息收集 在开始排查之前&#xff0c;首先需要清晰地了解问题的现象和范围。 故障现象&#xff1a; 数…

用AI写简历是否可行?

让AI批量写简历然后投简历是绝对不行的&#xff01;&#xff01;&#xff01; 为什么不行&#xff0c;按照 "招聘经理" 工作经历举例&#xff1a; ai提示词&#xff1a;请帮我写一份招聘经理的工作经历内容&#xff1a; 招聘经理 | XXX科技有限公司 | 2020年…

【从零实现JsonRpc框架#1】Json库介绍

1.JsonCpp第三方库 JSONCPP 是一个开源的 C 库&#xff0c;用于解析和生成 JSON&#xff08;JavaScript Object Notation&#xff09;数据。它提供了简单易用的接口&#xff0c;支持 JSON 的序列化和反序列化操作&#xff0c;适用于处理配置文件、网络通信数据等场景。 2.Jso…