问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
在高并发场景下,Redis 通常用作缓存层,与数据库结合使用以提高系统的性能。为了保证缓存数据与数据库的最终一致性,通常采用的有双写机制、缓存失效机制,基于双写机制、缓存失效机制又衍生出来了消息队列、事件驱动架构等
常见机制
常见的机制如下,个人理解无非是先后或各种手段操作数据库、redis,代码ai给写的示列只需看懂即可。
- 双写机制
在更新数据库的同时,同步更新缓存。
适用于写操作较少的场景
public class CacheService {private final JdbcTemplate jdbcTemplate;private final RedisTemplate<String, Object> redisTemplate;public void updateData(String key, String value) {// 更新数据库jdbcTemplate.update("UPDATE table SET value = ? WHERE key = ?", value, key);// 更新缓存redisTemplate.opsForValue().set(key, value);}
- 缓存失效机制
在更新数据库后,删除缓存中的旧数据,读取数据时候时写入缓存
适用于写操作频繁的场景。
public class CacheService {private final JdbcTemplate jdbcTemplate;private final RedisTemplate<String, Object> redisTemplate;public void updateData(String key, String value) {// 更新数据库jdbcTemplate.update("UPDATE table SET value = ? WHERE key = ?", value, key);// 删除缓存redisTemplate.delete(key);}public String getData(String key) {// 从缓存中获取数据String value = (String) redisTemplate.opsForValue().get(key);if (value == null) {// 缓存未命中,从数据库中获取数据value = jdbcTemplate.queryForObject("SELECT value FROM table WHERE key = ?", new Object[]{key}, String.class);if (value != null) {// 将数据写入缓存redisTemplate.opsForValue().set(key, value);}}return value;}}
- 消息队列机制
使用消息队列异步更新redis,确保数据的一致性。
适用于高并发写操作的场景。
import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;public class CacheService {private final JdbcTemplate jdbcTemplate;private final RedisTemplate<String, Object> redisTemplate;public void updateData(String key, String value) {// 更新数据库jdbcTemplate.update("UPDATE table SET value = ? WHERE key = ?", value, key);// 发送消息到消息队列sendUpdateMessage(key, value);}private void sendUpdateMessage(String key, String value) {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection();Channel channel = connection.createChannel()) {channel.queueDeclare("cache_update_queue", true, false, false, null);channel.basicPublish("", "cache_update_queue", null, (key + ":" + value).getBytes());} catch (Exception e) {e.printStackTrace();}}public void consumeUpdateMessages() {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");try (Connection connection = factory.newConnection();Channel channel = connection.createChannel()) {channel.queueDeclare("cache_update_queue", true, false, false, null);DeliverCallback deliverCallback = (consumerTag, delivery) -> {String message = new String(delivery.getBody(), "UTF-8");String[] parts = message.split(":");String key = parts[0];String value = parts[1];// 更新缓存redisTemplate.opsForValue().set(key, value);};channel.basicConsume("cache_update_queue", true, deliverCallback, consumerTag -> {});} catch (Exception e) {e.printStackTrace();}}}
- 事件驱动机制
使用事件驱动架构,当数据库数据发生变化时,触发事件,事件处理器负责更新缓存。
适用于复杂的数据更新逻辑。
import org.springframework.context.ApplicationEventPublisher;import org.springframework.context.ApplicationEventPublisherAware;import org.springframework.stereotype.Service;@Servicepublic class CacheService implements ApplicationEventPublisherAware {private final JdbcTemplate jdbcTemplate;private final RedisTemplate<String, Object> redisTemplate;private ApplicationEventPublisher eventPublisher;public void updateData(String key, String value) {// 更新数据库jdbcTemplate.update("UPDATE table SET value = ? WHERE key = ?", value, key);// 发布事件eventPublisher.publishEvent(new DataUpdatedEvent(this, key, value));}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.eventPublisher = applicationEventPublisher;}@Servicepublic class EventListener {private final RedisTemplate<String, Object> redisTemplate;@org.springframework.context.event.EventListenerpublic void handleDataUpdatedEvent(DataUpdatedEvent event) {// 更新缓存redisTemplate.opsForValue().set(event.getKey(), event.getValue());}}}public class DataUpdatedEvent extends ApplicationEvent {private final String key;private final String value;public DataUpdatedEvent(Object source, String key, String value) {super(source);this.key = key;this.value = value;}public String getKey() {return key;}public String getValue() {return value;}}
- 定期补偿机制
定期对缓存和数据库的数据进行校验,发现不一致时进行补偿操作。
适用于对数据一致性要求较高的场景。
import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class DataConsistencyChecker {private final JdbcTemplate jdbcTemplate;private final RedisTemplate<String, Object> redisTemplate;private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);public void startChecking() {scheduler.scheduleAtFixedRate(() -> {// 从数据库中获取所有数据List<Map<String, Object>> dataFromDB = jdbcTemplate.queryForList("SELECT key, value FROM table");for (Map<String, Object> row : dataFromDB) {String key = (String) row.get("key");String value = (String) row.get("value");// 从缓存中获取数据String cacheValue = (String) redisTemplate.opsForValue().get(key);if (!value.equals(cacheValue)) {// 数据不一致,更新缓存redisTemplate.opsForValue().set(key, value);}}}, 0, 1, TimeUnit.HOURS);}}
废弃缓存与更新缓存的取舍
由上面代码可看出 1和2 最大的区别在于更新数据库时到底是更新缓存还是删除缓存。
【废弃缓存】
优点:
操作简单,只需在更新数据库后删除缓存,下次读取时重新从数据库加载数据,减少了写的操作日数
缺点:
可能短暂不一致:在缓存删除后和新数据写入缓存前,可能会出现短暂的缓存不一致
【更新缓存】
优点:
数据强一致性:更新数据库和缓存同时进行,确保数据的一致性。
减少数据库读压力:缓存始终是最新的,减少了对数据库的读操作。
缺点:
复杂性增加:需要处理缓存更新失败的情况,可能需要回滚操作。
性能影响:每次更新操作都需要同时更新数据库和缓存,增加了操作的复杂性和时间
- 写操作较少的场景:
推荐使用更新缓存:因为写操作较少,更新缓存的额外开销相对较小,且可以确保数据的一致性。 - 写操作频繁的场景:
推荐使用废弃缓存:因为写操作频繁,更新缓存会增加系统的复杂性和开销,而废弃缓存可以减少缓存的写操作,降低系统负担。 - 对数据一致性要求极高的场景:
推荐使用更新缓存:尽管复杂性增加,但可以确保数据的强一致性。 - 对性能要求较高且可以容忍短暂不一致的场景:
推荐使用废弃缓存:可以减少数据库的读压力,提高系统的整体性能
淘汰缓存的顺序
https://blog.csdn.net/qq_39033181/article/details/119276120
【 方案一 】先淘汰缓存,再更新数据库
在并发量较大的情况下,会导致数据的不一致。
1. A线程进行写操作,先成功淘汰缓存,但由于网络或其它原因,还未更新数据库
2. B线程进行读操作,发现缓存中没有想要的数据,从数据库中读取到的是旧数据,并把旧数据放入缓存。此时数据库与缓存都是旧值,数据没有不一致
3. A线程将数据库更新完成,数据库中是更新后的新数据,缓存中是更新前的旧数据,造成数据不一致。
【 方案二 】先更新数据库,再淘汰缓存
在并发量较大的情况下,会导致数据的短暂不一致,但是数据会最终一致。
1. A线程进行写操作,更新数据库,还未淘汰缓存
2. B线程从缓存中可以读取到旧数据,此时数据不一致
3. A线程完成淘汰缓存操作,其它线程进行读操作,从数据库中读入最新数据,此时数据一致
延时双删
上述方案二更简单,在高并发场景下也能保证数据的最终一致性,但是如果我就想用方案一呢?
什么是延时双删
先删再更新数据库 过N秒后再删一次缓存,怎么实现放后面spring-cache集成里,大概有 1.延时队列、2.线程池实现延时任务。
小结
- 这些都是理论,真正写代码,有cache框架,哪有这么烦,很多人喜欢问,那我们就得理,理了总比不理好,写这个就是怕我自己忘,呵
- 无论怎么样在高并发场景下,我们也只能要求缓存数据与数据库的最终一致性,如果要求强一致性还要缓存干嘛呢?操作直接走DB更香
- 大多数情况下建议使用淘汰缓存机制,然后先更新数据库,再淘汰缓存,满足大多数的场景了
相关文章:
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
在高并发场景下,Redis 通常用作缓存层,与数据库结合使用以提高系统的性能。为了保证缓存数据与数据库的最终一致性,通常采用的有双写机制、缓存失效机制,基于双写机制、缓存失效机制又衍生出来了消息队列、事件驱动架构等 常见机…...
Stable Diffusion核心网络结构——CLIP Text Encoder
🌺系列文章推荐🌺 扩散模型系列文章正在持续的更新,更新节奏如下,先更新SD模型讲解,再更新相关的微调方法文章,敬请期待!!!(本文及其之前的文章均已更新&…...
C语言-11-18笔记
1.C语言数据类型 类型存储大小值范围char1 字节-128 到 127 或 0 到 255unsigned char1 字节0 到 255signed char1 字节-128 到 127int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295short2 字节…...
数据结构_图的遍历
深度优先搜索遍历 遍历思想 邻接矩阵上的遍历算法 void Map::DFSTraverse() {int i, v;for (i 0; i < MaxLen; i){visited[i] false;}for (i 0; i < Vexnum; i){// 如果顶点未访问,则进行深度优先搜索if (visited[i] false){DFS(i);}}cout << endl…...
设计LRU缓存
LRU缓存 LRU缓存的实现思路LRU缓存的操作C11 STL实现LRU缓存自行设计双向链表 哈希表 LRU(Least Recently Used,最近最少使用)缓存是一种常见的缓存淘汰算法,其基本思想是:当缓存空间已满时,移除最近最少使…...
python中的base64使用小笑话
在使用base64的时候将本地的图片转换为base64 代码如下,代码绝对正确 import base64 def image_to_data_uri(image_path):with open(image_path, rb) as image_file:image_data base64.b64encode(image_file.read()).decode(utf-8)file_extension image_path.sp…...
Python之time时间库
time时间库 概述获取当前时间time库datetime库区别 时间元组处理获取时间元组的各个部分时间戳和时间元组的转换 格式化时间格式化时间解析时间格式符号说明 暂停程序计时操作简单计时高精度计时计时器类的实现 UTC时间操作time库datetime库 概述 time是Python标准库中的一个模…...
Easyexcel(4-模板文件)
相关文章链接 Easyexcel(1-注解使用)Easyexcel(2-文件读取)Easyexcel(3-文件导出)Easyexcel(4-模板文件) 文件导出 获取 resources 目录下的文件,使用 withTemplate 获…...
国产linux系统(银河麒麟,统信uos)使用 PageOffice 动态生成word文件
PageOffice 国产版 :支持信创系统,支持银河麒麟V10和统信UOS,支持X86(intel、兆芯、海光等)、ARM(飞腾、鲲鹏、麒麟等)、龙芯(LoogArch)芯片架构。 数据区域填充文本 数…...
Window11+annie 视频下载器安装
一、ffmpeg环境的配置 下载annie之前需要先配置ffmpeg视频解码器。 网址下载地址 https://ffmpeg.org/download.html1、在网址中选择window版本 2、点击后选择该版本 3、下载完成后对压缩包进行解压,后进行环境的配置 (1)压缩包解压&#…...
SAP GR(Group Reporting)配置篇(七)
1.7、合并处理的配置 1.7.1 定义方法 菜单路径 组报表的SAP S4HANA >合并处理的配置>定义方法 事务代码 SPI4...
共建智能软件开发联合实验室,怿星科技助力东风柳汽加速智能化技术创新
11月14日,以“奋进70载,智创新纪元”为主题的2024东风柳汽第二届科技周在柳州盛大开幕,吸引了来自全国的汽车行业嘉宾、技术专家齐聚一堂,共襄盛举,一同探寻如何凭借 “新技术、新实力” 这一关键契机,为新…...
优化表单交互:在 el-select 组件中嵌入表格显示选项
介绍了一种通过 el-select 插槽实现表格样式数据展示的方案,可更直观地辅助用户选择。支持列配置、行数据绑定及自定义搜索,简洁高效,适用于复杂选择场景。完整代码见GitHub 仓库。 背景 在进行业务开发选择订单时,如果单纯的根…...
每日一题 LCR 079. 子集
LCR 079. 子集 主要应该考虑遍历的顺序 class Solution { public:vector<vector<int>> subsets(vector<int>& nums) {vector<vector<int>> ans;vector<int> temp;dfs(nums,0,temp,ans);return ans;}void dfs(vector<int> &…...
cocos creator 3.8 Node学习 3
//在Ts、js中 this指向当前的这个组件实例 //this下的一个数据成员node,指向组件实例化的这个节点 //同样也可以根据节点找到挂载的所有组件 //this.node 指向当前脚本挂载的节点//子节点与父节点的关系 // Node.parent是一个Node,Node.children是一个Node[] // th…...
微信小程序底部button,小米手机偶现布局错误的bug
预期结果:某button fixed 到页面底部,进入该页面时,正常显示button 实际结果:小米13pro,首次进入页面,button不显示。再次进入时,则正常展示 左侧为小米手机第一次进入。 遇到bug的解决思路&am…...
【计组】复习题
冯诺依曼型计算机的主要设计思想是什么?它包括哪些主要组成部分? 主要设计思想: ①采用二进制表示数据和指令,指令由操作码和地址码组成。 ②存储程序,程序控制:将程序和数据存放在存储器中,计算…...
Apache Maven 标准文件目录布局
Apache Maven 采用了一套标准的目录布局来组织项目文件。这种布局提供了一种结构化和一致的方式来管理项目资源,使得开发者更容易导航和维护项目。理解和使用标准目录布局对于有效的Maven项目管理至关重要。本文将探讨Maven标准目录布局的关键组成部分,并…...
Android 功耗分析(底层篇)
最近在网上发现关于功耗分析系列的文章很少,介绍详细的更少,于是便想记录总结一下功耗分析的相关知识,有不对的地方希望大家多指出,互相学习。本系列分为底层篇和上层篇。 大概从基础知识,测试手法,以及案例…...
【Xbim+C#】创建圆盘扫掠IfcSweptDiskSolid
基础回顾 https://blog.csdn.net/liqian_ken/article/details/143867404 https://blog.csdn.net/liqian_ken/article/details/114851319 效果图 代码示例 在前文基础上,增加一个工具方法: public static IfcProductDefinitionShape CreateDiskSolidSha…...
IntelliJ+SpringBoot项目实战(四)--快速上手数据库开发
对于新手学习SpringBoot开发,可能最急迫的事情就是尽快掌握数据库的开发。目前数据库开发主要流行使用Mybatis和Mybatis Plus,不过这2个框架对于新手而言需要一定的时间掌握,如果快速上手数据库开发,可以先按照本文介绍的方式使用JdbcTemplat…...
利用oss进行数据库和网站图片备份
1.背景 由于网站迁移到香港云 服务器,虽然便宜,但是宿主服务器时不时重启,为了预防不可控的因素导致网站资料丢失,所以想到用OSS 备份网站数据,bucket选择在香港地区创建,这样和你服务器传输会更快。 oss…...
Excel - VLOOKUP函数将指定列替换为字典值
背景:在根据各种复杂的口径导出报表数据时,因为关联的表较多、数据量较大,一行数据往往会存在三个以上的字典数据。 为了保证导出数据的效率,博主选择了导出字典code值后,在Excel中处理匹配字典值。在查询百度之后&am…...
实验室管理平台:Spring Boot技术构建
3系统分析 3.1可行性分析 通过对本实验室管理系统实行的目的初步调查和分析,提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本实验室管理系统采用SSM框架,JAVA作为开发语言&a…...
操作系统进程和线程——针对实习面试
目录 操作系统进程和线程什么是进程和线程?进程和线程的区别?进程有哪些状态?什么是线程安全?如何实现线程安全?什么是线程安全?如何实现线程安全? 进程间的通信有哪几种方式?什么是…...
使用 cnpm 安装 Electron,才是正确快速的方法
当然,下面是总结的几种安装 Electron 的方法,包括使用 npm 和 cnpm,以及一些常见的问题解决技巧。 ### 1. 使用 npm 安装 Electron #### 步骤 1: 初始化项目 在你的项目目录中初始化一个新的 Node.js 项目: bash npm init -y …...
【人工智能】PyTorch、TensorFlow 和 Keras 全面解析与对比:深度学习框架的终极指南
文章目录 PyTorch 全面解析2.1 PyTorch 的发展历程2.2 PyTorch 的核心特点2.3 PyTorch 的应用场景 TensorFlow 全面解析3.1 TensorFlow 的发展历程3.2 TensorFlow 的核心特点3.3 TensorFlow 的应用场景 Keras 全面解析4.1 Keras 的发展历程4.2 Keras 的核心特点4.3 Keras 的应用…...
【第八课】Rust中的函数与方法
目录 前言 函数指针 函数当作另一个函数的参数 函数当作另一个函数的返回值 闭包 方法 关联函数 总结 前言 在前面几课中,我们都或多或少的接触到了rust中的函数,rust中的函数和其他语言的并没有什么不同,简单的语法不在这篇文章中赘…...
c语言学习25二维数组
1 二维数组 1.1二维数组认识 二维数组本质是一个数组。 举例: int a[10][3] 数组名 a; 元素个数10; 数组元素类型:int [3]; 数组元素下标:0~9 这是一个数组,有十个元素,每个元…...
如何理解Lua 使用虚拟堆栈
虚拟堆栈的基本概念 Lua使用虚拟堆栈来实现Lua和C(或其他宿主语言)之间的交互。这个虚拟堆栈是一个数据结构,用于存储Lua的值,如数字、字符串、表、函数等。它在Lua状态机(lua_State)内部维护,为…...
大浪网站建设/网络广告投放网站
解决在Oracle数据库中使用hibernate生成表不能正确创建表的有关问题 www.MyException.Cn 发布于:2013-08-26 12:17:40 浏览:16次 0解决在Oracle数据库中使用hibernate生成表不能正确创建 表的问题 最近在项目中使用hibernate的动态生成表&#x…...
苏州做网站好的公司/网络营销是做什么
AOP概念 AOP的实现 方式1:使用Spring的API接口 1:编写一个类,实现相关的接口 2:核心配置文件 3:单元测试 方式2:自定义实现AOP 1:自定义一个类 2:核心配置文件 3:…...
网站备案背景布/站牛网是做什么的
作者:张华 发表于:2016-02-25 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 ) 使用gdb或者crash时需要debug symbols, 可以从http://ddebs.ubuntu.…...
建站历史查询/国际新闻最新消息战争
这篇文章只是我自己一些胡乱荒唐的吐槽,记录下来,希望多年以后再看到这里时能有不同的想法吧。 2013年9月,二狗在OUC入学,从此从一个只会用电脑打游戏的傻小子,开始逐步变成一个靠电脑维生的“秃头”码农。我的大学生涯…...
大连个人网站建设/seo领导屋
主要的问题可能就是页面加载的问题,有时候页面没有加载成功导致对象找不到,从而导致脚本不能运行。 主要使用的方式是,在测试的全局中设置一个页面的加载时间,如果timeout就是没有找到对象。如下参考: http://chon.tec…...
哪个独立网站做的比较好/网站关键词排名seo
简 介 熟悉深度学习的开发者对Papers with Code肯定不陌生,作为全球领先的开源机器学习资源平台,集成论文、代码、数据集等全方位资料。 每年Papers with Code都会和Medium(基于主题的高质量媒体平台)共同评选出十大年度趋势论文…...