前言
作为前端开发,我们在web开发的过程中需要调用后端接口,用node搭建服务器或者写一些中间件对数据进行封装处理等等,必不可少地会和http打上交道,所以我们都要掌握http各方面的知识。
说到性能优化问题,一定会提到http缓存,如何利用好http缓存机制,理解http缓存的整个过程和http如何设置缓存,把这些都弄懂后会对我们以后在项目实践中都会有很大帮助。本篇文章将会和大家分享一下笔者对http缓存机制的一些知识整理和总结。
与缓存相关的http头部字段
我们先看看http头部中与缓存相关的一些字段的基本概念和用法。
Pragma
该字段来自http1.0时代,当其设置为no-cache
的时候和Cache-Control: no-cache
效果一样,意为无缓存,客户端不会读取缓存,直接向服务器发起请求。因为是http1.0留下的遗物,仅为了做向后的兼容。
Expires
该字段也是来自http1.0时代,用于控制缓存时间。例如设置了Thu, 18 Jul 2019 08:43:30 GMT
,即告诉浏览器如果没有超过这个时间,将不会向服务器发起请求。
Expires存在两个缺点:
- Expires的值是一个时间值。这就要求了客户端和服务器端需要时间严格同步。否就会导致缓存失效。
- 当Expires定义的时间过期了,服务器需要重新配置一个新的时间。
cache-control
http1.1新增了 Cache-Control 字段,用来定义缓存过期时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。(网上也有Pragma优先级高于Cache-Control的说法,但是一般设置了Pragma: no-cache
的话也会设置Cache-Control: no-cache
的吧-.-)
先看看cache-control的常用设置值
cache-directive | 描述 |
---|---|
no-cache | 不使用缓存,强制向服务器请求资源 |
no-store | 所有内容都不会被缓存 |
public | 资源将会被客户端和代理服务器缓存 |
private | 资源将会被客户端缓存但不会被代理服务器缓存 |
max-age | 缓存资源的最大时间段 |
s-maxage | 同上, 依赖public设置, 覆盖max-age, 且只在代理服务器上有效. |
max-stale | 指定时间内, 即使缓存过时, 资源依然有效 |
min-fresh | 缓存的资源至少要保持指定时间的新鲜期 |
must-revalidation / proxy-revalidation | 作用与no-cache类似,强制重新向服务器(或代理)发起验证 |
only-if-cached | 只从缓存中获取资源,若没有缓存则返回504 |
cache-control字段可以包含以上多个值,但是最终会选择最为保守的方案。
Last-Modified & If-Modified-Since & If-Unmodified-Since
当客户端第一次发起请求时,服务端会返回状态码200以及客户端请求的资源,同时响应头部会带有Last-Modified字段,标记此文件在服务器最后修改的时间。
而下次客户端再次发起请求的时候,请求头部就会带有If-Modified-Since或者If-Unmodified-Since的字段,他们的值是上一次响应头部Last-Modified字段的值,用于校验资源是否过期。
若使用If-Modified-Since则服务端会判断此时间后此文件是否修改过,如果没有修改过则使用缓存资源,返回304,否则将获取资源返回200.
若使用If-Unmodified-Since,则是相反的情况,若资源未修改过则获取资源,否则返回412(Precondition Failed)响应。它常用于两个场景:
- 不安全的请求, 比如说使用post请求更新wiki文档, 文档未修改时才执行更新
- 与 If-Range 字段同时使用时, 可以用来保证新的片段请求来自一个未修改的文档
当遇到下面情况时,If-Unmodified-Since 字段会被忽略:
- Last-Modified值对上了(资源在服务端没有新的修改)
- 服务端需返回2XX和412之外的状态码
- 传来的指定日期不合法
Last-Modified存在的问题
- 服务器端的静态资源通常需要编译打包, 可能出现资源内容没有改变, 而Last-Modified却改变的情况.
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,If-Modified-Since无法检查到。
- 某些服务器不能精确的得到文件的最后修改时间。
Etag & If-Match & If-None-Match
为了解决以上Last-Modified存在的问题,http1.1还提出另外一个字段——Etag。Etag,实体首部字段,服务器资源的唯一标识符, 客户端可以根据ETag值缓存数据, 节省带宽。因此Etag的优先级高于Last-Modified。
服务器会通过某种算法,给资源计算得出一个唯一标志符(比如md5标志),在把资源响应给客户端的时候,会在实体首部加上“ETag: 唯一标识符”一起返回给客户端。
当再次向服务器发起请求时,请求头部会发送If-Match或者If-None-Match字段,服务器收到请求后则将其与被请求资源的唯一标识进行比对。
If-None-Match,若不同,说明资源又被改动过,则响应整片资源内容,返回状态码200;若相同,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。
If-Match,若不同,则返回412(Precondition Failed) 状态码给客户端。否则服务器直接忽略该字段。其应用场景是客户端走PUT方法向服务端请求上传/更替资源,这时候可以通过 If-Match 传递资源的ETag。
使用Etag注意两点:
- 如果资源是走分布式服务器(比如CDN)存储的情况,需要这些服务器上计算ETag唯一值的算法保持一致,才不会导致明明同一个文件,在服务器A和服务器B上生成的ETag却不一样。
- 如果 Last-Modified 和 ETag 同时被使用,则要求它们的验证都必须通过才会返回304,若其中某个验证没通过,则服务器会按常规返回资源实体及200状态码。
http缓存策略
做好前期的知识铺垫,马上扔个流程图上来
我们看到图中不同阶段有不同的缓存策略,我们逐一看看。
过期策略(命中强缓存)
过期策略主要是通过Expires和CacheControl来判断缓存是否已经过期,若两者都没有设置的话,则使用浏览器默认的一套‘10%’算法来进行判断,通常会取响应头的Date_value - Last-Modified_value值的10%作为缓存时间。
假如缓存没有过期则不向浏览器发出请求了,但是抓包的时候还是返回200 ok,但是留意了,会有Provisional headers are shown 的提示,说明就是浏览器并未发出请求, 缓存依然有效。
(这坑很大啊,明明没有向浏览器发起请求,还显示200来吓唬人-.-)
这时在Chrome里面会出现200(from disk cache)和200(from memory cache)的情况。官方解释如下:
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.
意思大概就是Chrome会使用磁盘缓存和内存缓存,内存缓存是直接从内存中读取的,所以速度更快。内部具体的规则还不大了解,估计是根据渲染刷新方式和文件大小决定的。
假如缓存过期了则走下一步协商策略。
协商策略
协商策略是通过Last-Modified和Etag字段向服务器发起验证能否使用缓存资源,若验证通过,则返回304状态码,根据响应头更新缓存,使用缓存资源。
若没有通过校验,则向服务器获取资源,返回200,并将响应内容存进缓存。
存储策略
这没啥好说的,就是将响应内容存入缓存或者根据响应内容更新缓存。
综上,再看回这个流程图,我们梳理一下整个过程。
- 客户端首次发起请求,没有缓存数据,向服务器请求数据,服务器返回数据和缓存规则,并存入缓存。
- 客户端再次发起请求,检测是否有缓存,若有缓存,则检测缓存是否过期,若缓存没有过期,则使用缓存数据。(过期策略)
- 若缓存数据已过期,则向服务器发起校验,验证缓存是否有效,若通过验证,则使用缓存,返回304;反之向服务器发起请求,返回200。(协商策略)