整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

浏览器强缓存和弱缓存的效果演示

文对强缓存和弱缓存进行讲解:

为了展示强缓存和弱缓存

第一步:Ctrl + shift + delete将缓存给清除

将浏览器的数据给清空一下

这里的数据大小是83.4kB

在响应头里面,会存在着ETag和Last-Modified资料

第一请求和第二请求,这里多了一个If-Modified,在弱缓存当中,从缓存中获取数据,检验匹配是经过多次匹配,出现304指令,

200或者304指令意味着没有发生变化

如果是强缓存这里根本不会产生数据,强缓存的特性是存储在用户的本地

浏览器缓存相关指令

expires指令

expires:该指令用来控制页面缓存的作用。可以通过该指令控制HTTP应 答中的“Expires"和”Cache-Control"


expires 有两种语法,一种是time,time是时间值,它默认是以秒来计数的

no-cache,无论缓存有没有过期,都需要发送请求,检验缓存有没有过期

max-age与expires的意思差不多,只不过有的没有这个配置,

epoch会指定1970的时间,max是2037年的时间

具体配置流程:

vim ngnix.conf

为了方便对比,先将请求头信息进行复制

之后在conf配置文件下配置

location ~ .*\.(html|js|css|png) $ {

}

以html和js和css等结尾的资源,都走这个

写成expiress 1000;的意思是缓存1000s

检验语法,重新加载

重新对jQuery.js中进行查看

现在相应头多了一块时间max-age=1000秒和Expires:Mon.27(Expires的意思是服务器的时间)

如果将配置expiress修改为-1000,改为了负数,在请求头中出现了no-cache

如果将该值指定为max

它会出现一些时间,默认是以秒来分割的

这个max的值是10年的意思,意思是最大的缓存时间可以已达到10年

add_header指令

add_header指令是用来添加指定的响应头和响应值。

语法 add_header name value [always];

位置存储在http、server、location..

如果想要到web服务器直接获取,不缓存,用no-store

不缓存的写法配置,在配置文件中添加add_header Cache-Control no-store

现在就实现了弱缓存,第一次访问了304kb数据,第二次就是83.5kb,实现了弱缓存了

ttp缓存:

Web 缓存是可以自动保存常见文档副本的 HTTP 设备。当 Web 请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这 个文档。

使用缓存主要有如下几个优点:

  • 缓存减少了冗余的数据传输,节省了你的网络费用。
  • 缓存缓解了网络瓶颈问题。不需要更多的带宽就能够更快的加载页面。
  • 缓存降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。
  • 缓存降低了距离时延,因为从较远的地方加载页面会更慢一些。

缓存的处理步骤:

一个缓存从接收到请求到做出响应,大概可以分为如下7个步骤:

  1. 接收——缓存从网络中读取抵达的请求报文。
  2. 解析——缓存对报文进行解析,提取出URL和各种首部。
  3. 查询——缓存查看是否有本地副本可用,如果没有,就获取一份副本(并将其保存在本地)。
  4. 新鲜度检测——缓存查看已缓存副本是否足够新鲜,如果不是,就询问服务器是否有任何更新。
  5. 创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文。
  6. 发送——缓存通过网络将响应发回给客户端。
  7. 日志——缓存可选地创建一个日志文件条目来描述这个事务。

Http缓存流程图:

状态码区别:

  • 200 请求成功,服务器返回全新的数据
  • 200 from memory cache / from disk cache 本地强缓存还在有效期,直接使用本地缓存
  • 304 请求成功,走了协商缓存,服务器判定(Etag和Last-modified)没有过期,告知浏览器使用缓存

业务中的使用场景:

项目中,我们主要使用缓存来存储html、css、js、img等静态资源,一般不会去存储动态资源,因为对动态资源的缓存会对数据实时性造成影响

1、强制缓存

强缓存是当我们访问URL的时候,不会向服务器发送请求,直接从缓存中读取资源,但是会返回200的状态码。

我们第一次进入页面,请求服务器,然后服务器进行应答,浏览器会根据response Header来判断是否对资源进行缓存,如果响应头中expires、pragma或者cache-control字段,代表这是强缓存,浏览器就会把资源缓存在memory cache 或 disk cache中。

