浏览器缓存控制讲解

缓存的作用

在你访问互联网中的任何资源其所产生的任何链路中的每一个节点几乎都会进行缓存,整个缓存体系和细节十分复杂。比如浏览器缓存,服务器缓存,代理服务器缓存,CDN缓存等。

但是缓存又十分重要,不可缺少,为什么这么说呢?

由于HTTP请求的链路漫长,环境复杂,把一些必要的信息在关键的节点进行缓存,下次请求的时候可以尽可能的复用数据,就可以节省一部分资源的消耗,减少HTTP请求应答的成本,节约带宽,加快响应速度。

那么,基于请求-应答模式的特点,缓存大致可以分为服务器缓存和客户端缓存,而服务器缓存经常与代理服务关联在一起。这次我们主要关注浏览器是如何进行缓存的。

缓存的形式

Memory Cache(内存缓存)

  • 时间间隔较短时, 重复请求相同的静态资源时, 一般情况下资源还在内存中, 那直接从内存中获取资源, 这种方式是最快的资源获取方式。
  • 通常情况下, 关闭 tab 页签时, 会清除内存中的资源。

Disk Cache(硬盘缓存)

  • 把资源存储到本机的硬盘上, 当再次需要数据时, 可以直接从硬盘上获取。
  • 缓存的时间一般会根据响应头的设置而定。

CDN 缓存

  • 当用户请求资源时会经过 CDN 服务器, 服务器会对可以缓存的资源进行缓存。
  • 当有另外的用户也需要这个资源时, CDN 服务器就可以直接返回资源, 不需要再找原服务器要了。

服务端缓存

  • 当一些数据需要服务端进行大量计算才能获得时, 服务端为了避免重复的处理影响服务器性能, 开发人员会对处理过的数据进行缓存, 下次再需要时直接取缓存中的数据。
  • 服务端缓存一般是一些接口的缓存处理,缓存时效可能根据数据的性质不同而不同。

缓存控制

假设,现在没有缓存,我们想象一下获取资源的方式是什么样的?

客户端请求资源,服务器返回资源,等下一次想要获取同样资源的时候,哪怕服务器的资源并没有更新,还是要重新走一遍网络请求,然后服务器返回资源的完整链路。

那如果有了缓存,在客户端第一次获取到请求的资源后,把资源缓存到本地,下次再去请求的时候,发现本地有这个资源,直接拿来用就好了,完全不用去走网络请求。

HTTP缓存都是从第二次请求开始的,可以概述如下:

  • 第一次请求资源时:

    服务器返回资源,并在 response header(响应头) 中回传资源的缓存策略;

  • 第二次请求时:

    浏览器判断这些请求参数,击中强缓存就直接200

    否则就把请求参数加到request header(请求头)中传给服务器,看是否击中协商缓存,击中则返回304,否则服务器会返回新的资源。

我们仔细的阅读一下这个简单的缓存资源请求流程,发现其中有几个重要的节点。

首先,服务器在返回该资源时,要标记该资源的有效期。然后,浏览器初次请求肯定是没缓存的,再次请求的时候,它要根据该资源的有效期来判断下一步该怎么办。

我们再简单一点,如果我们试图去获取缓存资源,其实是要看服务器的标记的。那么换句话说,服务器标记缓存资源,浏览器会验证该缓存资源的标记

浏览器每次发起请求时,会先在浏览器缓存中查找该请求的结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。如果缓存有效,则使用本地缓存;否则,则向服务器发起请求并携带缓存标识。

根据是否需向服务器发起请求,将缓存过程划分为两个部分:强制缓存协商缓存,强缓存优先于协商缓存。

浏览器缓存控制机制有两种:HTML Meta 标签 和 HTTP 头信息

使用HTML Meta 标签

HTML Meta标签是应用在HTML文件中的head头部分。

<meta http-equiv="Cache-Control" content="max-age=7200" />
// or 
<meta http-equiv="Cache-Control" content="no-cache" />

主要作用就是告诉浏览器此HTML页面不被缓存,每次访问都去服务器上下载。

使用上很简单,但这个是 IE 时代的私有属性,在 IE9 以前支持的,而现在主流的 Chrome / Firefox / Safari,包括 IE9 ~ IE11 都不支持。而且所有缓存代理服务器都不支持。因为代理不解析HTML内容本身。

所以我们常说的浏览器缓存还是通过HTTP头信息来控制缓存。

http-equiv 倒确实在 HTML 规范中有几个值可以设( content-security-policycontent-type、default-style、x-ua-compatiblerefresh),但都跟缓存无关,可以参阅文档 http-equiv。

