前端HTTP 缓存简单了解

始发掘金:《前端HTTP 缓存简单了解》 同步到博客园。

HTTP 缓存简单了解。文章整理了相关资料,记录了部分实践。方便大家轻松了解缓存。能回答上三个问题,HTTP缓存就算理解呢。能否缓存?缓存是否过期?协商缓存?

概要:

  • web缓存
  • 缓存的处理
  • 前端解决方案
  • 总结

1. web缓存

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

缓存是一种存储给定资源副本并在请求时将其提供回来的技术。

当Web缓存在其存储中具有请求的资源且能用时,它将拦截该请求并返回其副本,而不是从原始服务器重新下载。

关键字:缓存,原始服务器(产生原始文档)

1.1 缓存类型

缓存的种类:浏览器缓存(本文讨论点),代理缓存,网关缓存。

以上种类 缓存工作的原理是一致的,只是缓存所在的位置不同,涉及面更宽广。

前端HTTP 缓存简单了解

这几种缓存,可以分为两大类:

  • 专用缓存(私有缓存):私有缓存专用于单个用户。
  • 共享缓存:多用户共享。

前端HTTP 缓存简单了解

图片源:HTTP caching

上图展示了:

没有缓存:没有缓存直接向服务器请求资源。

共享缓存:当用户Browser1请求资源,经过缓存服务器,缓存服务器也没有资源,向原始服务器请求资源。得到资源后,缓存服务器缓存资源并返还数据给Browser1。当用户Browser2请求相同资源时,缓存服务器有资源,且能用,就直接返还数据给Browser2,不再向原始服务器发起请求。

私有缓存:用户Browser1请求资源,向服务器请求资源。得到资源后,缓存在本地,供下一次请求同样资源时判定使用。用户Browser2需要同样的资源,只能向服务器请求资源,并缓存供下一次请求同样资源时判定使用。

1.2 缓存目的

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

 

2. 缓存的处理

对于HTTP 缓存流程中涉及到的简单问题及相关首部字段。

前端HTTP 缓存简单了解

  • Http 响应的内容是否可缓存到客户端(能否缓存)。
  • 客户端是否可直接从本地缓存中加载并展示,或者发送请求到服务端再验证(缓存是否过期)。
  • 客户端将缓存标识发往服务端,服务端通过标识来判断客户端的缓存是否仍有效,或发送新的数据给客户端(协商缓存能否再用)。

2.1 相关的首部字段

2.1.1 数据能否缓存,相关字段

⑴ 默认存储

默认情况下,如果请求方法,请求标头字段和响应状态的要求表明响应是可缓存的,则该响应是可缓存的。

常见的HTTP缓存通常仅限于缓存对GET的响应,并且可能会拒绝其他方法。 主缓存键由请求方法和目标URI组成(通常仅使用URI,因为只有GET请求才是缓存目标)。

除非特别受cache-control指令约束,否则缓存系统可以始终将成功的响应存储为缓存条目,如果新鲜则可以不经验证就将其返回。如果新鲜也可以在成功验证后返回。

状态码为200、203、206、300、301或410的响应也可以由缓存存储,并用于回复后续请求。

具体参考响应可缓存性

⑵ Cache-Control

Cache-Control头里的no-store、no-cache、Public、Private、max-age 用来指明响应内容是否可以被客户端存储,

no-store :禁止进行缓存 缓存不应存储有关客户端请求或服务器响应的任何内容。每次由客户端发起的请求都会下载完整的响应内容。
no-cache:缓存但重新验证 缓存将在使用缓存副本之前,将此请求(带有与本地缓存相关的验证字段)到原始服务器进行验证。
public: 公共缓存 表示该响应可以被任何缓存器(比如中间代理、CDN等)缓存
一些通常不被中间缓存器缓存的页面(比如 带有HTTP验证信息(帐号密码)的页面 或 某些特定状态码的页面),将会被其缓存。
s-maxage=<seconds>: 缓存有效时间 同max-age作用一样表示缓存有效时间,但 s-maxage指令只适用于供多位用户使用的公共缓存服务器(比如CDN缓存)。使用 s-maxage 指令后,直接忽略对 Expires 首部字段及max-age 指令的处理。
private: 私有缓存 表示该响应是专用于某单个用户的,该响应只能应用于浏览器私有缓存中。
max-age=<seconds>: 缓存有效时间 表示资源能够被缓存(保持新鲜)的最大时间。相对Expires而言,max-age是距离请求发起的时间的秒数。

 

⑶ Expires:

指明可以被客户端存储,还告诉了时间。(Expires首部和Cache-control:max-age 首部做的事情基本一致)
Expires:value为缓存过期时间,用来指定资源到期的时间,是服务器端具体的时间点,在过期时间前浏览器可以直接使用缓存数据。如果响应中存在带有max-age或s-maxage指令的Cache-Control标头,则Expires标头将被忽略。

2.1.2 缓存是否过期

⑴ 不能直接使用

Cache-Control:no-cache

缓存将在使用缓存副本之前,将此请求(带有与本地缓存相关的验证字段)到原始服务器进行验证。

⑵ 计算是否过期

Date:创建报文的日期时间。

Expires:指明可以被客户端存储,还告诉了时间。(Expires首部和Cache-control:max-age 首部做的事情基本一致)。

Cache-Control:max-age=<seconds> 缓存有效时间。

计算新鲜度公式如下:

max-age指令优先于Expires,因此,如果响应中存在max-age,则计算很简单:
// 新鲜度 = max_age_value
fresh_lifetime = max_age_value
否则,如果响应中存在Expires,则计算为:
// 新鲜度 = expires_value - date_value(Date创建报文的日期时间(启发式缓存阶段会用到这个字段))
fresh_lifetime = expires_value - date_value

Age:告诉接收端响应已产生多长时间(Age值有具体算法感兴趣可以查看Age Calculations)(HTTP/1.1缓存必须在发送每条响应中都包含一个Age头部)

缓存计算是否过期:

// 响应是否新鲜    current_age: 是浏览器计算出的age 值
response_is_fresh = (freshness_lifetime > current_age)

当响应中没有Cache-Contral:max-age 首部,也没有Expires首部,缓存可以计算出一个试探性最大使用期,即启发式缓存。

⑶ 启发式缓存:

如果响应中未显示Expires,Cache-Control:max-age或Cache-Control:s-maxage,并且响应中不包含其他有关缓存的限制,缓存可以使用启发式方法计算新鲜度寿命。

通常会根据响应头中的2个时间字段 Date 减去 Last-Modified 值的 10% 作为缓存时间。

// Date 减去 Last-Modified 值的 10% 作为缓存时间。
// Date:创建报文的日期时间, Last-Modified 服务器声明文档最后被修改时间
  response_is_fresh =  max(0,(Date -  Last-Modified)) % 10

通常会设置计算出来的值会设置上线。但服务器端最好还是显示提供到期时间比较好

HTTP / 1.1规范没有提供特定的算法,但是对结果施加了最坏情况的约束。 由于启发式到期时间可能会损害语义透明性,因此应谨慎使用,并且我们鼓励原始服务器尽可能提供显式的到期时间。

当缓存文档过期的时候,需要客户端带上缓存的标识去服务端验证,缓存是否还能再用。这就是协商缓存过程了。

2.1.3 协商缓存能否再用相关字段

当客户端第一次请求的时候没有带条件首部,服务端响应带有条件首部,如Last-Modified ,ETag等,当下次缓存过期客户端将缓存的数据标识发往服务端进行验证。

前端HTTP 缓存简单了解

HTPP定义的条件首部最有用的两个 If-Modified-Since 和If-None-Match

⑴ Last-Modified 和 If-Modified-Since
Last-Modified(服务器响应首部): 服务器记录的资源的更新时间。
If-Modified-Since(请求首部字段):已缓存副本的最后修改日期。

当缓存过期,再验证。客户端将缓存的数据标识If-Modified-Since发往服务端,服务端将用Last-Modified 与 If-Modified-Since做对比。If-Modified-Since 字段值早于资源的Last-Modified更新时间,则希望返回新资源。而在指定 If-Modified-Since 字段值的日期时间之后,如果请求的资源都没有过更新,则返回状态码 304 Not Modified 的响应。

前端HTTP 缓存简单了解

缺点:last-Modified 只能精确到秒,文件的修改非常频繁,在秒以下的时间内进行修改,Last-Modified不能精确。

一个文件位于多个CDN服务器上内容虽然一样,当修改时间不一样。(比对后会返回信息更新)

所以在 HTTP / 1.1 出现了 ETag 。

⑵ ETag 和 If-None-Match

ETag(服务器响应首部): 实体标识。它是一种可将资源以字符串形式做唯一性标识的方式。服务器会为每份资源分配对应的 ETag值。

If-None-Match(请求首部字段):缓存的实体标签。用于指定 If-None-Match 字段值的实体标记(ETag)值与请求资源的 ETag 不一致时,它就告知服务器处理该请求返回新资源,相反则返回状态码 304 Not Modified 的响应。