第二次请求时,浏览器判断请求参数,如果符合强缓存条件就直接返回状态码200,从本地缓存中拿数据。否则把响应参数存在request header请求头中,看是否符合协商缓存,符合则返回状态码304,不符合则服务器会返回全新资源。

Expires:

这是 http1.0 时的规范;它的值为一个绝对时间的GMT格式的时间字符串,如Mon, 10 Jun 2022 21: 31 :12 GMT,如果发送请求的时间在expires之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源。

Expires过度依赖本地时间,如果本地与服务器时间不同步,就会出现资源无法被缓存或者资源永远被缓存的情况。所以,Expires字段几乎不被使用了

cache-control:

上面我们提到了Expires有个缺点,当客户端本地时间和服务器时间不一致时会产生误差,浏览器会直接向服务器请求新的资源,为了解决这个问题,在http1.1规范中,提出了cache-control字段,且这个字段优先级高于上面提到的Expires,值是相对时间。

在cache-control中有常见的几个响应属性值,它们分别是:

  • max-age=100 缓存100秒后过期,资源缓存在本地
  • s-maxage 覆盖 max-age,作用与max-age一样,但只用于代理服务器中缓存
  • no-cache 不使用本地缓存。使用协商缓存,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
  • no-store 所有内容都不会被缓存,既不使用强制缓存也不适用协商缓存,每次用户请求该资源,都会向服务器发送一个请求,服务器再返回资源
  • public 可以被所有的用户缓存,包括客户端和代理服务器
  • private 只能被客户端缓存,不允许CDN等中继缓存服务器对其缓存

2、协商缓存

上面提到的强缓存都是由本地浏览器在确定是否使用缓存,当浏览器没有命中强缓存时就会向浏览器发送请求,验证协商缓存是否命中,如果缓存命中则返回304状态码,否则返回新的资源数据。

协商缓存(也叫对比缓存)是由服务器来确定资源是否可用,这将涉及到两组字段成对出现的,在浏览器第一次发出请求时会带上字段(Last-Modified或者Etag),则后续请求则会带上对于的请求字段(if-modified-since或者if-none-Match),若响应头没有Last-Modified或者Etag,则请求头也不会有对应的字段

基于Last-Modified:

  1. 首先需要在服务器端读出文件修改时间,
  2. 将读出来的修改时间赋给响应头的last-modified字段。
  3. 最后设置Cache-control:no-cache

const http = require('http');
const fs = require('fs');
http.createServer((req,res)=>{
if(req.url === 'test.png'){
const data = fs.readFileSync('./test.png');
const {mtime} = fs.statSync('./test.png');
const temp = req.headers['if-modified-since'];
if(temp === mtime.toUTCString()){
res.statusCode = 304;
res.end();
return;
}
res.setHeader('last-modified',mtime.toUTCString());
res.setHeader('Catch-Control','no=cache');
res.end(data);
}else{
res.end(fs.readFileSync('./test.html'));
}
})

基于ETag:

  1. 第一次请求某资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的etag字段中跟资源一起返回给客户端。
  2. 第二次请求某资源的时候,客户端自动从缓存中读取出上一次服务端返回的ETag也就是文件指纹。并赋给请求头的if-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端。
  3. 服务端拿到请求头中的is-None-Match字段值(也就是上一次的文件指纹),并再次读取目标资源并生成文件指纹,两个指纹做对比。如果两个文件指纹完全吻合,说明文件没有被改变,则直接返回304状态码和一个空的响应体并return。如果两个文件指纹不吻合,则说明文件被更改,那么将新的文件指纹重新存储到响应头的ETag中并返回给客户端
const http = require('http');
const fs = require('fs');
const etag = require('etag');
http.createServer((req,res)=>{
if(req.url === 'test.png'){
const data = fs.readFileSync('./test.png');
const etagContent = etag(data);
const noMatch = req.headers['if-none-match'];
if(noMatch === etagContent){
res.statusCode = 304;
res.end();
return;
}
res.setHeader('etag',etagContent);
res.setHeader('Catch-Control','no=cache');
res.end(data);
}else{
res.end(fs.readFileSync('./test.html'));
}
})