使用HTTP头信息控制缓存

使用 HTTP 头信息进行缓存处理一般是通过设置相应的请求头/响应头实现的

常见的处理方式有 4 种

  • Cache-Control
  • Expires
  • Etag / If-None-Match
  • Last-Modified / If-Modified-Since

后3种是 http 1.0 就已经支持的,Cache-Control 是 http 1.1 支持的

缓存优先级: Cache-Control > Expires > Etag > Last-Modified

Cache-ControlExpires 首部用于指定缓存时间,Last-ModifiedETag 首部提供验证机制。

通过浏览器开发者工具我们可以看到,浏览器请求服务器静态资源的响应状态码主要就是下图的四种:

强缓存

通过 Expires(http1.0) 和 Cache-Control(http1.1) 两个响应头字段来控制,如果同时存在,则后者优先级高于前者。

服务器通知浏览器一个缓存时间,在缓存时间内,下次请求直接从本地缓存中读取资源而不发起请求。不在时间内,执行比较缓存策略。

  • 强缓存命中则直接读取浏览器本地的资源,在network中显示的是from memory cache或者from disk cache

  • Cache-Control是一个相对时间,用以表达自上次请求正确的资源之后的多少秒的时间段内缓存有效。

  • Cache-Control的出现是为了解决 Expires 在浏览器时间被手动更改导致缓存判断错误的问题。

Expires 缓存过期时间

Expires是http响应头字段,是一个绝对时间,用以告诉浏览器这个时间点之前发起请求可以直接从浏览器中读取数据,而无需发起请求。

  • Expires 字段为一个日期, 客户端请求该资源时将这个日期与客户端当前日期进行比对。
  • 如果当前时间小于这个日期, 则表示资源未过期, 使用缓存。
  • 如果当前时间大于这个日期, 则表示资源已过期, 客户端就会重新请求该资源。

优势:

  • HTTP 1.0 产物,可以在HTTP 1.0和1.1中使用,简单易用。
  • 以时刻标识失效时间,简易易理解

劣势:

  • 我们依赖客户端时间, 就会因为客户端的时间不准确导致判断错误,致使缓存失效。如果客户端时间早于服务器的时间, 会导致资源还未过期就重新请求。反之, 会导致客户端还在使用过期的旧资源。
Cache-Control 缓存控制

Cache-Control 通用消息头字段,被用于在 http 请求和响应中,通过指定指令来实现缓存机制。

缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

一般用在http响应中进行客户端缓存设置。

Cache-Control 是在 HTTP/1.1 中才有, 算是一个"新"的API (新是相对以前的处理, 因为现在已经有 HTTP3 了)

可选的参数有很多: max-age=no-storeno-cachemust-revalidateprivatepublic

在HTTP/1.1中增加的字段。属于相对时间。该字段表示资源缓存的最大有效时间,在该时间内,客户端不需要向服务器发送请求。比Expires多了很多选项设置。

  1. max-age

    可以用来表示过期时长,单位是秒。表达式是这样子的:

    Cache-Control: max-age=30
    

    该资源的有效期是30秒。

    这是一个相对时间,时间计算的起点是报文创建的时刻,也就是响应头Date字段的时间,是指资源离开服务器的时刻,而不是客户端收到报文的时候。

    换句话说,假设我们设置的时间是5秒,但是链路请求很长,花费了4秒的时间,那么缓存对于浏览器的有效时间只是1秒。

    那你可能会说,就这一秒有啥用啊~~假设你网站的访问量特别大,每秒有上百万的访问,那你可以想象到这仅仅一秒的时间能节省服务器多大的压力了吧。当然,只是举个极限的例子~

    我们先搞个max-age的小例子,看看缓存能否生效:

    res.setHeader("Cache-Control", "max-age=20");
    

    我们可以看到在响应头中返回了我们设置好的缓存字段:

    除了max-age这个最常用的属性以外,还有三个属性可以更精确的指示浏览器如何使用缓存

  2. no-store

    浏览器和代理服务器禁止缓存,每次只能去服务器请求最新资源。用于某些变化非常频繁的页面,比如秒杀页面。

    Cache-Control: no-store
    
  3. no-cache

    它的字面意思和no-store很容易搞混,实际上它的意思并不是不允许缓存,而是可以缓存,只是不使用强缓存,每次使用前必须要去服务器验证是否过期,是否有最新的版本。同时,代理服务器也不能对资源进行缓存。一般用于长期不变的资源,客户端只需要发送很小的信息跟服务端进行确认 (协商缓存验证)。

  4. must-revalidate

    no-cache又很相似,它的意思是缓存不过期就可以继续使用,但是过期了如果还想用就必须去服务器验证一下。

  5. private

    只有浏览器可以缓存

  6. public

    浏览器,服务器,代理服务器都可以缓存

