Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

阿里巴巴:浏览器的强缓存和协商缓存(一面) #29

Open
yiliang114 opened this issue May 1, 2019 · 3 comments
Open

Comments

@yiliang114
Copy link

yiliang114 commented May 1, 2019

面试公司:

阿里

面试环节:

一面

问题:

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

@yiliang114
Copy link
Author

yiliang114 commented May 1, 2019

答案:

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

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

  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 级别的缓存,关闭浏览器之后就没有了。
image.png

请求流程

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

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

借用网上的一张图片

image.png

强缓存

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

  • Expireshttp 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.1Etag 的出现主要是为了解决几个 Last-Modified 比较难解决的问题:

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

优先级

 Cache-Control  > expires > Etag > Last-Modified

用户行为对缓存的影响

简单说就是 F5 刷新的时候,会暂时禁用强缓存

经过对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 强制刷新的时候,会暂时禁用强缓存和协商缓存。

在写这篇博客时,对于我仅仅测试了一个浏览器之后便写了无效(因为网上大多数帖子写了无效,我也以为我验证通过了),对指出这个问题的群友,表示感谢,希望其他人不会被我误导。

用户操作 Expires/Cache-Control Last-Modied/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进回退 有效 有效
F5刷新 无效(有争议,不同浏览器反馈不一致) 有效
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 字段。

    image.png

两个示例

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

    image.png

    image.png

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

    image.png

三级缓存原理(大白话)

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

  1. 先去内存看,如果有,直接加载

  2. 如果内存没有,择取硬盘获取,如果有直接加载

  3. 如果硬盘也没有,那么就进行网络请求

  4. 加载到的资源缓存到硬盘和内存

参考文档

from disk cache 与 from memory cache

http协商缓存VS强缓存

@acodercc
Copy link
Member

acodercc commented May 1, 2019

图文并茂,条理清晰,赞用心的写作

@yiliang114 yiliang114 changed the title To zhijianzhang: 浏览器的强缓存和协商缓存(阿里) To yiliang114: 浏览器的强缓存和协商缓存(阿里) May 4, 2019
@acodercc acodercc changed the title To yiliang114: 浏览器的强缓存和协商缓存(阿里) 阿里巴巴: 浏览器的强缓存和协商缓存(一面) May 6, 2019
@acodercc acodercc changed the title 阿里巴巴: 浏览器的强缓存和协商缓存(一面) 阿里巴巴:浏览器的强缓存和协商缓存(一面) May 6, 2019
@ZhengMengSi
Copy link

ZhengMengSi commented Jan 31, 2021

并且返回头中的 Date 表示消息发送的时间,表示当前资源在 Date ~ Date +3600s 这段时间里都是有效的。不过我在实际使用中常常遇到设置了 max-age 之后,在 max-age 时间内重新访问资源却会返回 304 not modified ,这是由于服务器的时间与本地的时间不同造成的。

我觉得这段表述的不太准确,Date确实是服务器响应时间,但是与资源的有效期无关。资源的有效期应该是:客户端接收到资源的时间点到3600s之间的这段时间,max-age相对的是客户端接收到响应的时间,而不是Date。这也就解释了你所描述的在 max-age 时间内重新访问资源却会返回 304 not modified的这一现象,原因就是服务器时间比客户端时间要快一些。 @yiliang114

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants