缓存,这是一个老生常谈的话题,也常被作为前端面试的一个知识点。
本文,重点在与探讨在实际项目中,如何进行缓存的设置,并给出一个较为合理的方案。
在介绍缓存的时候,我们习惯将缓存分为强缓存
和协商缓存
两种。两者的主要区别是使用本地缓存的时候,是否需要向服务器验证本地缓存是否依旧有效。顾名思义,协商缓存,就是需要和服务器进行协商,最终确定是否使用本地缓存。
强缓存
我们知道,强缓存主要是通过 http
请求头中的 Cache-Control
和 Expire
两个字段控制。Expire
是 HTTP1.0
标准下的字段,在这里我们可以忽略。我们重点来讨论的 Cache-Control
这个字段。
一般,我们会设置 Cache-Control
的值为 “public, max-age=xxx”
,表示在 xxx 秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。
显而易见,如果在 xxx 秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果说你不着急,可以接受这样的,那是不是完美?然而,很多时候不是你想的那么简单的,如果发布新版本的时候,后台接口也同步更新了,那就 gg 了。有缓存的用户还在使用旧接口,而那个接口已经被后台干掉了。怎么办?
协商缓存
协商缓存最大的问题就是每次都要向服务器验证一下缓存的有效性,似乎看起来很省事,不管那么多,你都要问一下我是否有效。但是,对于一个有追求的码农,这是不能接受的。每次都去请求服务器,那要缓存还有什么意义。
实践
缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效。
在更新版本之后,如何让用户第一时间使用最新的资源文件呢?机智的前端们想出了一个方法,在更新版本的时候,顺便把静态资源的路径改了,这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。
伟大的 webpack
可以让我们在打包的时候,在文件的命名上带上 hash
值。
|
综上所述,我们可以得出一个较为合理的缓存方案:
- HTML:使用协商缓存。
- CSS&JS&图片:使用强缓存,文件命名带上 hash 值。
哈希也有讲究
webpack
给我们提供了三种哈希值计算方式,分别是 hash
、chunkhash
和 contenthash
。那么这三者有什么区别呢?
hash
:跟整个项目的构建相关,构建生成的文件hash
值都是一样的,只要项目里有文件更改,整个项目构建的hash
值都会更改。chunkhash
:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk
,生成对应的hash
值。contenthash
:由文件内容产生的 hash 值,内容不同产生的contenthash
值也不一样。
显然,我们是不会使用第一种的。改了一个文件,打包之后,其他文件的 hash
都变了,缓存自然都失效了。这不是我们想要的。
那
chunkhash
和contenthash
的主要应用场景是什么呢?
在实际在项目中,我们一般会把项目中的 css
都抽离出对应的 css
文件来加以引用。如果我们使用 chunkhash
,当我们改了 css
代码之后,会发现 css
文件 hash
值改变的同时,js
文件的 hash
值也会改变。这时候,contenthash
就派上用场了。
ETag 计算
Nginx 官方默认的 ETag
计算方式是为”文件最后修改时间 16 进制-文件长度 16 进制”。
|
ETag
的用处
HTTP1.1
用 ETag
来判断请求的文件是否被修改,主要为了解决 Last-Modified
无法解决的一些问题
1、一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候并不希望客户端认为这个文件被修改了重新 GET
;
2、某些文件修改非常频繁,1 秒内修改了 N 次,If-Modified-Since
能检查到的粒度是秒级的,这种修改无法判断
3、某些服务器不能精确的得到文件的最后修改时间;
为此,HTTP1.1
引入了 ETag
.但标准并没有规定 ETag
的内容是什么或者说要怎么实现,唯一规定的是 ETag
需要放在双引号内。ETag
由服务器端生成,客户端通过 If-Match
或者说 If-None-Match
这个条件判断请求来验证资源是否修改。我们常见的是使用 If-None-Match
.请求一个文件的流程可能如下:
第一次请求:
客户端发起
HTTP get
请求一个文件服务器处理请求,返回文件内容和一堆
Header
,当然包括ETag
(例如”1ec5-502264e2ae4c0”)(假设服务器支持ETag
生成和已经开启了ETag
).状态码 200
第二次请求:
客户端发起 HTTP GET 请求一个文件,这个时候客户端同时发送一个 If-None-Match 头,这个头的内容就是我们第一次请求时服务器返回的
ETag
:1ec5-502264e2ae4c0服务器判断发送过来的
ETag
和计算出来的ETag
是匹配的,不返回 200,返回 304,让客户端继续使用本地缓存。
站点启用 ETag
的好处
1、Sitemap
启用 ETag
后,百度就可以快速的知道 Sitemap
的更新。在内容没有做更改的情况下,服务器只发回 304 应答头,对流量的消耗极小。平均每个 head 在 227B 大小的通常下,如果 24 个小时每秒回应的流量消耗仅为 18.7M。
2、站长平台对每个站点的 Sitemap
主动抓取次数是有上限的,且 这项功能没有对所有站点开放,如果你的站点 Sitemap
开启了 ETag
,并通过了测试,就可以用较小的流量享受百度及时更新网站的 Sitemap
的功能。
后端需要怎么设置
上文主要说的是前端如何进行打包,那后端怎么做呢?我们知道,浏览器是根据响应头的相关字段来决定缓存的方案的。所以,后端的关键就在于,根据不同的请求返回对应的缓存字段。以 nodejs
为例,如果需要浏览器强缓存,我们可以这样设置:
|
如果需要协商缓存,则可以这样设置:
|
- 本文作者: luckyship
- 本文链接: https://luckyship.github.io/2021/08/29/2021-08-29-http-cache/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!