Cache-Control时指定 no-cachemax-age=0, must-revalidate 表示客户端可以缓存资源,但每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。

Cache-Control: no-cache
# or
Cache-Control: max-age=0, must-revalidate

优势:

  • HTTP 1.1 产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。
  • Expires多了很多选项设置。

劣势:

  • 存在版本问题,到期之前的修改客户端是不可知的。
协商缓存

协商缓存有 2 组字段(不是两个),控制协商缓存的字段有:Last-Modified / If-Modified-Since(http1.0)和 Etag / If-None-Match(http1.1)。如果同时存在,则后者优先级高于前者。

Last-Modified / If-Modified-Since

Last-Modified很好理解,就是服务端返回最后一次修改文件的时间。

If-Modified-Since(客服端发起): 本地文件的修改时间(可以理解为上次请求返回的 Last-Modified的值),以后每次请求,请求头都会都带上。

如果强制缓存未命中,但协商缓存可用,需要第一次请求的时候,HTTP响应头中返回Last-ModifiedETag,当第二次请求的时候请求头会自动设置 If-Modified-Since 或者 If-None-Match 的值,服务端根据值,去决策验证是否命中协商缓存。

如果命中了协商缓存,那么服务器仅返回**304(不返回数据实体)**状态码,更新下资源的有效时间,使用本地缓存,因此在响应体体积上的节省是它的优化点。没有命中则返回200状态码。

优势:

  • 不存在版本问题,每次请求都会去服务器进行校验。

劣势:

  • 有新资源服务端却返回304,因为Last-Modified是以秒级为记录的,如果资源在1秒内改变的话,Last-Modified是无感的。
Etag / If-None-Match

ETag实体标签(Entity Tag) 的缩写。请求数据成功后, 服务端返回数据时响应头会携带 Etag,他是根据文件内容生成的一个字符串, 当文件变化时他也会跟着改变,一般都是 hash 生成的,是资源的唯一标识。

If-None-Match 是客户端发起的上次返回的 Etag 值。服务器会将 If-None-Match 的值与自己本地资源的 ETag 的值进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回最新数据。整个流程基本上和 (Last-Modified / If-Modified-Since) 很类似。

ETag主要是用来解决修改时间无法准确区分文件变化的问题。

比如,文件的修改时间是秒级甚至更短的,所以一秒内的新版本是无法区分的,再比如,一个文件定期更新,但有时内容没有变化,用修改时间就会以为发生了变化,发送给客户端以为是新的资源,浪费带宽。使用ETag就可以精确的识别资源的变动情况,让浏览器更有效地利用缓存。

ETag还有强弱之分。

ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个W/标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。至于是强还是弱,其实是由服务器自主决定的。弱也可以强,强也可以弱。

优势:

  • 可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
  • 不存在版本问题,每次请求都会去服务器进行校验。

劣势:

  • 计算ETag值需要性能损耗。
  • 分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时出现ETag不匹配的情况。

浏览器自身的缓存控制

客户端也可以控制缓存,客户端是怎么控制的呢?

当你点击浏览器的刷新按钮的时候,实际上,浏览器就在HTTP请求中夹带了Cache-Control:max-age=0,之前说过,max-age是生存时间,纪录的是从服务器生成的那一刻的有效期,而浏览器本地的资源,肯定不可能是0,所以当浏览器加上了max-age=0的时候,每次都会向服务器请求最新的资源。

而当你使用Control+F5强制刷新的时候其实是浏览器发了一个Cache-Control: no-cache。基本上和max-age=0差不多一样的效果,但是含义肯定是不一样的,就看服务器要怎么理解和处理不同的字段。

那么什么时候缓存才能派上用场呢?当我们点击浏览器的前进后退按钮的时候,就会直接从缓存中获取数据,另外,重定向的时候,也可能会使用到缓存。那这两类操作有啥区别呢。其实本质上来说,就是前进、后退、跳转这样的操作,浏览器不会私自加上Cache-Control字段,并且会清空If-Modified-SinceIf-None-Match字段,所以就会检查缓存,直接利用之前的资源,不再进行网络通信。

大家还可以自己尝试设置no-storeno-cache等字段。当你设置了no-store属性后,你会发现,哪怕使用浏览器的前进,后退按钮,每次也是重新从服务器获取资源,但是no-cachemax-age则会使用缓存。