作者简介

毕清华:精选主力干将!


来源:微信公众号:58本地服务终端技术

出处:https://mp.weixin.qq.com/s/-83N7RS1B6V_r5tcU2CyrA

试公司:

阿里

面试环节:

一面

问题:

说一说你对浏览器强缓存和协商缓存的理解

答案:

这里说的缓存是指浏览器(客户端)在本地磁盘中对访问过的资源保存的副本文件。

浏览器缓存主要有以下几个优点:

  1. 减少重复数据请求,避免通过网络再次加载资源,节省流量。
  2. 降低服务器的压力,提升网站性能。
  3. 加快客户端加载网页的速度, 提升用户体验。

浏览器缓存分为强缓存和协商缓存,两者有两个比较明显的区别:

  1. 如果浏览器命中强缓存,则不需要给服务器发请求;而协商缓存最终由服务器来决定是否使用缓存,即客户端与服务器之间存在一次通信。
  2. 在 chrome 中强缓存(虽然没有发出真实的 http 请求)的请求状态码返回是 200 (from cache);而协商缓存如果命中走缓存的话,请求的状态码是 304 (not modified)。 不同浏览器的策略不同,在 Fire Fox 中,from cache 状态码是 304.

其中 from cache 会分为 from disk cache 和 from memory cache. 从内存中获取最快,但是是 session 级别的缓存,关闭浏览器之后就没有了。

请求流程

浏览器在第一次请求后缓存资源,再次请求时,会进行下面两个步骤:

  1. 浏览器会获取该缓存资源的 header 中的信息,根据 response header 中的 expires 和 cache-control 来判断是否命中强缓存,如果命中则直接从缓存中获取资源。
  2. 如果没有命中强缓存,浏览器就会发送请求到服务器,这次请求会带上 IF-Modified-Since 或者 IF-None-Match, 它们的值分别是第一次请求返回 Last-Modified 或者 Etag,由服务器来对比这一对字段来判断是否命中。如果命中,则服务器返回 304 状态码,并且不会返回资源内容,浏览器会直接从缓存获取;否则服务器最终会返回资源的实际内容,并更新 header 中的相关缓存字段。

借用网上的一张图片

强缓存

强缓存是根据返回头中的 Expires 或者 Cache-Control 两个字段来控制的,都是表示资源的缓存有效时间。

  • Expires 是 http 1.0 的规范,值是一个GMT 格式的时间点字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT 。这个时间点代表资源失效的时间,如果当前的时间戳在这个时间之前,则判定命中缓存。有一个缺点是,失效时间是一个绝对时间,如果服务器时间与客户端时间偏差较大时,就会导致缓存混乱。而服务器的时间跟用户的实际时间是不一样是很正常的,所以 Expires 在实际使用中会带来一些麻烦。
  • Cache-Control 这个字段是 http 1.1 的规范,一般常用该字段的 max-age 值来进行判断,它是一个相对时间,比如 .Cache-Control:max-age=3600 代表资源的有效期是 3600 秒。并且返回头中的 Date 表示消息发送的时间,表示当前资源在 Date ~ Date +3600s 这段时间里都是有效的。不过我在实际使用中常常遇到设置了 max-age 之后,在 max-age 时间内重新访问资源却会返回 304 not modified ,这是由于服务器的时间与本地的时间不同造成的。当然 Cache-Control 还有其他几个值可以设置, 不过相对来说都很少用了:no-cache 不使用本地缓存。需要使用协商缓存。no-store直接禁止浏览器缓存数据,每次请求资源都会向服务器要完整的资源, 类似于 network 中的 disabled cache。public 可以被所有用户缓存,包括终端用户和 cdn 等中间件代理服务器。private 只能被终端用户的浏览器缓存。

如果 Cache-Control 与 Expires 同时存在的话, Cache-Control 的优先级高于 Expires 。

协商缓存