当缓存过期,再验证。客户端将缓存的数据实体标签If-None-Match发往服务端,服务端将用If-None-Match 与 ETag做对比。

前端HTTP 缓存简单了解

⑶ 其它if 条件首部参考文档

2.1.4 vary 可以简单了解

vary可以简单了解,后端用于配置。

vary定义如下:

Vary 是一个HTTP响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).

举个例子:图片来源《图解http》

前端HTTP 缓存简单了解

一个客户端向服务器请求/sample.html资源,Accept-Language: en-us,代理服务器没有此资源,向服务器请求

服务器返回了资源,HTTP响应头部信息Vary指定了 Accept-Language,代理服务器返回资源给客户端,并缓存了数据。

第二个客户端向服务器请求/sample.html资源,Accept-Language: zh-cn,代理服务器没有此资源,向服务器请求

服务器返回了资源,HTTP响应头部信息Vary指定了 Accept-Language,代理服务器返回资源给客户端,并缓存了数据。缓存如下

前端HTTP 缓存简单了解

第三个客户端向服务器请求/sample.html资源,Accept-Language: zh-cn或者 en-us, 代理服务器都能返回缓存数据(缓存没过期)。

所以:

服务器使用Vary字段来通知缓存哪些请求头字段用于区分相同的URL请求,服务端存在不同内容的响应。

缓存也会根据Vary指定了 的字段X,根据X字段的值,决定使用缓存,还是发起请求获取数据

上述例子是简单描述存在代理服务器的请款,浏览器通常不实现针对每个URL存储多个变体的功能。

感兴趣可以查看:Understanding The Vary Header Caching Negotiated Responses

2.2 缓存流程

假设只有浏览器缓存和服务器的场景。参考以上字段画图如下

前端HTTP 缓存简单了解

2.2.1 流程分析

⑴ 当对资源发起请求的时候,缓存对url 报文进行解析,提取首部判断客户端是否有缓存。

没有缓存,求直接向服务器端请求数据,当得到数据后按缓存控制存储。

有缓存的情况:

  • 缓存需要验证才可使用,向服务器发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证。
  • 缓存能用,是否过期
    • 缓存能用,没有过期,构造响应报文,展示缓存内容
    • 缓存过期,发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证。

 

⑵ 向服务器发送本地缓存的相关的验证字段(If-None-Match 、If-Modified-Since)到原始服务器进行验证

条件方法再验证(协商缓存):

  • 条件验证成功:原始服务器向客户端发送一个小的 HTTP 304 Not Modified 响应,不包括内容。客户端缓存会更新缓存文档的新鲜度,构造响展示缓存内容。
  • 验证失败:原始服务器向客户端返回新的内容,客户端缓存,展示新的内容。
  • 内容被删除:原始服务器向客户端发送一个 404 Not Found 响应,客户缓存也会删除缓存。

2.2.2 memory cache和 disk cache

当缓存足够新鲜,直接返回缓存数据或者重新加载数据,数据 from memory cache 还是 from disk cache 不要太过纠结。这个跟HTTP 缓存机制没关系。

Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.

大致意思就是:memory cache 的生存期与渲染过程的生存期相关,渲染过程的生存期大致与选项卡相对应。

Chrome优化可以询问正在运行的进程,然后再在磁盘上查找它们是否仍在内存中加载了它们的副本。当页面刷新或者加载,所有的内容文档都会读取到内存中展示,如果此时文档在内存中已经存在,那么缓存 from memory cache,如果是从磁盘中读取的就 from disk cache 。

前端HTTP 缓存简单了解

我自己的简单理解如上图

如果感兴趣资料:Disk Cache 3.0

3. 前端解决方案

前端HTTP 缓存简单了解

HTML:设置Cache-control:no-cache(服务器端配置)浏览器再每次请求时都始终重新验证文档,并在内容变化时获取最新版本。
在HTML内挂载的 js css png 都带上 文件唯一标识字符串。任意文件变化,url 就会变化,从而引起HTML 文件变化。下次请求资源就会更新。

4. 总结

文章整理了相关资料,记录了部分实践和自己的理解,理解不准确之处,还请教正。欢迎一起讨论学习。

 

 

 

参考资料:

《图解HTTP》

《HTTP权威指南》

Understanding The Vary Header

rfc2616

Disk Cache 3.0

http-caching

HTTP caching(MDN)

Caching Tutorial

What does Blink in-memory cache store?

 

 

上一篇:单分支 git 的冲突问题


下一篇:微信小程序之性能优化