深入解析缓存模式下的数据一致性问题

今天,我们来聊聊常见的缓存模式和数据一致性问题。

常见的缓存模式有:Cache AsideRead ThroughWrite ThroughWrite BackRefresh AheadSingleflight

缓存模式

Cache Aside

在 Cache Aside 模式中,是把缓存当做一个独立的数据源,在读取和写入数据时,由业务方来控制顺序。

在这里插入图片描述
在【写策略】中,有一个更新顺序,是先更新数据库,还是先更新缓存呢?

一般来说,我们要先更新数据库,因为在大多数业务场景下,数据是以数据库为准的,如果写入数据库成功了,即使写入缓存失败,缓存本身会有过期时间,在过期之后重新加载,数据就会恢复一致了。

不管【先写数据库,还是先写缓存】都不能解决数据一直性问题。

在这里插入图片描述

删除缓存

其实,实际最常用的并不是去更新缓存,而是去删除缓存。也就是在更新数据的时候先更新数据库,然后将缓存删除。

【先更新数据库、再删除缓存】下,依旧存在数据一致性问题,但是它的一致性问题不是源自两个线程同时更新数据,而是源自一个线程更新数据,一个线程缓存未命中回查数据库。

实际上这个情况出现的概率非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

在这里插入图片描述

Read Through(读穿透)

在该模式下,当缓存里面没有数据的时候,缓存会代替我们去数据库里面把数据加载出来,并且缓存起来。

当缓存失效时,Cache Aside 是由业务方负责把数据加载入缓存,而 Read Through 则是缓存服务自己来加载,从而对应用方是透明的。

在这里插入图片描述
可以看到,Read Through 的写模式和Cache Aside是一样的,所以也会存在数据一致性问题。

Write Through(写穿透)

当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库。

在这里插入图片描述
显然,Write Through 也没有解决数据一致性的问题,可以参考 Cache Aside 中的分析。

Write Back

此模式下,当写入数据时,只是写到了缓存,当缓存过期的时候,才会被刷新到数据库。

在这里插入图片描述

这里有一个问题,如果数据还在缓存中的时候,缓存突然崩溃了,那数据就直接丢了,所以在使用中我们要能保证缓存的高可用。

那么,Write Back 能否解决数据一致性问题呢?

先看【写操作】,在更新数据时,由于业务代码只更新 redis 缓存中的数据,所以对于业务方来说,数据必然是一致的。

这里需要注意下,虽然缓存和数据库中的数据不一致,但是业务方只读写缓存,所以对于业务方来说,数据是一致的。

比如下面的例子,只有 a 过期时,数据库中的 a 才等于 2

在这里插入图片描述
再看【读操作】

当业务方读数据时,如果缓存没有数据,就要去数据库里面加载。这个时候,就有可能产生不一致的问题。

比如说,数据库中 a=3,读出来之后还没写到缓存里面。这个时候来了一个写请求,在缓存中写入了 a = 4。如果这时候读请求回写缓存,就会用数据库里的老数据覆盖缓存中的新数据。

在这里插入图片描述

那么这个问题怎么解决呢?

当读请求回写的时候,使用 SETNX 命令。也就是说,只有当缓存中没有数据的时候,才会回写数据。而如果回写失败了,那么读请求会再从缓存中读取到数据。

在这里插入图片描述

所以,如果能保证缓存的高可用,那么 Write Back 在缓存一致性的表现上,比其他模式要好。

Refresh Ahead

Refresh Ahead 是指利用 CDC(Capture Data Change)接口异步刷新缓存的模式。

比如说利用 Canal 来监听数据库的 binlog,然后 Canal 刷新 Redis。
在这里插入图片描述

这种模式也存在数据一致性的问题,也是出现在缓存未命中的读请求和写请求上。

在这里插入图片描述

解决方案跟 Write Back 模式是一样的,也是利用 SETNX 命令。

在这里插入图片描述

Singleflight

Singleflight 主要是为了控制住加载数据的并发量,它是指当缓存未命中的时候,访问同一个 key 的线程中只有一个会去真的加载数据,其他都在原地等待。

在这里插入图片描述

这个模式最大的优点就是可以减轻访问数据库的并发量。

比如说如果同一时刻有 100 个线程要访问 key1,那么最终也只会有 1 个线程去数据库中加载数据。

这个模式的缺点是如果并发量不高,那么基本没有效果。所以热点之类的数据就很适合用这个模式。

如何保证缓存一致性

在 深入解析缓存与数据库数据不一致问题 的文章中,我们已经深入讨论过,数据不一致的来源有两个

  • 源自操作部分失败;
  • 源自并发操作。

并且对【源自操作部分失败】的场景,给出了解决方案-【重试机制】。

所以,我们今天只讨论【源自并发操作】的场景。

