前端HTTP 缓存简单了解

6,747 阅读14分钟

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

概要:

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

1. web缓存

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

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

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

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

1.1 缓存类型

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

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

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

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

图片源:HTTP caching

上图展示了:

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

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

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

1.2 缓存目的

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

2. 缓存的处理

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

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

当Last-Modified 最后修改时间也没有


图片从《HTTP权威指南》截取

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

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

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 的响应。

缺点: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做对比。

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

2.1.4 vary 可以简单了解

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

vary定义如下:

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

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

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

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

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

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

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

所以:

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

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

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

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

2.2 缓存流程

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

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 。

我自己的简单理解如上图

如果感兴趣资料:Disk Cache 3.0

3. 前端解决方案

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?