当前位置: 首页 > news >正文

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

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

常见的缓存模式有: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 命令。

在这里插入图片描述

相关文章:

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

今天,我们来聊聊常见的缓存模式和数据一致性问题。 常见的缓存模式有:Cache Aside、Read Through、Write Through、Write Back、Refresh Ahead、Singleflight。 缓存模式 Cache Aside 在 Cache Aside 模式中,是把缓存当做一个独立的数据源…...

嵌入式常用功能之通讯协议1--IIC

嵌入式常用功能之通讯协议1--串口 嵌入式常用功能之通讯协议1--IIC(本文) 嵌入式常用功能之通讯协议1--SPI 一、IIC总线协议介绍 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP 半导体…...

【Wi-Fi】Wi-Fi 7(802.11be) Vs Wi-Fi 8 (802.11bn)

介绍 WiFi 7 (802.11be) 是 WiFi-6 (802.11ax) 的继任者,旨在提高数据速率并减少拥挤环境中的延迟。 WiFi 8 (8021.1bn)是后续标准,专注于提高 WLAN 连接的可靠性, 提高…...

Ubuntu软件包管理机制

文章目录 🍊自我介绍🍊Ubuntu软件包管理机制🍊软件安装命令详解: 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞关注评论收藏(一键四连)哦~ 🍊自我介绍 Hello,大家好…...

SpringBoot详解:概念、优点、运行方式、配置文件、异步请求及异常处理

一、什么是SpringBoot? SpringBoot是一个基于Spring框架的开源项目,它简化了Spring应用的初始搭建以及开发过程。它提供了自动配置、起步依赖、Actuator、命令行界面等特性,使得开发者可以快速构建出一个独立、生产级别的Spring应用。 二、…...

npm install -g @vue/cil 非常卡慢

安装 vue/cli 时遇到卡慢的情况通常和网络问题有关,特别是国内的网络环境下访问 npm 的服务器可能较慢。你可以尝试以下几种方法来加速: 使用淘宝镜像源 淘宝 NPM 镜像源对国内用户更加友好。你可以临时使用淘宝镜像源安装 vue/cli: npm inst…...

Windows 基础 (二):系统目录与环境变量

内容预览 ≧∀≦ゞ Windows 基础 2:系统目录与环境变量声明系统目录系统核心目录其他重要日志目录应用程序数据目录用户数据目录隐藏目录 环境变量1. 查看环境变量2. 设置永久环境变量3. 查看特定环境变量的值4. 环境变量的存储位置5. 自定义环境变量的应用 结语 Wi…...

World of Warcraft [CLASSIC][80][the Ulduar] BOSS 05 06 07

BOSS-05-钢铁议会 BOSS-06-科隆加恩(无困难模式) BOSS-07-欧尔莉亚(无困难模式)...

World of Warcraft [CLASSIC][80][the Ulduar] BOSS 12 13

BOSS-12-维扎克斯将军 BOSS-13-尤格萨隆...

第一篇 硬件篇1[学习-来自 正点原子]

在电路设计中,TVS(瞬态电压抑制器)是一种有效的保护元件,可以用来防止瞬时过电压对芯片和其他敏感器件造成损坏。 STM32F103RCT6作为MCU 一键下载电路的具体实现过程: 首先, mcuisp控制 DTR输出低电平&…...

【TextIn:开源免费的AI智能文字识别产品(通用文档智能解析识别、OCR识别、文档格式转换、篡改检测、证件识别等)】

TextIn:开源免费的AI智能文字识别产品(通用文档智能解析识别、OCR识别、文档格式转换、篡改检测、证件识别等) 产品的官网:TextIn官网 希望感兴趣以及有需求的小伙伴们多多了解,因为这篇文章也是源于管网介绍才产出的…...

C++语言有哪些常用语句?

1. 变量定义语句 在 C 中,首先要定义变量才能使用。例如 int a;定义了一个整型变量a。这是很基础的语句,它告诉编译器为变量a分配内存空间,用于存储整数值。 如果要定义多个相同类型的变量,可以写成 int a, b, c;除了基本数据类…...

linux alsa-lib snd_pcm_open函数源码分析(二)

​ 访问原版内容,可直接到博客 linux alsa-lib snd_pcm_open函数源码分析(二) https://blog.whatsroot.xyz/2020/08/12/alsa_snd_open-analysis-2/ 系列文章其他部分: linux alsa-lib snd_pcm_open函数源码分析(一) linux alsa-lib snd_pc…...

机翼的抖振与颤振

机翼的抖振与颤振 1. 机翼颤振:飞机设计的隐形杀手2. 机翼抖振:由气流不稳定性引起的振动3. 两种振动的区分和管理3.1 检测与预防 机翼的颤振和抖振是飞机设计和航空工程师面临的两个重要技术问题。这两种现象虽然都与机翼的振动相关,但它们的…...

React04 State变量 组件渲染

State变量 & 渲染和提交 State 变量state 变量的使用State 是隔离且私有的 组件渲染 State 变量 state 变量的使用 导入 useState import { useState } from react;定义一个 state 变量 const [index, setIndex] useState(0);useState 的唯一参数是 state 变量的初始值…...

【数据库系统概论】第3章 关系数据库标准语言SQL(一)数据查询(超详细)

目录 一、单表查询 1. 简单的数据查询 (1)选择表中若干列 (2)选择表中若干行(元祖) 2. 聚合函数与分组查询 聚集函数 GROUP BY分组查询 二、联接查询 1、连接概述 2. 内联接(INNER JO…...

mysql-恢复数据(日志管理)

前言 在mysql中我们有时候会出现误删除,或者其他的问题,我们可以通过mysql的日志进行恢复 操作 我们可以在mysql里面定义一个错误日志,方便我们可以排查是因为什么原因来解决mysql无法启动问题 ----------------------------------------…...

探索Unity:从游戏引擎到元宇宙体验,聚焦内容创作

unity是实时3D互动内容创作和运营平台,包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助Unity将创意变成现实。提供一整套完善的软件解决方案,可用于创作、运营和变现任何实时互动的2D和3D内容,支持平台包括手机、…...

自动化测试类型与持续集成频率的关系

持续集成是敏捷开发的一个重要实践,可是究竟多频繁的集成才算“持续”集成? 一般来说,持续集成有3种常见的集成频率,分别是每分钟集成、每天集成和每迭代集成。项目组应当以怎样的频率进行集成,这取决于测试策略&…...

React 中组件通信的几种主要方式

一、父传子&#xff1a; 1. 传递多个属性 父组件可以通过 props 传递多个属性给子组件。 示例 // 子组件 function Son(props) {return (<div>This is Son, {props.name} - Age: {props.age}</div>); }// 父组件 function App() {const name This is App N…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

Java线上CPU飙高问题排查全指南

一、引言 在Java应用的线上运行环境中&#xff0c;CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时&#xff0c;通常会导致应用响应缓慢&#xff0c;甚至服务不可用&#xff0c;严重影响用户体验和业务运行。因此&#xff0c;掌握一套科学有效的CPU飙高问题排查方法&…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

Java求职者面试指南:计算机基础与源码原理深度解析

Java求职者面试指南&#xff1a;计算机基础与源码原理深度解析 第一轮提问&#xff1a;基础概念问题 1. 请解释什么是进程和线程的区别&#xff1f; 面试官&#xff1a;进程是程序的一次执行过程&#xff0c;是系统进行资源分配和调度的基本单位&#xff1b;而线程是进程中的…...