缓存的使用及常见问题的解决方案

用户通过浏览器向我们发送请求,这个时候浏览器就会建立一个缓存,主要缓存一些静态资源(js、css、图片),这样做可以降低之后访问的网络延迟。然后我们可以在Tomcat里面添加一些应用缓存,将一些从数据库查询到的数据放到缓存里面,下次的查询可以直接从缓存里面拿,这样做的目的可以减少数据库查询,提高查询效率。数据库里面的索引数据也可以缓存起来,当我们根据索引查询数据时可以在内存里面快速检索,不用每次都读取磁盘,提高了查询效率。数据库做一些排序或者表关联的话会使用cpu做运算。这时候就会用到cpu的多级缓存。一个web应用的任何环节都可以添加缓存,但是这个缓存不能滥用。缓存是一把双刃剑。

缓存的作用:

降低后端负载:对于不使用缓存的查询业务,每次请求都会从数据库(磁盘)里面查询数据在响应到前端,这一过程比较缓慢,而且对数据库压力比较大。使用缓存之后,请求之后,可以在Tomcat缓存里面直接拿去数据,响应比较迅速,而且降低了数据库的压力。

提高读写速率、降低响应时间:缓存一般通过Redis实现,Redis的读写效率非常高(微秒级别)。

缓存的成本:

数据一致性成本:数据本来是存储在数据库里面,将其缓存了一份放到内存里面,用户查询的时候可以查询内存(使用Redis)虽然减轻了数据库压力,但是如果数据库的数据发生改变。如果Redis还是旧的数据

代码维护成本:为了解决数据一致性的问题,给我们代码的维护成本带来了一定问题。会有很多复杂的业务代码。在数据一致性处理的过程中还会碰到缓存穿透、击穿等问题也会增加代码成本。

运维成本:为了避免缓存雪崩的问题、保证缓存高可用,缓存一般搭建集群的模式会增加运维成本

2.添加Redis缓存

缓存工作的模型:

未添加缓存的web应用,客户端发送的请求会直接从数据库查询,拿到数据库数据之后,再返回给客户端。

添加缓存的web应用,客户端的请求会先到我们Redis,如果Redis有我们需要的数据,就会直接返回给客户端,不会使用数据库,数据库的压力就会减轻。如果没有我们需要的数据(请求未命中)才会使用数据库,数据库将数据返回给我们客户端。未命中的话还会将数据添加到缓存里面,提高缓存的命中率。

这是一个查询商铺的缓存流程:

首先前端会提高一个商铺的id,然后从Redis里面查询商铺,判断是否查询到(判断是否命中),如果命中就返回商铺的信息,如果未命中我们会去查询数据库,假如数据库不存在就返回404,假如数据存在,我们会先将这个数据写入Redis,然后返回商铺信息。

3.缓存更新策略

内存淘汰:

不用自己维护,利用Redis的内存淘汰机制,当内存不足的时候自动淘汰部分数据下次查询更新缓存。可以在一定程度上保证数据一致性,如果需要更新的数据被淘汰,下次通过数据库查询,又会被重新写入Redis从而保证数据一致性,但是内存淘汰不能被我们控制,淘汰数据不确定,所以一致性差,但维护成本低。

超时剔除:

给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时跟新缓存。这种方式的一致性跟TTL设置的时间有关,时间越短一致性越高,这种一致性我们是可以控制的。但是数据库在我们设置的时间内发生改变的话数据还是会不一致,所以这种方式一致性一般,维护成本也很低。

主动更新:

编写业务逻辑,在修改数据库的同时,更新缓存。这种数据一致性比较好,但是维护成本很高。我们在写数据的crud的时候还得对缓存进行更新。

具体应该选择哪一种策略,需要考虑业务的场景:

低一致性需求:使用内存淘汰机制,例如店铺类型的查询缓存。

高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存。

主动更新的三种实现方式:

01:人工编码的方式,通过自己的代码来实现数据更新之后更新缓存。

02:缓存与数据库整合为一个服务,由服务来维护一致性,调用者调用该服务,无需关心缓存一致性。这种服务维护成本比较高,开发难度大。

03:写回:调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终的一致,就是crud直接对缓存操作,异步线程定期将缓存的数据对数据库进行更新。如果宕机数据就丢失了。

