如何选择缓存模式?

news2025/3/11 2:40:30

如何选择缓存模式

当一个系统引入缓存后,最大的挑战之一便是如何确保缓存与后端数据库的一致性。目前,常见的解决方案主要有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策略时,由于缓存失效可能会导致旧值被重新写入缓存。虽然这种情况发生的概率极低,但在数据库读写分离的架构下,主从同步延迟会增加数据不一致的概率。

读写分离导致的不一致示例

例如,在“先更新数据库,再删除缓存”的方案中,“读写分离 + 主从库延迟”可能会导致如下数据不一致的情况:

  1. 线程A更新主库,将X的值从1更新为2。
  2. 线程A删除缓存。
  3. 线程B查询缓存,未命中,于是查询从库,得到旧值X = 1。
  4. 从库完成与主库的同步,此时主从库中X的值都为2。
  5. 线程B将从库中查询到的旧值1写入缓存。
    最终,X的值在缓存中为1(旧值),而在主从库中为2(新值),出现了数据不一致的情况。

延迟双删解决方案

针对上述问题,最简单的解决办法是进行延迟双删。即在删除缓存后,延迟一段时间再次删除缓存。延迟的时间需要通过具体的测试来确定,同时删除的逻辑也可以异步执行,并进行重试。需要注意的是,延迟双删只能减少数据不一致的可能性,并不能完全避免。

定时矫正

强一致

在实际应用中,要实现缓存和数据库的“强一致”是非常困难的。常见的实现强一致的方案,如2PC、3PC、Paxos、Raft等一致性协议,虽然能够保证数据的强一致性,但其性能往往较差,并且这些方案相对复杂,还需要考虑各种容错问题。
当我们引入缓存时,其主要目的是提升系统性能。在这种情况下,性能和一致性就如同天平的两端,难以同时兼顾。以我们前面提到的方案为例,在操作数据库和缓存的过程中,只要在操作完成之前有其他请求介入,就有可能查询到“中间状态”的数据。
如果非要追求强一致,那么在所有更新操作完成之前,必须禁止“任何请求”进入。虽然我们可以通过加“分布锁”的方式来实现这一目标,但这样做所付出的代价,很可能会超过引入缓存所带来的性能提升。
因此,既然决定使用缓存,就必须在一定程度上容忍“一致性”问题。我们所能做的,就是尽可能降低问题出现的概率。同时,我们也要清楚地认识到,缓存都设置有“失效时间”。即使在某些时间段内存在短期的数据不一致,我们仍然可以依靠缓存的失效时间来兜底,从而实现数据的最终一致性。

总结

  1. 引入缓存后,需要着重考虑缓存和数据库的一致性问题。可供选择的方案主要有“更新数据库 + 更新缓存”以及“更新数据库 + 删除缓存”这两种。
  2. “更新数据库 + 更新缓存”方案在并发场景下,难以保证缓存和数据库的数据一致性,并且还可能出现“缓存资源浪费”和“机器性能浪费”的问题。
  3. 在“更新数据库 + 删除缓存”的方案中,“先删除缓存,再更新数据库”在并发场景下依然存在数据不一致的风险。针对这一问题,解决方案是“延迟双删”,但延迟时间的评估具有一定难度。因此,通常推荐采用“先更新数据库,再删除缓存”的方案。
  4. 在“先更新数据库,再删除缓存”的方案下,为了确保数据库更新和缓存删除这两步操作都能成功执行,需要配合“消息队列”或“订阅变更日志”等方案。其本质是通过“重试”机制来保证数据的一致性。
  5. 在“先更新数据库,再删除缓存”的方案下,“读写分离 + 主从库延迟”同样会导致缓存和数据库的数据不一致。缓解这一问题的方案仍然是“延迟双删”,即凭借经验将“延迟消息”发送到队列中,延迟删除缓存。同时,还需要对主从库延迟进行有效控制,尽可能降低数据不一致情况发生的概率。

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

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

相关文章

网络安全配置截图 网络安全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 通过以多种开放表格式提供对数据的访问,在增强互作性方面迈出了一大步。移动数据很困难,在过去,这意味着在为数据湖仓一体选择开放表格式时,您被锁定在该选择中。一个令人兴奋的项目当在数据堆栈的这一层引入互作性…

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…

