如何选择缓存模式?
如何选择缓存模式
当一个系统引入缓存后,最大的挑战之一便是如何确保缓存与后端数据库的一致性。目前,常见的解决方案主要有Cache Aside、Read/Write Throught和Write Back这三种缓存更新策略。
Read/Write Throught策略
读操作方面,如果缓存失效,则从数据库回源数据并设置到缓存中;写操作时,会同时更新数据库和缓存。这种策略的优点是能够保证缓存不会因为更新而失效,从而避免大量请求直接进入数据库。但其缺点也较为明显,容易出现数据不一致的情况。

产生不一致的情况

如下图所示,当缓存失效后,系统从数据库查询到v1版本数据。此时,如果发生数据更新操作,在数据库和缓存都更新完成后(数据库为v2版本,Redis缓存也为v2版本),查询流程可能会将之前从数据库回源的v1版本数据写回缓存,最终导致数据库为v2版本,而Redis缓存为v1版本,出现数据不一致的情况。这种不一致状态会一直持续到下一次数据修改或者缓存再次失效。

一般来说,在查询操作不复杂的情况下,其速度通常快于写操作。但出现上述数据不一致情况的原因主要有以下两点:
- 如果查询操作复杂且未命中mysql buffer,可能会慢于写操作,毕竟写操作有redo log的优化,执行速度也较快。
- 网络波动也可能对数据读写的顺序和时效性产生影响。
同样并发更新也可能产生问题
在更新操作并发的情况下,也可能出现数据不一致的问题。例如,多个并发的写操作可能会导致缓存和数据库之间的数据状态混乱,具体表现为不同操作的先后顺序和数据覆盖问题,从而引发数据不一致。

Cache Aside策略
此策略即为前文提到的只读缓存模式。读操作时,如果命中缓存则直接返回数据;若未命中,则从后端数据库加载数据到缓存后再返回。写操作则直接更新数据库,然后删除缓存。这种策略的优势在于始终以后端数据库的数据为准,能够有效保证缓存和数据库的一致性。然而,其缺点是写操作会使缓存失效,对于查询请求频繁的业务,可能会导致大量请求回源到数据库。在实际软件开发中,当使用Memcached或Redis时,这是一种较为常用的方案。不过,该策略虽然避免了并发更新导致的不一致问题,但缓存失效导致的数据不一致情况仍然存在。

