浏览器缓存解析,看完不懂,你打我!

2,927 阅读7分钟

缓存概念

浏览器缓存(Brower Caching)是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,节省带宽,提高访问速度,降低服务器压力

http缓存机制主要在http响应头中设定,响应头中相关字段为ExpiresCache-ControlLast-Modified/If-Modified-SinceEtag/If-None-Match

缓存策略

通常浏览器缓存策略分为两种:强缓存和协商缓存

  • 1)浏览器在加载资源时,根据请求头的expires和cache-control判断是否命中强缓存,是则直接从缓存读取资源,不会发请求到服务器。
  • 2)如果没有命中强缓存,浏览器一定会发送一个请求到服务器,通过last-modified和etag验证资源是否命中协商缓存,如果命中,服务器会将这个请求返回,但是不会返回这个资源的数据,依然是从缓存中读取资源
  • 3)如果前面两者都没有命中,直接从服务器加载资源

强缓存不发请求到服务器,协商缓存会发请求到服务器。

强缓存

强缓存通过Expires和Cache-Control两种响应头实现

Expires

Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回。 Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效

Expires: Wed, 11 May 2018 07:20:00 GMT

Cache-Control

Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires ,表示的是相对时间

Cache-Control: max-age=315360000

Cache-Control的头部

Cache-Control:no-store

禁止一切缓存(这个才是响应不被缓存的意思)。缓存通常会像非缓存代理服务器一样,向客户端转发一条 no-store 响应,然后删除对象。

Cache-Control:no-cache

强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。服务器接收到请求,然后判断资源是否变更,是则返回新内容,否则返回304,未变更。这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。

Cache-Control:max-age

部表示的是从服务器将文档传来之时起,可以认为此 文档处于新鲜状态的秒数。

Cache-Control:s-maxage

和max-age是一样的,不过它只针对代理服务器缓存而言。

Cache-Control:private

只能被终端浏览器缓存(而且是私有缓存),不允许中继缓存服务器进行缓存

Cache-Control:public

可以被所有用户缓存(多用户共享),包括终端和CDN等中间代理服务器

协商缓存

当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串

Last-Modified/If-Modified-Since

Last-Modified 表示本地文件最后修改日期,浏览器会在request header加上If-Modified-Since(上次返回的Last-Modified的值),询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来

但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag

ETag/If-None-Match

Etag就像一个指纹,资源变化都会导致ETag变化,跟最后修改时间没有关系,ETag可以保证每一个资源是唯一的

If-None-Match的header会将上次返回的Etag发送给服务器,询问该资源的Etag是否有更新,有变动就会发送新的资源回来

ETag的优先级比Last-Modified更高

具体为什么要用ETag,主要出于下面几种情况考虑:

  • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新GET;
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),If-Modified-Since能检查到的粒度是s级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
  • 某些服务器不能精确的得到文件的最后修改时间。

整体流程图

第一次请求

第一次请求.jpg

非第一次请求

http缓存非第一次请求流程.jpg

缓存解决的问题

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

缓存位置

  1. Service Worker(是运行在浏览器背后的独立线程,一般可以用来实现缓存功能,传输协议必须为HTTPS)
  2. Memory Cache 这个是内存中的缓存,刷新页面大多数数据都来源与内存缓存,读取速度快,但容量不大,持续性很短,会随着进程的释放而释放
  3. Disk Cache 这个是存储在硬盘中的缓存,读取速度慢一些,但是什么都能存储在磁盘中
  4. Push Cache 这个是HTTP/2中的内容,当上门三种缓存都没有命中时,才会被使用,缓存时间也很短暂,只在session中存在,一旦会话结束就会被释放
  5. 网络请求 如何所有缓存都没有命中,那么就会发起请求来获取资源了

选择合适的缓存策略

协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义

大部分web服务器都默认开启协商缓存,而且是同时启用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】

但是下面的场景需要注意:

  • 分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败;
  • 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样);

实际场景的缓存策略

webpack的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就派上用场了。

webpack打包策略下,对于JS文件,我们基本都会使用chunkhash,对于css文件,我们一般都会使用contenthash

综上所述,我们可以得出一个较为合理的缓存方案:

HTML:使用协商缓存。 CSS&JS&图片:使用强缓存,文件命名带上hash值。