在日常的开发者我们通常使用方法01,这种方法的可控性最高,那我们通过这种方式实现数据一致性,需要考虑三个问题

问题一:删除缓存还是更新缓存?

更新缓存:每次更新数据库的操作同时更新缓存,无效写操作较多

删除缓存:更新数据库时让缓存失效,查询时再更新缓存

显然采用删除缓存比较合适

问题二:如何保证缓存与数据库的操作的同时成功与失败(保证原子性 )?

单体系统,将缓存与数据库操作放在一个事务

分布式系统,利用TCC等分布式事务方案

问题三:先操作缓存还是先操作数据库?

涉及到线程安全问题:

先删除缓存,再操作数据库(在不加锁的情况下)

异常情况:可能性较高(数据库操作时间相对与缓存读写比较长,这种异常概率较大)

在先删除缓存,再操作数据库过程中间,有别的线程查询操作,此时请求未命中,查询数据库,然而数据库还没有完成更新操作,查询的还是旧的数据,旧数据又被写入缓存。

先操作数据库,再删除缓存

异常情况:可能性较小(缓存的写入很快,数据库操作比较慢,这种异常情况发生概率较低)

在线程一查询的时候如果缓存失效,请求未命中,查询数据库,如何此时另一个线程删除缓存,之后线程一将查询的旧数据写入到缓存。

综上所述:选择方案二比较靠谱,即:先操作数据库,再删除缓存

4.缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

危害:

正因为数据不存在,所以就不会建立缓存,所有的请求都会直接查询数据库,如果被攻击者通过并行的方式不断请求这个不存在的数据很可能我们的数据库就会崩溃。这就是缓存穿透的危害。

常见解决缓存穿透的方案:

缓存空对象:

如果数据库查询不到,就将这个控制存储到缓存里面,下次请求就可以命中缓存了。

优点:实现简单、维护方便

缺点:额外内存消耗、可能造成短期的数据不一致

布隆过滤:

请求会先通过布隆过滤器,如果数据不存在则会拒绝请求,如果存在就会先查询Redis再查询数据库

原理:将数据库的数据基于哈希算法将哈希值以二进制的形式放到布隆过滤器里面,这种过滤器是一种概率问题,当布隆过滤器拒绝就一定不存在,如果放行的话数据不一定存在,所以说还是有穿透的风险。

优点:内存占用少、没有多余key

缺点:实现复杂、存在误判的可能

解决穿透问题的业务逻辑

解决穿透问题除了上面的两种方法外还有其他的办法:

增强id的复杂度,避免被猜测id规律,我们可以通过前端判断id是否符合规范,从而过滤掉一些恶意的请求

做好数据的基础格式校验

加强用户权限校验(做访问次数的限流)

做好热点参数的限流

5.缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务器宕机,导致大量请求到达数据库,带来巨大压力。

解决方案:

大量的缓存key同时失效:

给不同的key的TTL添加随机值,通过random随机数将key失效时间控制到一段时间内,避免大量key同时失效。

Redis服务器宕机

利用Redis集群提高服务的可用性(Redis哨兵机制):通过集群主从的思想,主机宕机可以通过别的机器提高缓存服务,根据缓存的数据副本也不会导致数据丢失。

给缓存业务添加降级限流策略:对于一些查询服务,通过快速失效的方式,减少对数据库的压力,舍弃一些服务,从而保全数据库的健康。

给业务添加多级缓存:利用浏览器缓存、Tomcat的缓存,如果Redis宕机,可以由这些缓存缓解数据库压力,避免大量的查询落到数据库上。

6.缓存击穿

缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会瞬间给数据库造成巨大的冲击。

缓存重建业务较复杂:如果我们需要缓存的对象业务比较复杂,需要通过多个表关联查询得到的数据,再去做缓存。这个过程时间相对比较久,这一时间段大量的请求落到数据库给数据库造成巨大冲击。

常见的解决方案:

互斥锁:

添加互斥锁,线程一在重新写入缓存的过程中,其他线程会获取互斥锁,获取失败会进入休眠,并且重新查询查看是否命中。也就是说在大量请求中只会由一个线程去查询数据库并构建缓存,其他线程只会进入阻塞状态。等待缓存重建成功。