【GoTeams】-4:为项目引入etcd

本文目录 1. 书接上回2. 引入etcddiscoverystruct{}{} resolverserver 3. 将服务注册到etcd中4. 梳理下etcd调用逻辑 1. 书接上回 本节是为项目引入etcd这个环节,然后我们来看看具体该怎么实现。 首先来谈谈为什么要引入服务发现? 动态服务注册与发现…

Jmeter使用介绍

文章目录 前言Jmeter简介安装与配置JDK安装与配置JMeter安装与配置 打开JMeter方式一方式二 设置Jmeter语言为中文方法一(仅一次性)方法二(永久设置成中文) Jmeter文件常用目录 元件与组件元件组件元件的作用域元件的执行顺序第一个案例添加线程组添加 H…

Android Studio 配置国内镜像源

Android Studio版本号:2022.1.1 Patch 2 1、配置gradle国内镜像,用腾讯云 镜像源地址:https\://mirrors.cloud.tencent.com/gradle 2、配置Android SDK国内镜像 地址:Index of /AndroidSDK/

实战案例分享:WLAN TKIP/CCMP加密组件的选择

无线接入点(AP)与终端(STA)在连接过程中涉及多种加密算法,如CCMP、TKIP等,选择合适的加密组件对于保证网络安全和兼容性至关重要。本篇我们将分析Wi-Fi加密机制、Wi-Fi加密组件的选型要点、典型问题及解决方…

解锁STM32外设:开启嵌入式开发新世界

✨✨✨这里是小韩学长yyds的BLOG(喜欢作者的点个关注吧) ✨✨✨想要了解更多内容可以访问我的主页 小韩学长yyds-CSDN博客 目录 探索 STM32 强大的外设家族 初窥门径:STM32 外设开发基础 开发方式与工具 外设配置基础步骤 深入剖析:常见外设应用实例…

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 冯-诺依曼体系结构 我们现在所使用的计算机就是冯-诺依曼体系结构。 存储器就是内存。 由下图可知,寄存器最快,为啥不用寄存器呢? 因为越快价格就最贵,冯诺依曼体系结构的诞生&#xf…

PromQL计算gateway指标增量最佳实践及常见问题答疑

普米官方网站 普米官方帮助:Getting started | Prometheus 普米下载地址:Download | Prometheus 普米查询语法:Querying basics | Prometheus 普米函数参考:Query functions | Prometheus promql计算增量 在PromQL&#xff…

vue使用slot时子组件的onUpdated执行问题

vue使用slot时子组件的onUpdated执行问题 在使用 Vue 的插槽 (slot) 功能时,可能会遇到一个问题:当父组件的任何状态更新时,子组件的 onUpdated 事件会被触发。这个问题在使用默认插槽时尤为明显。 为了避免这种情况,可以使用作用…

从零到多页复用:我的WPF MVVM国际化实践

文章目录 第一步:基础实现,资源文件入门第二步:依赖属性,提升WPF体验第三步:多页面复用,减少重复代码第四步:动态化,应对更多字符串总结与反思 作为一名WPF开发者,我最近…

LeetCode 解题思路 12(Hot 100)

解题思路: 定义三个指针: prev(前驱节点)、current(当前节点)、nextNode(临时保存下一个节点)遍历链表: 每次将 current.next 指向 prev,移动指针直到 curre…

HTML-05NPM使用踩坑

2025-03-04-NPM使用踩坑 本文讲述了一个苦逼程序员在使用NPM的时候突然来了一记nmp login天雷,然后一番折腾之后,终究还是没有解决npm的问题😞😞😞,最终使用cnpm完美解决的故事。 文章目录 2025-03-04-NPM使用踩坑[toc…

学校地摊尝试实验

学校地摊尝试实验 诸位,我要告诉诸位一件大消息,那就是,我将会利用学校时光的最后一段时间进行疯狂摆摊练习,如何进行摆摊,大家 听我娓娓道来。我要确定摆摊的目的, 第一,赚钱,第二…