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

缓存最佳实践

目录

前言

一、Cache Aside(旁路缓存)策略

二、不一致解决场景及解决方案

一、数据库主从不一致

二、缓存与数据库不一致

三、问题分析

三、缓存误用

一、多服务共用缓存实例

二、调用方缓存数据

三、缓存作为服务与服务之间传递数据的媒介

四、使用缓存未考虑雪崩

总结


前言

缓存,是互联网分层架构中,非常重要的一个部分,通常用它来降低数据库压力,提升系统整体性能,缩短访问时间。有架构师说“缓存是万金油,哪里有问题,加个缓存,就能优化”,缓存的滥用,可能会导致一些错误用法。

缓存,你真的用对了么?


一、Cache Aside(旁路缓存)策略

旁路缓存策略是最常用的一种缓存读写策略,它适用于读请求比较多,数据更新频率不高的场景。它的基本思想是:应用程序直接访问缓存和数据库,而不通过中间层。当需要读取数据时,先从缓存中查找,如果命中则直接返回;如果未命中,则从数据库中查询,并将结果放入缓存中,然后返回。当需要更新数据时,先更新数据库,然后删除缓存。

Cache Aside 策略(也叫旁路缓存策略),这 个策略数据以数据库中的数据为准,缓存中的数据是按需加载的。它可以分为读策略和写策 略。

其中读策略的步骤是:

  1. 从缓存中读取数据,如果缓存命中,则直接返回数据;
  2. 如果缓存不命中,则从数据库中查询数据;
  3. 查询到数据后,将数据写入到缓存中,并且返回给用户。

     

写策略的步骤是:
  1. 更新数据库中的记录;
  2. 删除缓存记录。

    

你也许会问了,在写策略中,能否先删除缓存,后更新数据库呢?答案是不行的,因为这样也有可能出现缓存数据不一致的问题,我以用户表的场景为例解释一下。假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读 取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21,这就造成了缓存和数据库的不一致。
 

那么像 Cache Aside 策略这样先更新数据库,后删除缓存就没有问题了吗?其实在理论上还是有缺陷的。假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中,造成缓存和数据库数据不一致。

 不过这种问题出现的几率并不高,原因是缓存的写入通常远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且清空了缓存,请求 A 才更新完缓存的情况。而一旦请求 A 早于请求 B 清空缓存之前更新了缓存,那么接下来的请求就会因为缓存为空而从数据库中重新加载数据,所以不会出现这种不一致的情况。

Cache Aside 策略是我们日常开发中最经常使用的缓存策略,不过我们在使用时也要学会依情况而变。比如说当新注册一个用户,按照这个更新策略,你要写数据库,然后清理缓存(当然缓存中没有数据给你清理)。可当我注册用户后立即读取用户信息,并且数据库主从分离时,会出现因为主从延迟所以读不到用户信息的情况。而解决这个问题的办法恰恰是在插入新数据到数据库之后写入缓存,这样后续的读请求就会从缓存中读到数据了。并且因为是新注册的用户,所以不会出现并发更新用户信息的情况。Cache Aside 存在的最大的问题是当写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响。

二、不一致解决场景及解决方案

发生写请求后(不管是先操作DB,还是先淘汰Cache),在主从数据库同步完成之前,如果有读请求,都可能发生读Cache Miss,读从库把旧数据存入缓存的情况。此时怎么办呢?

一、数据库主从不一致

无缓存时,数据库主从不一致问题

如上图,发生的场景是,写后立刻读:
(1)主库一个写请求(主从没同步完成)
(2)从库接着一个读请求,读到了旧数据
(3)最后,主从同步完成

导致的结果是:主动同步完成之前,会读取到旧数据。可以看到,主从不一致的影响时间很短,在主从同步完成后,就会读到新数据。

二、缓存与数据库不一致

再看,引入缓存后,缓存和数据库不一致问题。

 如上图,发生的场景也是,写后立刻读

导致的结果是:旧数据放入缓存,即使主从同步完成,后续仍然会从缓存一直读取到旧数据。