优点:简单粗暴、保证一致性

缺点:会造成大量的线程等待、可能有死锁风险

逻辑过期:

存储缓存的时候不设置TTL,而是存储一个字段作为过期时间(当前时间+过期时间),所以是逻辑过期

为了避免获取锁后其他线程等待时间过长,他不是自己做查询和重建,而是开启一个新的线程来做,并且释放锁,自己会返回一个过期的数据,在此期间其他线程如果请求也会获取锁,获取失败会直接返回过期数据,避免线程的等待。

优点:线程无需等待、性能较好

缺点:不能保证一致性、有额外内存消耗、实现复杂

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

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

相关文章

wsl中ollama不能使用gpu加速

之前还能有gpu加速的, 突然一次发现不能加速了, 启动之后发现只能用cpu了 log time2024-04-19T00:05:08.21308:00 levelINFO sourceimages.go:806 msg"total blobs: 80" time2024-04-19T00:05:08.24808:00 levelINFO sourceimages.go:813 msg"tota…

Flask:URL与视图的映射

默认端口号80、443 blog_id 限制数据类型的话(int) 除此之外别的数据类型也可以,或者多个(用any) /book/list?page6

骑砍2霸主MOD开发(5)-游戏事件

一.MissionBehavior Mission任务中发生的事件,AgentSpawn,AgentRemove,BeforeMissionStart等统称为MissionBehavior. 通过在Mission中添加属于自己的MissionBehavior实现对游戏任务事件的捕捉 <1.在MBSubModuleBase中重写OnBeforeMissionBehaviorInitialize(Mission mission…

【笔记】ASP.NET Core Web API之Token验证

在实际开发中经常需要对外提供接口以便客户获取数据&#xff0c;由于数据属于私密信息&#xff0c;并不能随意供其他人访问&#xff0c;所以就需要验证客户身份。那么如何才能验证客户的身份呢&#xff1f;一个简单的小例子&#xff0c;简述ASP.NET Core Web API开发过程中&…

Git学习笔记(三)Git分支

Git分支是Git中非常重要的一个概念&#xff0c;无论是个人开发还是多人协作中&#xff0c;分支都起着至关重要的作用。几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离 开来进行重大的Bug修改、开发新的功能&#xff0c;以免影响…

笨蛋学C++【C++基础第二弹】

C基础第二弹 2.C运算2.1运算符2.1.1算术运算符2.1.2关系运算符2.1.3逻辑运算符2.1.4位运算符2.1.5赋值运算符2.1.6杂项运算符2.1.7运算符优先级2.1.8注意 3.C循环3.1Cwhile循环3.1.1语法 3.2Cfor循环3.2.1基于范围的for循环方式13.2.2基于范围的for循环方式23.2.3基于范围的for…

Linux驱动开发笔记(零)驱动基础知识及准备

文章目录 前言一、Liunx、MCU和FPGA编程的区别二、Linux内核模块1. 什么是内核模块2. 内核模块的代码架构3. 头文件4. 模块参数5. makefile说明 三、 驱动程序设计思路1. 基本步骤2. 设备号3. 数据结构3.1 file_operations3.2 file3.3 inode3.4 哈希表3.5 cdev结构体3.6 kobj_m…

[Linux][进程信号][一][信号基础][如何产生信号]详细解读

目录 0.前言预备1.系统定义的信号列表2.核心转储 -- Core Dump 1.信号基础1.信号概念2.信号处理方式概览3.理解信号如何被保存4.信号发送的本质 2.如何产生信号&#xff1f;1.终端按键产生信号2.系统调用接口1.kill()2.raise()3.abort()4.如何理解&#xff1f; 3.由软件条件产生…

将闲置的windows硬盘通过smb共享的方式提供给mac作为时间机器备份

1.windows端&#xff0c;开启smb共享 自行解决 2.mac端 磁盘工具-文件-新建映像-空白映像 假设你的名字为&#xff1a;backup 大小&#xff1a;350GB&#xff08;自己修改&#xff09; 格式&#xff1a;MacOS扩展&#xff08;日志式&#xff09; 分区&#xff1a;单个分区-A…

C# 图像旋转一定角度后,对应坐标怎么计算?

原理分析 要计算图像内坐标在旋转一定角度后的新坐标&#xff0c;可以使用二维空间中的点旋转公式。假设图像的中心点&#xff08;即旋转中心&#xff09;为 (Cx, Cy)&#xff0c;通常对于正方形图像而言&#xff0c;中心点坐标为 (Width / 2, Height / 2)。给定原坐标点 (X, …

开发与产品的战争之自动播放视频

开发与产品的战争之自动播放视频 起因 产品提了个需求&#xff0c;对于网站上的宣传视频&#xff0c;进入页面就自动播放。但是基于我对chromium内核的一些浅薄了解&#xff0c;我当时就给拒绝了: “浏览器不允许”。&#xff08;后续我们浏览器默认都是chromium内核的&#…

【深度学习】Vision Transformer

一、Vision Transformer Vision Transformer (ViT)将Transformer应用在了CV领域。在学习它之前&#xff0c;需要了解ResNet、LayerNorm、Multi-Head Self-Attention。 ViT的结构图如下&#xff1a; 如图所示&#xff0c;ViT主要包括Embedding、Encoder、Head三大部分。Class …

K8s: 运行Pod时的root用户和非root用户的安全相关配置

关于 root 用户 1 &#xff09;概述 docker 容器运行起来&#xff0c;默认是 root 用户这样运行起来后&#xff0c;基本不会遇到权限相关问题带来的问题是: 权限过大&#xff0c;被攻击后会遇到严峻挑战基于这个问题&#xff0c;K8s提出了特权用户的概念在容器启动时&#xff…

Javascript 中 package.json 和 package-lock.json 有什么区别呢

package.json 和 package-lock.json 文件都是 Node.js 项目中的重要组成部分&#xff0c;但它们的作用不同。以下是它们之间的主要区别&#xff1a; 作用: package.json: 这个文件主要用于管理和记录关于项目的元数据&#xff0c;包括项目的名称、版本、作者、依赖项、脚本和…

OpenHarmony鸿蒙南向开发案例:【智能燃气检测设备】

样例简介 本文档介绍了安全厨房案例中的相关智能燃气检测设备&#xff0c;本安全厨房案例利用轻量级软总线能力&#xff0c;将两块欧智通V200Z-R/BES2600开发板模拟的智能燃气检测设备和燃气告警设备组合成。当燃气数值告警时&#xff0c;无需其它操作&#xff0c;直接通知软总…

VOS3000加装登陆服务器安全防护系统有用吗

VOS3000是一款专业的软交换系统&#xff0c;它主要用于中小规模的VoIP运营业务&#xff0c;包括运营费率设定、套餐管理&#xff0c;账户管理、业终端管理、网关管理、数据查询、卡类管理、号码管理、系统管理等功能1。而关于加装登陆服务器安全防护系统是否有用&#xff0c;这…

Multi-granularity Correspondence Learning from Long-term Noisy Videos--论文笔记

解决在视频语言学习中&#xff0c;如何有效地从长期&#xff08;long-term&#xff09;且带有噪声的视频数据中学习时间上的对应关系&#xff08;temporal correspondence&#xff09;。 噪声对应学习&#xff08;Noisy Correspondence Learning&#xff09;是指在处理视频和文…

2.4 Web容器配置:Tomcat

2.4 Web容器配置 2.4.1Tomcat配置1.常规配置2. HTTPS配置 *********** 2.4.1Tomcat配置 1.常规配置 在SpringBoot项目中&#xff0c;可以内置Tomcat、Jetly、Undertow、Netty等容器。 当开发者添加了spring-boot-starter-web依赖之后&#xff0c;默认会使用Tomcat作为Web容器…

基于Springboot+Vue的Java项目-网上点餐系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

【EdgeBox-8120AI-TX2】Ubuntu18.04 + ROS_ Melodic + 星秒PAVO2单线激光 雷达评测

大家好&#xff0c;我是虎哥&#xff0c;好久不见&#xff0c;最近这断时间出现了一点变故&#xff0c;开始自己创业&#xff0c;很多事需要忙&#xff0c;所以停更了大约大半年&#xff0c;最近一切已经理顺&#xff0c;所以我还是抽空继续我之前的FLAG&#xff0c;CSDN突破十…