要想解决并发操作带来的问题,可以使用缓存模式分布式锁消息队列版本号

版本号

版本号解决并发更新的问题的基本思路就是每一次更新版本号都要加一,然后低版本数据不能覆盖高版本的数据。

比如在 Cache Aside 模式里面,加上版本号之后,就不会出现不一致的问题了。

在这里插入图片描述

这个方案的难点在于怎么去维护版本号?

可以在数据库里面增加一个版本号字段,然后在更新 redis 缓存的时候,就得使用 lua 脚本,先检测并比对缓存的版本号,再执行更新。

假如, redis 中有两个 key,name_1118:ybbyqqname_1118_v:4,分别代表用户姓名和版本号值。

在更新 redis 的时候,我们需要有一个 lua 脚本来校验缓存的版本号。

-- Lua 脚本用于更新缓存,确保版本号的一致性
-- KEYS[1] 是缓存中的key
-- KEYS[2] 是缓存中的key的版本号
-- ARGV[1] 是新的版本号
-- ARGV[2] 是新的值

-- 获取缓存中的版本号
local cacheVersion = redis.call('GET', KEYS[2])

-- 检查传入的新版本号是否小于缓存中的版本号
if cacheVersion and tonumber(cacheVersion) >= tonumber(ARGV[1]) then
    -- 如果新版本号小于或等于缓存中的版本号,则不更新缓存
    return 0
end

-- 更新缓存中的值
local result = redis.call('SET', KEYS[1], ARGV[2])
-- 更新缓存中的版本号
result = redis.call('SET', KEYS[2], ARGV[1])

return result

然后将该脚本加载到服务中

redis-cli -a 111111 script load "$(cat version.lua)"

会返回一段 sha 码 057995d53a5f06cabc5e16a6c3ffdee666752f9

执行EVALSHA 命令

 EVALSHA 057995d53a5f06cabc5e16a6c3ffdee666752f95 2 name_1118 name_1118_v 5 ybb

消息队列

让更新请求都跑到消息队列上排个队,保证同一时刻只有一个线程在更新数据。

可以参考缓存模式中的 Refresh Ahead,且在回写缓存时,使用 SETNX 命令。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tensorflow案例4--人脸识别(损失函数选取,调用VGG16模型以及改进写法)

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前言 这个模型结构算上之前的pytorch版本的,算是花了不少时间,但是效果一直没有达到理想情况,主要是验证集和训练集准确率…

系统架构图设计(行业领域架构)

物联网 感知层:主要功能是感知和收集信息。感知层通过各种传感器、RFID标签等设备来识别物体、采集信息,并对这些信息进行初步处理。这一层的作用是实现对物理世界的感知和初步处理,为上层提供数据基础网络层:网络层负责处理和传输…

PySpark 本地开发环境搭建与实践

目录 一、PySpark 本地开发环境搭建 (一)Windows 本地 JDK 和 Hadoop 的安装 (二)Windows 安装 Anaconda (三)Anaconda 中安装 PySpark (四)Pycharm 中创建工程 二、编写代码 …

基于Python的自然语言处理系列(51):Weight Quantization

浮点数表示简介 浮点数的设计允许表示范围广泛的数值,同时保持一定的精度。浮点数表示的基本公式为: 在深度学习中,常见的浮点数格式有:float32(FP32)、float16(FP16)和bfloat16(BF16)。每种格式的具体特性如下: 格式总位数符号位指数位数尾数位数精度计算成…

c++:vector模拟实现

一、vector成员变量 库里实现用的就是这三个成员变量&#xff0c;咱们实现跟库里一样&#xff0c; namespace myvector {template<class T>class vector{public://vecttor的迭代器是原生指针typedef T* iterator;typedef const T* const_iterator; private:iterator _sta…

【Linux系列】Linux 和 Unix 系统中的`set`命令与错误处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

抗疫物资管理:SpringBoot技术应用案例

目 录 摘 要 1 前 言 2 第1章 概述 2 1.1 研究背景 3 1.2 研究目的 3 1.3 研究内容 4 第二章 开发技术介绍 5 2.1相关技术 5 2.2 Java技术 6 2.3 MySQL数据库 6 2.4 Tomcat介绍 7 2.5 Spring Boot框架 8 第三章 系统分析 9 3.1 可行性分析 9 3.1.1 技术可行性 9 3.1.2 经济可行…

机器学习实战(一)机器学习基础

"我不断地告诉大家&#xff0c;未来十年最热门的职业是统计学家。很多人认为我是开玩笑&#xff0c; 谁又能想到计算机工程师会是20世纪90年代最诱人的职业呢&#xff1f;如何解释数据、处理数据、从中抽取价值、展示和交流数据结果&#xff0c;在未来十年将是最重要的职业…

树叶分类竞赛(Baseline)以及kaggle的GPU使用

