【前端】深入浅出缓存原理
缓存的基本原理
对于前端来说,缓存主要分为浏览器缓存(比如 localStorage、sessionStorage、cookie等等)以及http缓存,也是本文主要讲述的。
当然叫法也不一样,比如客户端缓存大概包括浏览器缓存和http缓存
所谓http缓存,顾名思义,是将某一次的响应结果保存在客户端(比如浏览器)中,而后续的请求仅需要从缓存中读取即可,极大的降低了服务器的处理压力。
http缓存的原理如下:
这只是一个简易的原理图,实际情况可能有差异
这里就设计到一个缓存策略的问题,这些问题包括:
- 哪些资源需要加入到缓存,哪些不需要?
- 缓存的时间是多久呢?
- 如果服务器的资源有改动,客户端如何更新缓存呢?
- 如果缓存过期了,可是服务器上的资源并没有发生变动,又该如何处理呢?
- …
要回答这些问题,就必须要清楚http
中关于缓存的协议
理解了http的缓存协议,自然就能回答上面的问题了。
来自服务器的缓存指令
当客户端发出一个get
请求到服务器,服务器可能有以下的内心活动:「你请求的这个资源,我很少会改动它,干脆你把它缓存起来吧,以后就不要来烦我了」
为了表达这个美好的愿望,服务器在响应头中加入了以下内容:
Cache-Control: max-age=3600
ETag: W/"121-171ca289ebf"
Date: Thu, 30 Apr 2020 12:39:56 GMT
Last-Modified: Thu, 30 Apr 2020 08:16:31 GMT
这个响应头表达了下面的信息:
Cache-Control: max-age=3600
,我希望你把这个资源缓存起来,缓存时间是3600秒(1小时)ETag: W/"121-171ca289ebf"
,这个资源的编号是W/"121-171ca289ebf"
Date: Thu, 30 Apr 2020 12:39:56 GMT
,我给你响应这个资源的服务器时间是格林威治时间2020-04-30 12:39:56
Last-Modified: Thu, 30 Apr 2020 08:16:31 GMT
,这个资源的上一次修改时间是格林威治时间2020-04-30 08:16:31
这个美好的缓存愿望,就这样通过响应头传递给客户端了
如果客户端是其他应用程序,可能并不会理会服务器的愿望,也就是说,可能根本不会缓存任何东西。
但是凑巧客户端是一个浏览器,它和服务器一直以来都是相亲相爱的小伙伴,当它看到服务器的这个响应头表达的美好愿望后,立即忙起来:
- 浏览器把这次请求得到的响应体缓存到本地文件中
- 浏览器标记这次请求的请求方法和请求路径
- 浏览器标记这次缓存的时间是3600秒
- 浏览器记录服务器的响应时间是格林威治时间
2020-04-30 12:39:56
- 浏览器记录服务器给予的资源编号
W/"121-171ca289ebf"
- 浏览器记录资源的上一次修改时间是格林威治时间
2020-04-30 08:16:31
这一次的记录非常重要,它为以后浏览器要不要去请求服务器提供了各种依据。
来自客户端的缓存指令
当客户端收拾好行李,准备再次请求GET /index.js
时,它突然想起了一件事:我需要的东西在不在缓存里呢?
此时,客户端会到缓存中去寻找是否有缓存的资源
寻找的过程如下:
- 缓存中是否有匹配的请求方法和路径?
- 如果有,该缓存资源是否还有效呢?
以上两个验证会导致浏览器产生不同的行为
要验证是否有匹配的缓存非常简单,只需要验证当前的请求方法GET
和当前的请求路径/index.js
是否有对应的缓存存在即可
如果没有,就直接请求服务器,就和第一次请求服务器时一样,这种情况没有什么好讨论的
关键在于验证缓存是否有效
如何验证呢?
非常简单,就是把max-age + Date
,得到一个过期时间,看看这个过期时间是否大于当前时间,如果是,则表示缓存还没有过期,仍然有效,如果不是,则表示缓存失效。
缓存有效
当浏览器发现缓存有效时,完全不会请求服务器,直接使用缓存即可得到结果
此时,如果你断开网络,会发现资源仍然可用
这种情况会极大的降低服务器压力,但当服务器更改了资源后,浏览器是不知道的,只要缓存有效,它就会直接使用缓存
缓存无效
当浏览器发现缓存已经过期,它并不会简单的把缓存删除,而是抱着一丝希望,想问问服务器,我这个缓存还能继续使用吗?
于是,浏览器向服务器发出了一个带缓存的请求
所谓带缓存的请求,无非就是加入了以下的请求头:
If-Modified-Since: Thu, 30 Apr 2020 08:16:31 GMT
If-None-Match: W/"121-171ca289ebf"
它们表达了下面的信息:
If-Modified-Since: Thu, 30 Apr 2020 08:16:31 GMT
,亲,你曾经告诉我,这个资源的上一次修改时间是格林威治时间2020-04-30 08:16:31
,请问这个资源在这个时间之后有发生变动吗?If-None-Match: W/"121-171ca289ebf"
,亲,你曾经告诉我,这个资源的编号是W/"121-171ca289ebf
,请问这个资源的编号发生变动了吗?
其实,这两个问题可以合并为一个问题:快说!资源到底变了没有!
之所以要发两个信息,是为了兼容不同的服务器,因为有些服务器只认If-Modified-Since
,有些服务器只认If-None-Match
,有些服务器两个都认
目前的很多服务器,只要发现
If-None-Match
存在,就不会去看``If-Modified-Since`
If-Modified-Since
是http1.0
版本的规范,If-None-Match
是http1.1
的规范
此时,问题又抛给了服务器,接下来,就是服务器的表演时间了
服务器可能会产生两个情况:
- 缓存已经失效
- 缓存仍然有效
如果是第一种情况——缓存已经失效,那么非常简单,服务器再次给予一个正常的响应(响应码200
带响应体),同时可以附带上新的缓存指令,这就回到了上一节——来自服务器的缓存指令
这样一来,客户端就会重新缓存新的内容
但如果服务器觉得缓存仍然有效,它可以通过一种极其简单的方式告诉客户端:
- 响应码为
304 Not Modified
- 无响应体
- 响应头带上新的缓存指令,见上一节——来自服务器的缓存指令
这样一来,就相当于告诉客户端:「你的缓存资源仍然可用,我给你一个新的缓存时间,你那边更新一下就可以了」
于是,客户端就继续happy的使用缓存了
这样一来,可以最大程度的减少网络传输,因为如果资源还有效,服务器就不会传输消息体
它们完整的交互过程如下:
细节
上面描述了客户端缓存的基本概念和过程
但其中仍然有不少细节值得我们注意
Cache-Control
在上述的讲解中,Cache-Control
是服务器向客户端响应的一个消息头,它提供了一个max-age
用于指定缓存时间。
实际上,Cache-Control
还可以设置下面一个或多个值:
public
:指示服务器资源是公开的。比如有一个页面资源,所有人看到的都是一样的。这个值对于浏览器而言没有什么意义,但可能在某些场景可能有用。本着「我告知,你随意」的原则,http
协议中很多时候都是客户端或服务器告诉另一端详细的信息,至于另一端用不用,完全看它自己。private
:指示服务器资源是私有的。比如有一个页面资源,每个用户看到的都不一样。这个值对于浏览器而言没有什么意义,但可能在某些场景可能有用。本着「我告知,你随意」的原则,http
协议中很多时候都是客户端或服务器告诉另一端详细的信息,至于另一端用不用,完全看它自己。no-cache
:告知客户端,你可以缓存这个资源,但是不要直接使用它。当你缓存之后,后续的每一次请求都需要附带缓存指令,让服务器告诉你这个资源有没有过期。见:「来自客户端的缓存指令 - 缓存无效」no-store
:告知客户端,不要对这个资源做任何的缓存,之后的每一次请求都按照正常的普通请求进行。若设置了这个值,浏览器将不会对该资源做出任何的缓存处理。max-age
:不再赘述
比如,Cache-Control: public, max-age=3600
表示这是一个公开资源,请缓存1个小时。
Expire
在http1.0
版本中,是通过Expire
响应头来指定过期时间点的,例如:
Expire: Thu, 30 Apr 2020 23:38:38 GMT
到了http1.1
版本,已更改为通过Cache-Control
的max-age
来记录了。
记录缓存时的有效期
浏览器会按照服务器响应头的要求,自动记录缓存到本地文件,并设置各种相关信息
在这些信息中,有效期尤为关键,它决定了这个缓存可以使用多久
浏览器会根据服务器不同的响应情况,设置不同的有效期
具体的有效期设置,按照下面的流程进行:
例如,当max-age
设置为0时,缓存立即过期
虽然立即过期,但缓存仍然被记录下来,后续的请求通过缓存指令发送到服务器,来确认资源是否被更改。
因此,Cache-Control: max-age=0
类似于Cache-Control: no-cache
Pragma
这是http1.0
版本的消息头
当该消息头出现在请求中时,是向服务器表达:不要考虑任何缓存,给我一个正常的结果。
在http1.1
版本中,可以在请求头中加入Cache-Control: no-cache
实现同样的含义。
是的,
Cache-Control
可以出现在请求头中
在Chrome
浏览器中调试时,如果勾选了Disable cache
,则发送的请求中会附带该信息
Vary
有的时候,是否有缓存,不仅仅是判断请求方法和请求路径是否匹配,可能还要判断头部信息是否匹配。
此时,就可以使用Vary
字段来指定要区分的消息头
比如,当使用GET /personal.html
请求服务器时,请求头中cookie
的值不一样,得到的页面也不一样
如果还按照之前的做法,仅仅匹配请求方法和请求路径,如果cookie
变动,你可能得到的仍然是之前的页面。
正确的做法如下:
使用版本号或hash
如果你是一个前端工程师,使用过vue
或其他基于webpack
搭建的工程
你会发现打包的结果中很多文件名类似于这样:
app.68297cd8.css
文件的中间部分使用了hash
值
这样做的好处是,可以让客户端大胆的、长时间的缓存该文件,减轻服务器的压力
当文件改动后,它的文件hash
值也会随之而变,比如变成了app.446fccb8.css
这样一来,客户端要请求新的文件时,就会发现路径从/app.68297cd8.css
变成了app.446fccb8.css
,由于之前的缓存路径无法匹配到,因此就会发送新的请求来获取新资源了。
以上是现代流行的做法。
而在古老的年代,还没有构建工具出现时,人们使用的办法是在资源路径后面加入版本号来获取新版本的文件
比如,页面中引入了一个css资源app.css
,它可能的引入方式是:
<link href="/app.css?v=1.0.0">
这样一来,缓存的路径是/app.css?v=1.0.0
当服务器的版本发生变化时,可以给予新的版本号,让html中的路径发生变动
<link href="/app.css?v=1.0.1">
由于新的路径无法命中缓存,于是浏览器就会发送新的普通请求来获取这个资源
总结
最后,通过客户端和服务器两位大佬的视角,来总结一下以上内容
服务器视角
服务器无法知道客户端到底有没有像浏览器那样缓存文件,它只管根据请求的情况来决定如何响应
很多后端语言搭建的服务器都会自带自己的默认缓存规则,当然也支持不同程度的修改
浏览器视角
浏览器在发出请求时会判断要不要使用缓存
当收到服务器响应时,会自动根据缓存指令进行处理
相关文章:

【前端】深入浅出缓存原理
缓存的基本原理 对于前端来说,缓存主要分为浏览器缓存(比如 localStorage、sessionStorage、cookie等等)以及http缓存,也是本文主要讲述的。 当然叫法也不一样,比如客户端缓存大概包括浏览器缓存和http缓存 所谓htt…...

单调栈图文详解(附Java模板)
🍏🍐🍊🍑🍒🍓🫐🥑🍋🍉🥝 啥是"单调栈",它能解决什么样的问题? 文章目录🦩单调栈的概念&a…...

彻底理解Session、Cookie、Token,入门及实战
文章目录Session Cookie的使用Token的使用Session Cookie的使用 1. Session存储数据 HttpSession session request.getSession(); //Servlet底层通过的SESSIONID,获取Session对象。 session.setAttribute("loginTime",new Date()); out.println(&q…...

为什么运营商大数据可以精准获客?
“获客难”,“获客成本高”,一直是困扰企业的大问题,身边的许多朋友在吐槽客户的意向度不高,总是无法成交,员工非常积极主动去跟踪客户了,但始终事倍功半,这就像是老人们常说的一句老话“热脸贴…...

【数据结构】栈的实现
💯💯💯 本篇主要利用数组来实现栈,对于栈的各种操作都作详细介绍,压栈,出栈以及获取栈中元素的操作都是学习栈的必备知识,快来学起来吧!!!©Ⅰ.栈的概念及…...

【链表OJ题(六)】链表分割
📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:数据结构 🎯长路漫漫浩浩,万事皆有期待 文章目录链表OJ题(六)1. 链表…...
C++类中的三大函数(构造,析构,拷贝)
下面一段话与大家共勉:每个人的一生都会遇到很多边界,有些边界可以突破,有些则不能。那些无法突破的边界就是你的极限,而划分边界的标准就是“阈值”。每次突破阈值之后,人生轨迹就会发生剧烈变化,其间需要…...

【2024考研】计算机考研,4轮复习时间安排
文章目录🎨第1轮复习(暑假前&系统课)英语1/2数学1/2专业课408🎨第2轮复习(开学前&真题)英语1/2试卷数学1/2试卷专业课408试卷🎨第3轮复习(报名前&政治)政治试…...
(十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
系列文章: python网络爬虫专栏 目录 序言 本节学习目标 特别申明 4.7 使用BeautfulSoup解析h...

【经验】项目管理:瀑布式、Scrum
1、瀑布式开发 流程关键词关键人员输出立项简述、周期、预算领导立项申请表、立项评审表策划计划项目经理、QA、CM各种计划书(项目、配置、测试等),评审需求功能项目经理功能列表、需求规格书、需求开发计划等,评审设计UML开发设…...

Learning C++ No.17【STL No.7】双端队列
引言: 北京时间:2023/3/17/7:18,刚刚快乐的早锻炼回来(不对 ,应该说回来有一会了),因为此时我已经吃完早饭,洗过澡了;现在回想起上学期,就算是第二天需要晨跑…...
Snackbar
1.简介 位于底部的提示View 支持侧滑消失 同一时间只有一个 不支持跨Activity展示 国内使用率很低 2.基础使用 2.1 基本展示 Snackbar.make(view, "Content", Snackbar.LENGTH_LONG).show()2.2 设置点击事件 注意不设置点击事件回调,点击按钮的文字不…...

HummerRisk 使用教程:主机检测
1. 概述 HummerRisk 是开源的云原生安全平台,以非侵入的方式解决云原生环境的安全和治理问题。核心能力包括混合云的安全治理和容器云安全检测。 本文将介绍HummerRisk中的主机检测部分功能,包括如何管理主机、管理凭证,以及使用主机检测规…...

【Arduino无线气象站项目】
【Arduino无线气象站项目】 1. 概述2. Arduino无线气象站电路图3. 定制设计电路板4. Arduino无线气象站代码5. 总结1. 概述 使用DHT22传感器测量室外温度和湿度,并使用NRF24L01收发器模块将这些数据无线发送到室内机。在室内机,还有另一个用于测量室内温度和湿度的DHT22传感…...

HTTP详解
一,什么是HTTPHTTP(全称为“超文本传输协议”),是一种应用非常广泛的应用层协议,之前在《初识网络原理》的博客(初识网络原理_徐憨憨!的博客-CSDN博客)中,有详细讲解过TCP/IP五层模型,其中应用层描述了数据…...

cpufreq--处理器功耗控制
cpu 功耗控制 参考框架: cpufreq 框架。 cpufreq 框架提供 cpu 功耗管理接口,以及功耗管理方案。 用户可以通过功耗管理接口(以文件形式提供)来选择管理方案,并设置相关参数。 管理方案的实现则由具体的驱动来完成。…...

做技术,最忌讳东张西望
又好长时间没更新,研二了,忙着做实验、写论文、发论文,再加上给我导做一些事情(都习惯了,以前很不爽的事情,现在居然能这么平静的说出来)。 但这不是我今天说的重点,而是另外一件事…...
Oracle 常见报错问题汇总
Oracle 常见报错问题汇总 报错:ORA-01017: invalid username/password; logon denied报错:ORA-01031: insufficient privileges报错:"ORA-01034: ORACLE not available" 和 "ORA-27101: shared memory realm does not exist"报错:“ORA-00119: invalid…...

单片机连接有人云上传数据
首先采用有人物联网的模块 ,连接有人云平台服务器 看云平台相关配置配置连接设备在线后 添加设备添加设备完成后 添加变量模板 变量模板的添加方式如下 :本次采用的是标准的MODbus 协议添加一个温度变量温度变量如下显示云平台 下发数据 采集01 03 00 00…...
系统集成项目管理工程师:第18章项目风险管理学习笔记
第18章项目风险管理 一、目录 18.1 风险概述 18.1.1 风险的定义 18.1.2 风险的分类 18.1.3 风险的性质 18.2 项目风险管理 18.3 规划风险管理 18.3.1 规划风险管理的输入 18.3.2 规划风险管理的工具与技术 18.3.3 规划风险管理的输出 18.4 识别风险...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

GitFlow 工作模式(详解)
今天再学项目的过程中遇到使用gitflow模式管理代码,因此进行学习并且发布关于gitflow的一些思考 Git与GitFlow模式 我们在写代码的时候通常会进行网上保存,无论是github还是gittee,都是一种基于git去保存代码的形式,这样保存代码…...