哪些请求不会被缓存?

  • HTTP 信息头中包含 Cache-Control:no-cache,pragma:no-cache,或 Cache-Control:max-age=0 等告诉浏览器不用缓存的请求。

  • HTTP响应头中不包含Cache-Control/Expires,也不包含Last-Modified/ETag的请求无法被缓存。

  • POST 请求无法被缓存

部署时缓存的问题

我们不仅要缓存代码,还需要更新代码。如果静态资源名字不变,怎么让浏览器既能缓存,又能在有新代码时更新?

最简单的解决方式就是静态资源路径添加一个版本值。

版本不变就走缓存策略,版本变了就加载新资源。如下:

<script src="xx/xx.js?v=24334452"></script>

然而这种处理方式在部署时有问题。

背景:静态资源和页面是分开部署的

无论是先部署页面,还是先部署静态资源,在特定的阶段访问,就会出现页面和js文件不匹配进而报错。

这些问题的本质是以上的部署方式是“覆盖式发布”,解决方式是“非覆盖式发布”。

即用静态资源的文件摘要信息给文件命名,这样每次更新资源不会覆盖原来的资源,先将资源发布上去。

这时候存在两种资源,用户用旧页面访问旧资源,然后再更新页面,用户变成新页面访问新资源,就能做到无缝切换。

简单来说就是给静态文件名加hash值。

那如何实现呢?使用webpack持久化缓存

现在前端代码都用webpack之类的构建工具打包。浏览器有其缓存机制,想要既能缓存又能在部署时没有问题,需要给静态文件名添加hash值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

弹窗msvcp140_1.dll丢失的解决方法,超简单的方法分享

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是缺少某个文件的错误。最近&#xff0c;我在使用某些软件时&#xff0c;遇到了一个名为“msvcp140_1.dll”的错误提示。这个错误通常出现在运行某些程序时&#xff0c;由于缺少了msvcp140…

巧妙解决接口测试产生脏数据问题

测试数据创建后需要对其删除&#xff0c;不然可能产生脏数据&#xff0c;对开发和测试、生产环境造成一定影响。 其接口框架是基于Python&#xff0c;API规范基于REST。 产生原因 改进前&#xff1a;清除资源的操作放在每个正向测试用例里&#xff0c;没有在setUp和tearDown…

JMeter+Python 实现异步接口测试

当使用JMeter和Python来实现异步接口测试时&#xff0c;可以按照以下步骤进行操作&#xff1a; 1、安装JMeter和Java Development Kit&#xff08;JDK&#xff09;&#xff1a; 下载并安装JMeter&#xff08;https://jmeter.apache.org/download_jmeter.cgi&#xff09;和适用…

Error running Tomcat8: Address localhost:1099 is already in use 错误解决

摘要: 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in use 的错误&#xff0c;导致web项目无法运行。这篇 blog 介绍了解决办法。 有时候运行web项目的时候会遇到 Error running Tomcat8: Address localhost:1099 is already in …

Android11编译第七弹:串口文件读写

问题&#xff1a;需要对SIM卡进行管理&#xff0c;支持APP切换SIM卡。此功能需要访问串口文件&#xff0c;并且对串口文件进行读写。APP操作串口文件/dev/ttyUSB1时&#xff0c;串口文件打开失败。 2023-11-23 10:59:44.092 14264-14264 MULTI_CARD_SerialHandle com.wellnkio…

Python---函数定义时缺省参数(参数默认值)

缺省参数也叫默认参数&#xff0c;用于定义函数&#xff0c;为参数提供默认值&#xff0c;调用函数时可不传该默认参数的值&#xff08;注意&#xff1a;所有位置参数必须出现在默认参数前&#xff0c;包括函数定义和调用&#xff09;。 def user_info(name, age, gender男):pr…

DGL中NN模块的构造函数

上图引用自&#xff1a;dgl用户文档第三章(nn模块编写&#xff09; """构造函数完成以下几个任务&#xff1a; 1、设置选项。 2、注册可学习的参数或者子模块。 3、初始化参数。""" import torch.nn as nn from dgl.utils import expand_as_pai…

实例分割12篇顶会论文及代码合集,含2023最新

同学们&#xff0c;你们觉得视觉经典四个任务中哪个最难&#xff1f;我个人觉得是实例分割。 因为它既具备语义分割的特点&#xff0c;需要做到像素层面上的分类&#xff0c;也具备目标检测的一部分特点&#xff0c;即需要定位出不同实例&#xff0c;即使它们是同一种类。 但…

Docker 的基本概念和优势,以及在应用程序开发中的实际应用