树叶分类竞赛(Baseline)-kaggle的GPU使用 文章目录 树叶分类竞赛(Baseline)-kaggle的GPU使用竞赛的步骤代码实现创建自定义dataset定义data_loader模型定义超参数训练模型预测和保存结果 kaggle使用 竞赛的步骤 本文来自于Neko Kiku提供的Baseline&#xff0c;感谢大佬提供代码…

奥数与C++小学四年级(第十一题 试商)

参考程序代码&#xff1a; #include <iostream> using namespace std; int main() { int dividend 2023; int count 0; // 余数从0开始遍历到被除数 for (int remainder 0; remainder < dividend; remainder) { int divisor remainder 2; // 计算商 if…

GPT-Sovits-2-微调模型

1. 大致步骤 上一步整理完数据集后&#xff0c;此步输入数据, 微调2个模型VITS和GPT&#xff0c;位置在 <<1-GPT-SoVITS-tts>>下的<<1B-微调训练>> 页面的两个按钮分别执行两个文件: <./GPT_SoVITS/s2_train.py> 这一步微调VITS的预训练模型…

关于前端程序员使用Idea快捷键配置的说明

相信很多前端程序员 转到后端第一件事就是安装Idea然后学习java&#xff0c;在这里面最难的不是java的语法&#xff0c;而是关于快捷键的修改&#xff0c;前端程序员用的最多的估计就是VsCode或者Webstorm&#xff0c;就拿我自己举例我经常使用Vscode&#xff0c;当我写完代码下…

ubuntu运行gazebo导致内存越来越少

1.用vscode看代码会一直有没用的日志缓存&#xff0c;可以删掉&#xff08;文件夹留着&#xff0c;可以把里面的东西删掉&#xff09; 2.运行gazebo的模型会有很多缓存文件&#xff0c;可以删掉 log文件夹非常大

视频智能分析平台LiteAIServer入侵检测算法平台部署行人入侵检测算法:智能安防的新利器

在当今数字化时代&#xff0c;安全防护成为了社会各界高度关注的重要议题。随着人工智能技术的不断发展&#xff0c;视频智能分析平台LiteAIServer 行人入侵检测算法应运而生&#xff0c;为安防领域带来了全新的突破与变革。 视频智能分析平台LiteAIServer 行人入侵检测算法是基…

架构师备考-非关系型数据库

基础理论 CAP 理论 C&#xff08;Consistency&#xff09;一致性。一致性是指更新操作成功并返回客户端完成后&#xff0c;所有的节点在同一时间的数据完全一致&#xff0c;与ACID 的 C 完全不同。A &#xff08;Availability&#xff09;可用性。可用性是指服务一直可用&…

七、k8s快速入门之资源控制器

文章目录 :star: RC:star: Deployment:three: create 和 apply的区别 :star: DaemonSet:star: job&&CronJob ⭐️ RC 1️⃣ 查看版本 kubect api-resource 或 kubect explain rs2️⃣ 编写Yaml文档 [rootmaster ~]# vim pod/rc.yaml apiVersion: extensions/v1beta1…

FreeRTOS移植到STM32F103C8T6(HAL库)

目录 一、将STM32F103ZET6代码变更成STM32F103C8T6 二、 将FreeRTOS码源添加到文件 三、代码更改适配 四、测试 一、将STM32F103ZET6代码变更成STM32F103C8T6 点击魔法棒&#xff0c;点击Device&#xff0c;选择芯片为STM32F103C8T6 进行编译&#xff0c;无报错无警告&am…

Nginx 的 Http 模块介绍(上)

Nginx 的 Http 模块介绍&#xff08;上&#xff09; 1. http 请求 11 个处理阶段介绍 Nginx 将一个 Http 请求分成多个阶段&#xff0c;以模块为单位进行处理。其将 Http请求的处理过程分成了 11 个阶段&#xff0c;各个阶段可以包含任意多个 Http 的模块并以流水线的方式处理…

六西格玛项目助力,手术机器人零部件国产化稳中求胜——张驰咨询

项目背景 XR-1000型腔镜手术机器人是某头部手术机器人企业推出的高端手术设备&#xff0c;专注于微创手术领域&#xff0c;具有高度的精确性和稳定性。而XR-1000型机器人使用的部分核心零部件长期依赖进口&#xff0c;特别是高精度电机、关节执行机构和视觉系统等&#xff0c;…

基于Python爬虫与文本挖掘的网络舆情监控系统【附源码】

基于Python爬虫与文本挖掘的网络舆情监控系统 效果如下&#xff1a; 系统登录界面 注册页面界面 管理员主界面 用户界面 网络舆情管理界面 看板详细页面 系统简介界面 用户主界面 网络舆情界面 研究背景 随着网络空间舆论的日益活跃&#xff0c;其对社会事件的影响愈发显著。…