缓存位置:
- Memory Cache : 内存缓存
Disk Cache:硬盘缓存
打开网页:查找 disk cache 中是否有匹配,如有则使用,如没有则发送网络请求
普通刷新 (F5):因TAB没关闭,因此memory cache是可用的,会被优先使用,其次才是disk cache
强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache,服务器直接返回 200 和最新内容
浏览器缓存机制:强缓存优先级高于协商缓存;强缓存中 Cache-Control 优先级高于 Expires
先试图命中强缓存,然后再试图命中协商缓存。强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。
强缓存
强缓存命中不会发送请求到服务端,直接从本地缓存中获取资源。返回状态码200(from memory cache/from disk cache)
强缓存利用Cache-Control
和Expires
两个字段实现,表示资源在客户端的缓存有效期。
浏览器对于强缓存的处理:根据第一次请求资源时返回的响应头来确定的
- Expires:缓存过期时间,用来指定资源到期的时间(HTTP/1.0)
- 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Expires 字段。
- 浏览器接受此资源,会将此资源以及响应头一起缓存(引出: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的header)。
- 浏览器再请求此资源时,会先从缓存中找,找到后,拿 Expires 和当前的请求时间比较,如果请求时间小于 Expires,就能命中缓存,否则没命中。
- 如果没命中,浏览器直接从服务器加载资源时,Expires 重新加载的时候会被更新。
-
缺点:
http1.0 的产物,由于它是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题。(比如:随意修改下客户端时间,就能影响缓存命中的结果)。所以在 HTTP 1.1 的时候,提出了一个新的 header,就是 Cache-Control,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示。
- Cache-Control:cache-control: max-age=2592000第一次拿到资源后的2592000秒内(30天),再次发送请求,读取缓存中的信息(HTTP/1.1)
- 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Cache-Control 字段。
- 浏览器接受此资源,会将此资源以及响应头一起缓存(引出: 缓存命中的请求返回的 header 不是来自服务端,而是来自之前缓存的header)。
- 浏览器再请求此资源时,会先从缓存中找,找到后,根据它第一次的请求时间和 Cache-Control 设定的有效期,计算出一个资源过期时间,再用这个过期时间和当前请求时间比较,如果请求时间小于资源过期时间,就能命中缓存,否则没命中。
- 如果没命中,浏览器直接从服务器加载资源时,Cache-Control 重新加载的时候会被更新。
Cache-Control 描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较 Expires,Cache-Control 的缓存管理更有效,安全一些。
- 两者同时存在的话,Cache-Control优先级高于Expires
没有命中强缓存就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的 http 状态为 304 并且会显示一个 Not Modified 的字符串
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
协商缓存是利用的是Last-Modified, If-Modified-Since
和ETag, If-None-Match
这两对字段来管理的。
协商缓存会发送请求到服务端,服务端通过请求头的头部字段验证是否命中协商缓存。如果命中,服务器会将这个请求返回,但是不会返回这个资源的实体,而是返回状态码304(not modified),通知浏览器从缓存中获取资源。
Last-Modified & If-Modified-Since 控制的协商缓存
- 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 Last-Modified 字段,表示这个资源在服务器上最后的修改时间。
- 浏览器再次请求此资源时,会在请求头加 If-Modified-Since 字段,值等于上次请求时的 Last-Modified 的值。
- 服务器再次收到资源请求时,会根据请求头的 If-Modified-Since 和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化返回 304(Not Nodified),但不会返回资源内容;如果有变化,则正常返回资源。当服务器返回304的响应时,响应头不会再添加 Last-Modified ,因为资源没有变化,那么 Last-Modified 也不会变化,即不更新 Last-Modified
- 浏览器收到 304 响应后,就会从缓存中加载资源。
- 如果协商缓存没有命中,浏览器直接从服务器加载,Last-Modified 会被更新,下次请求时,If-Modified-Since 会使用上次返回的 Last-Modified 。
Last-Modified 的弊端
- 如果本地打开缓存文件,即使没有对文件进行修改(文件内容没有变化),但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
- 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
- 有些服务器无法精准获取文件的最后修改时间
Last-Modified & If-Modified-Since 都是服务端响应头返回的字段,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个字段配合起来管理协商缓存是非常可靠的,但是有时候会存在服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来,而当这种情况出现的时候,就会影响协商缓存的可靠性。所以就有了另外一对ETag, If-None-Match
来管理协商缓存。
ETag & If-None-Match 控制的协商缓存
- 浏览器第一次请求服务器的资源,服务器在返回这个资源时,在响应头中加 ETag 字段,ETag 是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就会不同,跟最后修改时间没有关系,所以能很好的补充 Last-Modified 的问题
- 浏览器再次请求此资源时,会在请求头加 If-None-Match 字段,值等于上次请求时的 ETag 的值。
- 服务器再次收到资源请求时,会根据请求头的 If-None-Match 和 服务端再根据资源生成的新的 ETag 比较,如果没有变化返回 304(Not modified),但是不会返回资源的内容;如果有变化,就正常返回资源。与 Last-Modified 不一样的是,当服务器返回 304 的响应时,由于 ETag 重新生成过,响应头中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。
- 浏览器收到 304 响应后,就会从缓存中加载资源。
Etag 和 Last-Modified 非常相似,都是用来判断一个参数,从而决定是否启用缓存。但是 ETag 相对于Last-Modified 也有其优势,可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。如果两个都支持的话,服务器会优先选择 ETag
协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。大部分web服务器都默认开启协商缓存,而且是同时启用Last-Modified & If-Modified-Since
和 ETag & If-None-Match
,同时开启是为了处理 Last-Modified 不可靠的问题。
协商最佳实践
- 如果使用 Last-Modified 不会出现任何问题,可以直接移除 ETag,google 的搜索首页则没有使用 ETag。
- 确定要使用 ETag,在配置 ETag 的值的时候,移除可能影响到组件集群服务器验证的属性,例如只包含组件大小和时间戳。
对比
- 在精准度上,ETag 优于 Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
- 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
- Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
- 在性能上,Last-Modified 优于 ETag,也很简单理解,Last-Modified 仅仅只是记录一个时间点,而 Etag 需要根据文件的具体内容生成哈希值。
缓存实践
缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效。
在更新版本之后,如何让用户第一时间使用最新的资源文件呢?机智的前端们想出了一个方法,在更新版本的时候,顺便把静态资源的路径改了,即,可以利用 webpack 的文件指纹策略。这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。
一个较为合理的缓存方案:
- HTML:使用协商缓存。
- 假设 html 使用了强缓存,当我们重新上线 css、js等文件,html文件(因为css改变了,所以html要重新引入css文件,因此html也要改并且重新上线),但是 html 使用的是强缓存,在缓存期间浏览器直接从强缓存中读取html文件,以至于你重新上线了但是还是原来的 html,内容还是原来,必须要用户强制刷新才会有新内容出现,这就不合理。所以不能使用强缓存。
- CSS、JS、图片:使用强缓存,且文件命名带上 hash 值。
- 更新资源发布路径以实现非覆盖式发布,使强缓存失效。
- 利用版本号更新策略可行吗?
<link rel="stylesheet" href="/index.css?v=1.0.2">
在后面加个版本号,因为每次 html 都会发送请求询问是否过期,所以就可以达到缓存更新效果。但是开发不可能就引用一个 css 文件,往往是多个的。每次修改一个css 文件,其余的版本也要跟着升级,没有必要。
既然可以使用协商缓存的 Etag,那有必要使用文件指纹吗?
- 缓存的意义。缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效,访问最新资源,那么就需要文件指纹、版本号等使强缓存失效的手段。
- CSS、js、图片、字体等资源要做强缓存。利用 Webpack 合理分包加上文件指纹做到客户端的持久化缓存,不去解析 DNS,不去请求服务器,减小服务端压力的同时提高用户体验。所以,文件指纹用于资源路径更新使缓存失效,达到及时拉取最新资源的目的,使用文件指纹和 Etag 没什么关系。
- 分布式服务不使用 Etag。分布式服务,Etag 是关闭的,因为每台机器生成的 ETag 都会不一样。
- 持久换缓存做无覆盖式更新。CSS、js、图片、字体等资源不需要做协商缓存,不需要每次请求服务器询问资源是否新鲜。只需要 hrml 做协商缓存即可。若发布了最新资源,由于 html 中引用的资源的路径变了,会拉取最新资源,做无覆盖式更新。
总之一句话,为减少服务器请求,资源要尽可能做强缓存,而资源的更新需要利用文件指纹变更资源路径使强缓存失效,从而达到及时拉取到最新资源的目的。所以,文件指纹是为强缓存资源服务的,与协商缓存 Etag 无任何关系。
问题
Q1. no-cache和no-store 以及 max-age=0 的区别
- no-cache
Cache-Control 为 no-cache。表示必须重新去获取请求。由服务端来判断是否使用缓存, 即可以在本地缓存,可以在代理服务器缓存,但是这个缓存要服务器验证才可以使用
- no-store
是真正的不进行缓存。即彻底得禁用缓存,本地和代理服务器都不缓存,每次都从服务器获取
-
max-age=0
- max-age > 0 时。直接从浏览器缓存中提取。
- max-age <= 0 时。向服务端发送 http 请求确认,该资源是否有修改, 有的话返回 200 。没有返回 304。(即 max-age=0 表示不管 response 怎么设置,在重新获取资源之前,先检验 ETag/Last-Modified)
Q2. 状态码为 200 from cache 和 304 Not modified 的区别
- 请求状态码为 200 from cache
表示该资源已经被缓存过,并且在有效期内,所以不再向浏览器发出请求,直接使用本地缓存。
- 状态码为 304 Not modified
表示浏览器虽然发现了本地有该资源的缓存,但是不确定是否是最新的,于是想服务器询问,若服务器认为浏览器的缓存版本还可用(即还未更新),那么便会返回 304,继续使用本地的缓存。