Write Back策略
这一策略类似于前文所述的读写缓存模式 + 异步写回策略。
写操作仅对缓存进行,操作相对简单,系统会定时将缓存数据同步回数据库。
读操作方面,如果命中缓存则直接返回数据;若未命中,则从数据库加载数据到缓存中。当缓存已满时,会先将需要淘汰的缓存数据写回到后端数据库,然后再将对应的数据放入缓存中。
此外,当数据即将过期时,还需判断是否为脏数据,若是,则需先进行持久化操作,然后再让数据过期。
这种策略的优点是写操作速度极快(因为只写缓存),但其缺点是如果数据还未来得及写入后端数据库,系统就发生异常,可能会导致缓存和数据库的数据不一致。这种策略常用于操作系统的Page Cache中,或者在应对大量写操作的数据库引擎中也较为常见。
全量缓存
这种适用于缓存内容较少,或性能要求极高的场景,比如一些系统配置;这时往往通过定时任务+事件监听的方式,将数据库修改同步到内存和redis,如果缓存中访问不到不会在回源到数据库。
如何解决不一致
重试机制
在缓存或数据库操作过程中,除了正常流程外,还需考虑操作发生异常时的处理方式。例如,当缓存操作成功但数据库操作失败,或者反之,都可能导致数据不一致的问题。
- 对于一致性要求不高的业务场景,可以根据业务特点设计好更新缓存和数据库的先后顺序,以此降低数据不一致的影响;或者通过给缓存设置较短的有效期,来缩短不一致状态的持续时间。
- 而对于需要严格保证缓存和数据库一致性,即确保两者操作原子性的场景,这就涉及到分布式事务问题了。常见的解决方案包括两阶段提交(2PC)、三阶段提交(3PC)、TCC、消息队列等。不过,这些方案通常较为复杂,一般应用于对一致性要求极高的业务场景。
利用消息队列重试
在实际应用中,一种常用的一致性解决方案是利用消息队列进行重试。当缓存修改或删除操作失败时,系统会异步发送一条消息,进行重试消费,以此确保缓存和数据库的一致性。
延迟双删
在正常流程下,使用Read/Write Throught和Cache Aside策略时,由于缓存失效可能会导致旧值被重新写入缓存。虽然这种情况发生的概率极低,但在数据库读写分离的架构下,主从同步延迟会增加数据不一致的概率。
读写分离导致的不一致示例
例如,在“先更新数据库,再删除缓存”的方案中,“读写分离 + 主从库延迟”可能会导致如下数据不一致的情况:
- 线程A更新主库,将X的值从1更新为2。
- 线程A删除缓存。
- 线程B查询缓存,未命中,于是查询从库,得到旧值X = 1。
- 从库完成与主库的同步,此时主从库中X的值都为2。
- 线程B将从库中查询到的旧值1写入缓存。
最终,X的值在缓存中为1(旧值),而在主从库中为2(新值),出现了数据不一致的情况。
延迟双删解决方案
针对上述问题,最简单的解决办法是进行延迟双删。即在删除缓存后,延迟一段时间再次删除缓存。延迟的时间需要通过具体的测试来确定,同时删除的逻辑也可以异步执行,并进行重试。需要注意的是,延迟双删只能减少数据不一致的可能性,并不能完全避免。
定时矫正
强一致
在实际应用中,要实现缓存和数据库的“强一致”是非常困难的。常见的实现强一致的方案,如2PC、3PC、Paxos、Raft等一致性协议,虽然能够保证数据的强一致性,但其性能往往较差,并且这些方案相对复杂,还需要考虑各种容错问题。
当我们引入缓存时,其主要目的是提升系统性能。在这种情况下,性能和一致性就如同天平的两端,难以同时兼顾。以我们前面提到的方案为例,在操作数据库和缓存的过程中,只要在操作完成之前有其他请求介入,就有可能查询到“中间状态”的数据。
如果非要追求强一致,那么在所有更新操作完成之前,必须禁止“任何请求”进入。虽然我们可以通过加“分布锁”的方式来实现这一目标,但这样做所付出的代价,很可能会超过引入缓存所带来的性能提升。
因此,既然决定使用缓存,就必须在一定程度上容忍“一致性”问题。我们所能做的,就是尽可能降低问题出现的概率。同时,我们也要清楚地认识到,缓存都设置有“失效时间”。即使在某些时间段内存在短期的数据不一致,我们仍然可以依靠缓存的失效时间来兜底,从而实现数据的最终一致性。
总结
- 引入缓存后,需要着重考虑缓存和数据库的一致性问题。可供选择的方案主要有“更新数据库 + 更新缓存”以及“更新数据库 + 删除缓存”这两种。
- “更新数据库 + 更新缓存”方案在并发场景下,难以保证缓存和数据库的数据一致性,并且还可能出现“缓存资源浪费”和“机器性能浪费”的问题。
- 在“更新数据库 + 删除缓存”的方案中,“先删除缓存,再更新数据库”在并发场景下依然存在数据不一致的风险。针对这一问题,解决方案是“延迟双删”,但延迟时间的评估具有一定难度。因此,通常推荐采用“先更新数据库,再删除缓存”的方案。
- 在“先更新数据库,再删除缓存”的方案下,为了确保数据库更新和缓存删除这两步操作都能成功执行,需要配合“消息队列”或“订阅变更日志”等方案。其本质是通过“重试”机制来保证数据的一致性。
- 在“先更新数据库,再删除缓存”的方案下,“读写分离 + 主从库延迟”同样会导致缓存和数据库的数据不一致。缓解这一问题的方案仍然是“延迟双删”,即凭借经验将“延迟消息”发送到队列中,延迟删除缓存。同时,还需要对主从库延迟进行有效控制,尽可能降低数据不一致情况发生的概率。
相关文章:
如何选择缓存模式?
如何选择缓存模式 当一个系统引入缓存后,最大的挑战之一便是如何确保缓存与后端数据库的一致性。目前,常见的解决方案主要有Cache Aside、Read/Write Throught和Write Back这三种缓存更新策略。 Read/Write Throught策略 读操作方面,如果缓…...
机器学习常见面试题
常见基模型 1. 线性模型(Linear Models) 特点:通过线性组合特征进行预测,适合处理线性关系。常见类型: 线性回归(Linear Regression)逻辑回归(Logistic Regression)岭回…...
网络安全配置截图 网络安全i
网络安全概念及规范 1.网络安全定义 网络安全的概述和发展历史 网络安全 广义的网络安全:Cyber Security(网络空间安全) 网络空间有独立且相互依存的信息基础设施和网络组成,包括互联网、电信网、计算机系统、嵌入式处理器和控…...
k8s概念及k8s集群部署(Centos7)
Centos7部署k8s集群 部署之前,先简单说下k8s是个啥: 一、k8s简介: k8s,全称:kubernetes,它可以看作是一个分布式系统支撑平台。k8s的作用: 1、故障自愈: k8s这个玩意可以监控容器…...
Manus详细介绍,Manus核心能力介绍
文章目录 前言Manus产品定位与核心理念:Manus产品特性与未来体验战略:Manus商业价值与创新指标:Manus技术特点与竞争优势:Manus用户反馈与展望:Manus市场竞争优势与团队战略:Manus深度总结与启发: 前言 这是一篇关于Manus智能体产品的用户体验评价报告,主要介绍了M…...
Apache XTable:在数据湖仓一体中推进数据互作性
Apache XTable 通过以多种开放表格式提供对数据的访问,在增强互作性方面迈出了一大步。移动数据很困难,在过去,这意味着在为数据湖仓一体选择开放表格式时,您被锁定在该选择中。一个令人兴奋的项目当在数据堆栈的这一层引入互作性…...
Java直通车系列14【Spring MVC】(深入学习 Controller 编写)
目录 基本概念 编写 Controller 的步骤和要点 1. 定义 Controller 类 2. 映射请求 3. 处理请求参数 4. 调用业务逻辑 5. 返回响应 场景示例 1. 简单的 Hello World 示例 2. 处理路径变量和请求参数 3. 处理表单提交 4. 处理 JSON 数据 5. 异常处理 基本概念 Cont…...
36-Openwrt wifi命令工具iwconfig、iwinfo、iwpriv、iwlist
增对wifi的调试命令有很多,这边列出我们常用的命令提供参考,方便查看信息定位问题。 1、iwconfig 查看当前 WIFI 的工作信道以及工作带宽模式: root@openwrt:/# iwconfig ra0 ra0 mt7603e ESSID:"openwrt" Mode:Managed Channel:8 Access Point: DC:4B…...
tauri加载网页处理点击a链接默认浏览器打开问题
添加click事件,当点击了a标签,就阻止默认事件,然后自己处理,在自己窗口中打开这个页面。将这个js注入到页面中就可以了 const hookClick (e) > {console.log(hookClick, e)e.preventDefault()const origin e.target.closest…...
openharmony 软总线-设备发现流程
6.1 设备发现流程 6.1.1 Wi-Fi设备发现 6.1.1.1 Wi-Fi设备发现流程 Wi-Fi设备在出厂状态或者恢复出厂状态下,设备上电默认开启SoftAP模式,SoftAP的工作信道在1,6,11中随机选择,SoftAP的Beacon消息中携带的SSID eleme…...
大白话CSS 优先级计算规则的详细推导与示例
大白话CSS 优先级计算规则的详细推导与示例 答题思路 引入概念:先通俗地解释什么是 CSS 优先级,让读者明白为什么要有优先级规则,即当多个 CSS 样式规则作用于同一个元素时,需要确定哪个规则起作用。介绍优先级的分类࿱…...
【GoTeams】-4:为项目引入etcd
本文目录 1. 书接上回2. 引入etcddiscoverystruct{}{} resolverserver 3. 将服务注册到etcd中4. 梳理下etcd调用逻辑 1. 书接上回 本节是为项目引入etcd这个环节,然后我们来看看具体该怎么实现。 首先来谈谈为什么要引入服务发现? 动态服务注册与发现…...
DeepSeek + Kimi:高效制作PPT实战详解
在快节奏的职场环境中,制作高质量的PPT已成为许多人的日常任务。然而,从零开始构思、设计、撰写并优化一份精美的PPT往往耗时费力。幸运的是,AI技术的飞速发展为我们提供了全新的解决方案。本文将详细介绍如何利用DeepSeek与Kimi智能助手的高…...
计算机基础:二进制基础06,用八进制来计数
专栏导航 本节文章分别属于《Win32 学习笔记》和《MFC 学习笔记》两个专栏,故划分为两个专栏导航。读者可以自行选择前往哪个专栏。 (一)WIn32 专栏导航 上一篇:计算机基础:二进制基础05,八进制简介 回…...
OSCP最新备考攻略:迎接2024改版后的OSCP+认证
OSCP(Offensive Security Certified Professional)是渗透测试领域一块金字招牌,由Offensive Security打造,因其硬核实战和高门槛备受推崇。2024年11月1日,OSCP迎来了一次重量级改版,推出了OSCP认证…...
Jmeter使用介绍
文章目录 前言Jmeter简介安装与配置JDK安装与配置JMeter安装与配置 打开JMeter方式一方式二 设置Jmeter语言为中文方法一(仅一次性)方法二(永久设置成中文) Jmeter文件常用目录 元件与组件元件组件元件的作用域元件的执行顺序第一个案例添加线程组添加 H…...
hooks useModule自定义hooks (二次封装AgGridReact ag-table)自定义表头,自定义表头搜索
场景业务: 多次运用AgGridReact的table 列表 思路: 运用自定义hooks进行二次封装: 通用配置例如:传参的参数,传参的url,需要缓存的key这些键值类 定制化配置例如:需要对table 的一些定制化传…...
Android Studio 配置国内镜像源
Android Studio版本号:2022.1.1 Patch 2 1、配置gradle国内镜像,用腾讯云 镜像源地址:https\://mirrors.cloud.tencent.com/gradle 2、配置Android SDK国内镜像 地址:Index of /AndroidSDK/...
OFA:通过简单的序列到序列学习框架统一架构、任务和模态
【摘要】 摘要总结 本文介绍了一种新的统一框架OFA(One For All),旨在通过一个简单的序列到序列学习框架来实现跨模态和单模态任务的统一预训练。OFA框架支持任务无关性和模态无关性,并能实现任务全面性。OFA统一了包括图像生成、视觉定位、图像字幕、图像分类、语言建模…...
C++11新特性2.空指针nullptr
目录 一.简介 1.基本概念 2.语法 二.使用示例 示例1:初始化指针 示例2:作为函数参数 三.nullptr与NULL的区别 1.类型安全 2.函数重载问题 3.注意事项 一.简介 1.基本概念 nullptr 是一个类型安全的空指针常量,它的类型是 std::nul…...
实战案例分享:WLAN TKIP/CCMP加密组件的选择
无线接入点(AP)与终端(STA)在连接过程中涉及多种加密算法,如CCMP、TKIP等,选择合适的加密组件对于保证网络安全和兼容性至关重要。本篇我们将分析Wi-Fi加密机制、Wi-Fi加密组件的选型要点、典型问题及解决方…...
Day(19)--IO流(三)
文件加密 ps:^异或: 两边相同就是false 两边不同就是true 如果比较的是数字,那就会把它转换成为二进制,从右自左依次比较 总结:如果一个数字被异或两次,结果还是原来的数字 缓冲流 字节缓冲流 BufferedInputStream------字节缓冲输入流 BufferedOutputStream----字节…...
解锁STM32外设:开启嵌入式开发新世界
✨✨✨这里是小韩学长yyds的BLOG(喜欢作者的点个关注吧) ✨✨✨想要了解更多内容可以访问我的主页 小韩学长yyds-CSDN博客 目录 探索 STM32 强大的外设家族 初窥门径:STM32 外设开发基础 开发方式与工具 外设配置基础步骤 深入剖析:常见外设应用实例…...
SSLScan实战指南:全面检测SSL/TLS安全配置
SSLScan是一款开源的SSL/TLS安全扫描工具,用于检测服务器的加密协议、支持的加密套件、证书信息以及潜在的安全漏洞。本指南将详细介绍如何安装、使用SSLScan,并结合实战案例帮助您全面评估服务器的安全性。 一、SSLScan简介 功能特性: 检测支持的SSL/TLS协议版本(如TLS 1.…...
docker学习笔记(1)从安装docker到使用Portainer部署容器
docker学习笔记第一课 先交代背景 docker宿主机系统:阿里云ubuntu22.04 开发机系统:win11 docker镜像仓库:阿里云,此阿里云与宿主机系统没有关系,是阿里云提供的一个免费的docker仓库 代码托管平台:github&…...
基于Spring Boot的健美操评分管理系统设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
【Linux】——初识操作系统
文章目录 冯-诺依曼体系结构操作系统shell 冯-诺依曼体系结构 我们现在所使用的计算机就是冯-诺依曼体系结构。 存储器就是内存。 由下图可知,寄存器最快,为啥不用寄存器呢? 因为越快价格就最贵,冯诺依曼体系结构的诞生…...
PromQL计算gateway指标增量最佳实践及常见问题答疑
普米官方网站 普米官方帮助:Getting started | Prometheus 普米下载地址:Download | Prometheus 普米查询语法:Querying basics | Prometheus 普米函数参考:Query functions | Prometheus promql计算增量 在PromQLÿ…...
vue使用slot时子组件的onUpdated执行问题
vue使用slot时子组件的onUpdated执行问题 在使用 Vue 的插槽 (slot) 功能时,可能会遇到一个问题:当父组件的任何状态更新时,子组件的 onUpdated 事件会被触发。这个问题在使用默认插槽时尤为明显。 为了避免这种情况,可以使用作用…...
从零到多页复用:我的WPF MVVM国际化实践
文章目录 第一步:基础实现,资源文件入门第二步:依赖属性,提升WPF体验第三步:多页面复用,减少重复代码第四步:动态化,应对更多字符串总结与反思 作为一名WPF开发者,我最近…...