协商缓存是由服务器来确定缓存资源是否可用。 主要涉及到两对属性字段,都是成对出现的,即第一次请求的响应头带上某个字, Last-Modified 或者 Etag,则后续请求则会带上对应的请求字段 If-Modified-Since或者 If-None-Match,若响应头没有 Last-Modified 或者 Etag 字段,则请求头也不会有对应的字段。

  • Last-Modified/If-Modified-Since 二者的值都是GMT格式的时间字符串, Last-Modified 标记最后文件修改时间, 下一次请求时,请求头中会带上 If-Modified-Since 值就是 Last-Modified 告诉服务器我本地缓存的文件最后修改的时间,在服务器上根据文件的最后修改时间判断资源是否有变化, 如果文件没有变更则返回 304 Not Modified ,请求不会返回资源内容,浏览器直接使用本地缓存。当服务器返回 304 Not Modified 的响应时,response header 中不会再添加的 Last-Modified 去试图更新本地缓存的 Last-Modified, 因为既然资源没有变化,那么 Last-Modified 也就不会改变;如果资源有变化,就正常返回返回资源内容,新的 Last-Modified 会在 response header 返回,并在下次请求之前更新本地缓存的 Last-Modified,下次请求时,If-Modified-Since会启用更新后的 Last-Modified。
  • Etag/If-None-Match, 值都是由服务器为每一个资源生成的唯一标识串,只要资源有变化就这个值就会改变。服务器根据文件本身算出一个哈希值并通过 ETag字段返回给浏览器,接收到 If-None-Match 字段以后,服务器通过比较两者是否一致来判定文件内容是否被改变。与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于在服务器上ETag 重新计算过,response header中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。

HTTP中并没有指定如何生成 ETag,可以由开发者自行生成,哈希是比较理想的选择。

为什么要有 Etag

HTTP1.1 中 Etag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:

  • 一些文件也许会周期性的更改,但是内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since 能检查到的粒度是秒级的,使用 Etag 就能够保证这种需求下客户端在1秒内能刷新 N 次 cache。
  • 某些服务器不能精确的得到文件的最后修改时间。

优先级

Cache-Control > expires > Etag > Last-Modified

用户行为对缓存的影响

经过对qq、fire fox 、safari 、chrome 这几个浏览器的访问同一个页面测试我发现,不同的浏览器在 F5 刷新的时候 ,同一个文件 qq 、fire fox 浏览器会返回 304 Not Nodified,在请求头中不携带 Expires/Cache-Control; 而 chrome 和 safari 刷新的时候,会返回 200 from cache, 没有真正发起请求,走强缓存。可见不同的浏览器反馈是不一致的,所以下面表格中"F5刷新"时 Expires/Cache-Control 会无效我认为是存在一定争议的。

而 Ctrl + F5 强制刷新的时候,会暂时禁用强缓存和协商缓存。

如何设置强缓存和协商缓存

  1. 后端服务器,写入代码逻辑中:res.setHeader('max-age': '3600 public') res.setHeader(etag: '5c20abbd-e2e8') res.setHeader('last-modified': Mon, 24 Dec 2018 09:49:49 GMT)
  2. Nginx 配置add_header Cache-Control "max-age=3600" 一般来说,通过 nginx 静态资源服务器,会默认给资源带上强缓存、协商缓存的 header 字段。

两个示例

  1. 如果在 cache-control 定义的 max-age 时间之内,js, css 文件会走强缓存,http 状态码是 200, 跟服务器也并不会有交互。但是第一个文件 index.html 文件, 每次回车或者刷新都是状态码都是 304 ,因为它的请求头中默认每次都携带了 Cache-Control: max-age=0 。

  1. js css 文件 cache-control 超时之后,重新按回车会走协商缓存,请求服务器发现资源没有改变,于是返回 304 ,浏览器从缓存中获取内容,从 size 中也可以看出端倪, 几百 B 的包不是静态资源的体积。

三级缓存原理(大白话)

最后总结一下浏览器的三级缓存原理:

  1. 先去内存看,如果有,直接加载
  2. 如果内存没有,择取硬盘获取,如果有直接加载
  3. 如果硬盘也没有,那么就进行网络请求
  4. 加载到的资源缓存到硬盘和内存

原文:https://github.com/frontend9/fe9-interview/issues/29