可以看到,加入缓存后,导致的不一致影响时间会很长,并且最终也不会达到一致。

三、问题分析

可以看到,这里提到的缓存与数据库数据不一致,根本上是由数据库主从不一致引起的。当主库上发生写操作之后,从库binlog同步的时间间隔内,读请求,可能导致有旧数据入缓存。

思路:那能不能写操作记录下来,在主从时延的时间段内,读取修改过的数据的话,强制读主,并且更新缓存,这样子缓存内的数据就是最新。在主从时延过后,这部分数据继续读从库,从而继续利用从库提高读取能力。

选择性读主

可以利用一个缓存记录必须读主的数据。

如上图,当写请求发生时:


(1)写主库
(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”
 

PS:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。

如上图,当读请求发生时:


这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,


(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询。并且把主库的数据set到缓存中,防止下一次cahce miss。
(2)cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询

以此,保证读到的一定不是不一致的脏数据。

PS:如果系统可以接收短时间的不一致,建议定时更新缓存就可以了。避免系统过于复杂。

三、缓存误用

一、多服务共用缓存实例

如上图:服务A和服务B共用一个缓存实例(不是通过这个缓存实例交互数据)

该方案存在的问题是:

1、可能导致key冲突,彼此冲掉对方的数据

可能需要服务A和服务B提前约定好了key,以确保不冲突,常见的约定方式是使用namespace:key的方式来做key。

2、不同服务对应的数据量,吞吐量不一样,共用一个实例容易导致一个服务把另一个服务的热数据挤出去

3、共用一个实例,会导致服务之间的耦合,与微服务架构的“数据库,缓存私有”的设计原则是相悖的

正确的部署方式是


如上图:各个服务私有化自己的数据存储,对上游屏蔽底层的复杂性。

二、调用方缓存数据

如上图,服务提供方缓存,向调用方屏蔽数据获取的复杂性。服务调用方,也缓存一份数据,先读自己的缓存,再决定是否调用服务(这个有问题)

该方案存在的问题是:
1、调用方需要关注数据获取的复杂性(耦合问题)
2、更严重的,服务修改db里的数据,淘汰了服务cache之后,难以通知调用方淘汰其cache里的数据,从而导致数据不一致(带入一致性问题)
3、有人说,服务可以通过MQ通知调用方淘汰数据,额,难道下游的服务要依赖上游的调用方,分层架构设计不是这么玩的(反向依赖问题)

三、缓存作为服务与服务之间传递数据的媒介

如上图:服务A和服务B约定好key和value,通过缓存传递数据服务A将数据写入缓存,服务B从缓存读取数据,达到两个服务通信的目的

多个服务关联同一个缓存实例,会导致服务耦合
(1)大家要彼此协同约定key的格式,ip地址等,耦合

(2)约定好同一个key,可能会产生数据覆盖,导致数据不一致

(3)不同服务业务模式,数据量,并发量不一样,会因为一个cache相互影响,例如service-A数据量大,占用了cache的绝大部分内存,会导致service-B的热数据全部被挤出cache,导致cache失效;又例如service-A并发量高,占用了cache的绝大部分连接,会导致service-B拿不到cache的连接,从而服务异常

四、使用缓存未考虑雪崩

常规的缓存玩法,如上图:
服务先读缓存,缓存命中则返回;缓存不命中,再读数据库

什么时候会产生雪崩?


如果缓存挂掉,所有的请求会压到数据库,如果未提前做容量预估,可能会把数据库压垮(在缓存恢复之前,数据库可能一直都起不来),导致系统整体不可服务。

如何应对潜在的雪崩?


提前做容量预估,如果缓存挂掉,数据库仍能扛住,才能执行上述方案。

否则,就要进一步设计。

常见方案一:高可用缓存


如上图:使用高可用缓存集群,一个缓存实例挂掉后,能够自动做故障转移。

常见方案二:缓存水平切分


如上图:使用缓存水平切分(推荐使用一致性哈希算法进行切分),一个缓存实例挂掉后,不至于所有的流量都压到数据库上。


总结

1、服务与服务之间不要通过缓存传递数据

2、如果缓存挂掉,可能导致雪崩,此时要做高可用缓存,或者水平切分

3、调用方不宜再单独使用缓存存储服务底层的数据,容易出现数据不一致,以及反向依赖

4、不同服务,缓存实例要做垂直拆分。

相关文章:

缓存最佳实践

目录 前言 一、Cache Aside(旁路缓存)策略 二、不一致解决场景及解决方案 一、数据库主从不一致 二、缓存与数据库不一致 三、问题分析 三、缓存误用 一、多服务共用缓存实例 二、调用方缓存数据 三、缓存作为服务与服务之间传递数据的媒介 四…...

Linux 终端命令之文件目录操作,对比Dos相关命令

目录 前言 基础命令(文件目录相关的) cd命令 【英文帮助】 【对应Dos命令】 pwd命令 【英文帮助】 【对应Dos命令】 ls命令 【英文帮助】 【对应Dos命令】 tree命令 【英文帮助】 【对应Dos命令】 mkdir命令 【英文帮助】 【对应Dos命令…...

C++学习第十八天----switch语句

1. ?:运算符 条件运算符,又叫三元运算符; 该运算符的通用格式为: expression1?expression2 :expression3; 意义是假如1为true,则整个条件表达式的值为2的值,否则为3的值&…...

基于poi生成excel模板并生成下拉选择框

直接上代码&#xff08;有注释&#xff09; public void downloadImportTemplate(HttpServletResponse response) {try {ServletOutputStream outputStream response.getOutputStream();//创建工作表XSSFWorkbook workbook new XSSFWorkbook();//标题行的标题List<String…...

Redis五种类型

Redis 基础类型 String 应用场景 缓存功能&#xff1a;string 最常用的就是缓存功能&#xff0c;会将一些更新不频繁但是查询频繁的数据缓存起来&#xff0c;以此来减轻 DB 的压力。 底层实现 如果字符串对象保存的是一个字符串值&#xff0c; 并且这个字符串值的长度大于…...

通过IP地址如何防范钓鱼网站诈骗?

随着互联网的普及和发展&#xff0c;钓鱼网站诈骗的风险日益增加。钓鱼网站通过伪装成合法网站&#xff0c;诱导用户输入个人敏感信息进而进行非法活动。IP地址作为网络通信的基本单位&#xff0c;可以在一定程度上帮助我们防范钓鱼网站诈骗。本文将探讨IP地址防范钓鱼网站诈骗…...

useEffect使用详解

useEffect是React中的一个钩子函数&#xff0c;用于处理副作用操作。副作用是指在组件渲染过程中&#xff0c;可能会对外部环境产生影响的操作&#xff0c;比如数据获取、订阅事件、操作DOM等。 useEffect接受两个参数&#xff1a;一个是副作用函数&#xff0c;另一个是依赖数…...

element-table的动态操作,自动以表格,动态新增行、列,删除行列

灵活的自定义表格行列以及增删改查的操作,右键选中列则是列的删除&#xff0c;效果如下 <template><div class"st-table"><div style"width: 100%"><el-button click"addRow()" type"primary" icon"CircleP…...

python--文件管理系统

文件系统管理项目说明文档 项目说明 基本任务 在内存中开辟一个空间作为文件存储器&#xff0c;在其上实现一个简单的文件系统退出这个文件系统时&#xff0c;需要该文件系统的内容保存到磁盘上&#xff0c;以便下次可以将其回复到内存中来 具体要求 文件存储空间管理可采取链…...

uniapp 微信小程序:RecorderManager 录音DEMO

uniapp 微信小程序&#xff1a;RecorderManager 录音DEMO 简介index.vue参考资料 简介 使用 RecorderManager 实现录音。及相关的基本操作。&#xff08;获取文件信息&#xff0c;上传文件&#xff09; 此图包含Demo中用于上传测试的服务端程序upload.exe&#xff0c;下载后用…...

__call__和__init__和__new__和__str__和__repr__

目录 一、__call__ 二、__init__和__new__ 三、__str__ 四、__repr__ python从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129328397?spm1001.2014.3001.5502 一、__call__ 对象后面加括号时&#xff0c;触发执行。注&#xff1a;构…...

设计模式--工厂模式(Factory Pattern)

一、 什么是工厂模式 工厂模式&#xff08;Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的接口&#xff0c;但是将对象的实例化过程推迟到子类中。工厂模式允许通过调用一个共同的接口方法来创建不同类型的对象&#xff0c;而无需暴露对…...

【Android】 No matching variant of com.android.tools.build:gradle:[版本号] was found

项目报错 No matching variant of com.android.tools.build:gradle:8.1.1 was found. The consumer was configured to find a library for use during runtime, compatible with Java 8, packaged as a jar, and its dependencies declared externally, as well as attribute …...

650V 1200V碳化硅二极管MOS管规格书参数,6A 8A 10A 15A 20A 封装TO220低VF电压 低内阻特性

650V碳化硅二极管6A 8A 15A提供样品 650V 40毫欧超结COOL MOS提供样品 650V 超结COOL MOS资料 国产替代 650V 1200V碳化硅二极管技术资料...

python基础—python6种基本数据类型及数据类型之间转换

文章目录 一、python标准数据类型&#xff08;一&#xff09;数字类型整型&#xff1a;int浮点型&#xff1a;flaot布尔型&#xff1a;bool复数类型&#xff1a;complex &#xff08;二&#xff09;字符串&#xff08;三&#xff09;列表类型&#xff08;四&#xff09;元组类型…...

Axure RP

Axure RP 简介下载安装汉化注册 简介 Axure RP&#xff08;Rapid Prototyping&#xff09;是一款交互式原型设计工具&#xff0c;用于创建高保真的交互式界面原型和线框图。它主要用于用户体验&#xff08;UX&#xff09;和用户界面&#xff08;UI&#xff09;设计&#xff0c…...

java使用ExcelExportUtil.exportBigExcel导出大文件(非分页)

网上看到很多使用这个方法处理的时候&#xff0c;大多使用的分页进行查询&#xff0c;但是当遇到特殊的产品需求&#xff0c;比如A类型数据&#xff0c;多条记录就显示多行&#xff0c;B类型的要求存在多条记录时&#xff0c;就进行汇总后只显示一条&#xff0c;这就导致无法使…...

PlantUML文本绘制类图

记录下文本绘制类图的语法 参考 https://juejin.cn/post/6844903731293585421 类的UML表示 使用UML表示一个类&#xff0c;主要由三部分组成。类名、属性、方法。其中属性和方法的访问修饰符用 - 、# 、 表示 private、protected、public。 如图所示&#xff0c;表示A类有一个…...

5分钟理解NPL算法 之 马尔可夫链 Markov Chain

马尔可夫链&#xff08;Markov Chain&#xff09; 马尔可夫链是一种简单的推理模型。用于描述受当前事件影响下的下一事件发生概率。在预测学科中广泛应用。例如股票预测、文字推理、路线推荐等。 他的核心思路是&#xff1a;假设事件顺序为: X 1 , X 2 , X 3 , . . . . . X…...

C#_GDI+ 绘图编程入门

官网提供相关API GDI 基本图形功能_drawing 高级二维和矢量图形功能_drawing2D GDI 图像处理功能_Imaging GDI 排版功能_text Windows 窗体应用程序提供打印功能_Printing 像素 构成图像的最小单位就是像素&#xff1b;屏幕上显示不管是位图或者矢量图&#xff0c;当描述…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

LeetCode - 199. 二叉树的右视图

题目 199. 二叉树的右视图 - 力扣&#xff08;LeetCode&#xff09; 思路 右视图是指从树的右侧看&#xff0c;对于每一层&#xff0c;只能看到该层最右边的节点。实现思路是&#xff1a; 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...