Docker是一种轻量级的虚拟化技术&#xff0c;可以将应用程序及其依赖项打包在一个容器中&#xff0c;并在不同的计算机上运行。以下是Docker的基本概念和优势&#xff1a; 基本概念&#xff1a; 镜像&#xff08;Image&#xff09;&#xff1a;一个只读的文件&#xff0c;包含…

Unity中Shader的Standard材质解析(二)

文章目录 前言一、我们对 Standard 的 PBR 的 GI 进行解析1、我们先创建一个PBR的.cginc文件&#xff0c;用于整理用到的函数2、然后在Standard的Shader中引用该cginc文件 二、依次整理函数到该cginc文件中我们来看一下PBR中GI的镜面反射做了些什么 二、最终代码.cginc代码&…

98、Text2Room: Extracting Textured 3D Meshes from 2D Text-to-Image Models

简介 github 利用预训练的2D文本到图像模型来合成来自不同姿势的一系列图像。为了将这些输出提升为一致的3D场景表示&#xff0c;将单目深度估计与文本条件下的绘画模型结合起来&#xff0c;提出了一个连续的对齐策略&#xff0c;迭代地融合场景帧与现有的几何形状&#xff0…

【C/PTA —— 11.函数2(课外实践)】

C/PTA —— 11.函数2&#xff08;课外实践&#xff09; 一.函数题6-1 计算A[n]1/(1 A[n-1])6-2 递归实现顺序输出整数6-3 自然数的位数(递归版)6-4 分治法求解金块问题6-5 汉诺塔6-6 重复显示字符(递归版)6-7 显示平行四边形(右)(递归版) 二.编程题7-2 N阶楼梯上楼问题 一.函数…

React项目中发生空白但不报错的原因分析和解决?

文章目录 前言组件渲染问题状态管理问题异步操作问题代码错误但未抛出异常如果我们使用的是chorme浏览器的话&#xff0c;可以下载一个开发者工具&#xff0c;例如下图&#xff1a;代码审查使用调试工具日志和输出检查外部依赖异步操作终极大法&#xff0c;不到万不得已不可以使…

Twincat使用:EtherCAT通信扫描硬件设备链接PLC变量

EtherCAT通信采用主从架构&#xff0c;其中一个主站设备负责整个EtherCAT网络的管理和控制&#xff0c;而从站设备则负责在数据环网上传递数据。 主站设备可以是计算机、工控机、PLC等&#xff0c; 而从站设备可以是传感器、执行器、驱动器等。 EL3102:MDP5001_300_CF8D1684;…

【LM358AD运放方波振荡器可控输出幅值】2022-2-25

缘由仿真如何缩小方波振荡电路方波幅值?-有问必答-CSDN问答

C#,《小白学程序》第六课:队列(Queue)其二,队列的应用,编写《实时叫号系统》

医院里面常见的《叫号系统》怎么实现的&#xff1f; 1 文本格式 /// <summary> /// 下面定义一个新的队列&#xff0c;用于演示《实时叫号系统》 /// </summary> Queue<Classmate> q2 new Queue<Classmate>(); /// <summary> /// 《小白学程序…

笔记:内网渗透流程之信息收集

信息收集 首先&#xff0c;收集目标内网的信息&#xff0c;包括子网结构、域名信息、IP地址范围、开放的端口和服务等。这包括通过主动扫描和渗透测试工具收集信息&#xff0c;以及利用公开的信息源进行信息搜集。 本机信息收集 查看系统配置信息 查看系统详细信息&#xf…

单链表的反转?太细了哥们!细到离谱!

单链表的反转&#xff08;面试常出&#xff09;&#xff1a; ​ 单链表的反转&#xff0c;可以通过很多种方法实现。包括迭代法&#xff0c;递归法&#xff0c; 迭代法&#xff1a; 定义三个指针&#xff1a;prev、current和next&#xff0c;它们分别表示前一个节点、当前节点…

【黑马甄选离线数仓day04_维度域开发】

1. 维度主题表数据导出 1.1 PostgreSQL介绍 PostgreSQL 是一个功能强大的开源对象关系数据库系统&#xff0c;它使用和扩展了 SQL 语言&#xff0c;并结合了许多安全存储和扩展最复杂数据工作负载的功能。 官方网址&#xff1a;PostgreSQL: The worlds most advanced open s…

中国企业500强的排名也在不断变化。面对不确定性的挑战,企业如何应对?

随着全球经济的不断发展和变化&#xff0c;中国企业500强的排名也在不断变化。面对不确定性的挑战&#xff0c;企业如何应对&#xff1f;在本文中&#xff0c;挖数据平台将提供数据源探讨中国企业500强在应对不确定性方面的突围与变革。 一、数据挖掘与分析 从2006年到2023年&…