前端性能优化-缓存(部分)

4,456 阅读10分钟

前言

面试中我们经常会遇到 前端性能如何优化缓存方面的知识,对于我们而言,我们常常是束手无策。 下面来详细介绍缓存及如何利用这方面的知识.

CDN 缓存

看到一个形象的比喻,来比喻CDN。

10年前,还没有火车票代售点一说,12306.cn更是无从说起。那时候火车票还只能在火车站的售票大厅购买,而我所在的小县城并不通火车,火车票都要去市里的火车站购买,而从我家到县城再到市里,来回就是4个小时车程,简直就是浪费生命。后来就好了,小县城里出现了火车票代售点,甚至乡镇上也有了代售点,可以直接在代售点购买火车票,方便了不少,全市人民再也不用在一个点苦逼的排队买票了。

CDN就可以理解为分布在每个县城或者乡镇的火车票代售点,用户在浏览网站的时候,CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了。

在未接入CDN 之前,用户使用浏览器访问服务的时候,相互交互的过程如下图所示。

用户在第一次访问网站服务器的时候,浏览器会从服务器获取所有的资源,在传输过程中,浏览器会通过一些约定好的响应头,从而确定是否需要将这个资源保存一份到本地作为缓存。当用户第二次访问该网站的时候,浏览器就会优先从缓存中加载资源,不用向服务器请求资源,从而提高了网站的访问速度。

例如我们第一次访问一个网站,下面就是浏览器加载资源的快照,可以看到 5.6MB 的数据被传输到本地。

在刷新后,我们可以看到传输数据降到了 9.9KB,在使用了缓存之后,浏览器不用再下载全部的文件,减少了下载量也就意味着提高了页面加载的速度。

通过上面的例子,可以直观地观察到浏览器缓存对解决网络延迟起到的作用是非常明显的。

而对于一些用户访问量巨大的网站而言,如果所有用户都去服务器请求数据,服务器会很快崩溃,并且在不同网络以及不同地区的用户,请求服务器的速度也不一样。为了提高这部分用户的访问速度,CDN 中又提出了新的网络架构,即创建一些最接近用户网络的边缘服务器,然后将文件缓存在这些边缘服务器(节点)上,这就是 CDN 缓存。

CDN的优势很明显:(1)CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;(2)大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。

关于CDN缓存 ,在浏览器本地缓存失效后,浏览器会向CDN边缘节点发起请求。类似浏览器缓存,CDN边缘节点也存在着一套缓存机制。CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的 Cache-control: max-age 的字段来设置CDN边缘节点数据缓存时间。

当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求,从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。 CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。

CND 缓存刷新

CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。

浏览器缓存

简单来说,浏览器缓存其实就是浏览器保存通过HTTP获取的所有资源,是浏览器将网络资源存储在本地的一种行为。

缓存的资源去哪里了?

你可能会有疑问,浏览器存储了资源,那它把资源存储在哪里呢?

memory cache

MemoryCache顾名思义,就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。Webkit早已支持memoryCache。 目前Webkit资源分成两类,一类是主资源,比如HTML页面,或者下载项,一类是派生资源,比如HTML页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader和SubresourceLoader。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等),以及解码过的图片数据。

disk cache

DiskCache顾名思义,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取,它的直接操作对象为CurlCacheManager。

|memory cache | disk cache

相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件
不同点 退出进程时数据会被清除 退出进程时数据不会被清除
存储资源 一般脚本、字体、图片会存在内存当中 一般非脚本会存在内存当中,如css等

因为CSS文件加载一次就可渲染出来,我们不会频繁读取它,所以它不适合缓存到内存中,但是js之类的脚本却随时可能会执行,如果脚本在磁盘当中,我们在执行脚本的时候需要从磁盘取到内存中来,这样IO开销就很大了,有可能导致浏览器失去响应。

三级缓存原理 (访问缓存优先级)

  1. 先在内存中查找,如果有,直接加载。
  2. 如果内存中不存在,则在硬盘中查找,如果有直接加载。
  3. 如果硬盘中也没有,那么就进行网络请求。
  4. 请求获取的资源缓存到硬盘和内存。

1. Cache-control: max-age

假设你的站点有引用一个脚本文件,你非常确认这个脚本文件内容五十年不变。那么自然希望浏览器把这个脚本缓存起来,不用每一次都请求服务器,然后服务器再返回相同的内容。这样能够节省带宽开销并且提升性能。

此时你只需要设置文件返回的HTTP头中的Cache-Control设置为:

Cache-Control: max-age=31536000。 虽然是五十年不变,但是标准中规定max-age值最大不能超过一年,又因为是以秒为单位,所以值为31536000

例如这个五十年不变的脚本地址是 www.haorooms.com/never-expire.js 那么接下来每次用户请求这个地址时,浏览器都不会再向服务器发出请求,而是直接从本地的浏览器缓存中取。直到一年以后或者用户手动的清除了缓存。

但是,如果这一年中的某一天你发现脚本内容必须要更改了怎么办?很简单,改变请求的文件名就好了,例如never-expire-v2.js。

Cache-control: max-age 可以控制缓存时间。如下图:

Max-age 使用秒来计量,如:

Cache-Control:max-age=645672

指定页面645672秒(7.47天)后过期。

注:Cache-control是http1.1的特性,能够更加精准的控制缓存

除了上面介绍的 max-age,Cache-control还有如信息:

no-cache:先不要读取缓存中的文件,向WEB服务器请求验证缓存是否新鲜,新鲜则使用缓存 no-store:这个字段很关键,它表示数据不在硬盘中临时保存 only-if-cached:就是在客户端有缓存时就是用客户端的缓存,这个一般都是在无网时使用 max-stale:只要缓存的时间没有超过它(max-stale)指定的时间,就可以加载使用.我们可以在无网络的情况下使用 must-revalidate:作用和相同,但是更为严格.每次请求都校验缓存和服务器源文件,一致就使用缓存,不一致就拿最新 s-maxage 同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。 public 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。 private 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

2. Expires

该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。

注:Expires是http1.0特性,比Cache-control要早,因此有些缺陷。由于失效时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端时间偏差变大以后,就会导致缓存混乱。比如说,服务器时间是2018年4月19号,客户端本来时间是2018年4月10号,但是我手动修改客户端时间为2018年4月20,因此服务器时间和客户端时间有偏差之后,缓存失效,这是不对的。

以上2个缓存,遵循三级缓存原理,Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

3、Last-Modified 304协商缓存

服务器为了通知浏览器当前文件的版本,会发送一个上次修改时间的标签,例如:

Last-Modified:Tue, 06 Jan 2018 08:26:32 GMT

如下图:

假如是304协商缓存,验证步骤如下:

  1. 浏览器:Hey,我需要jquery.min.js这个文件,如果是在 Last-Modified:Tue, 06 Jan 2018 08:26:32 GMT 之后修改过的,请发给我。
  1. 服务器:(检查文件的修改时间)
  1. 服务器:Hey,这个文件在那个时间之后没有被修改过,你已经有最新的版本了。
  1. 浏览器:太好了,那我就显示给用户了。

4. ETag

上面截图中也圈出来了,其实Etag和304类似,但是级别比 Last-Modified 高一些。

请求过程如下:

  1. 浏览器:Hey,我需要haorooms的main.css这个文件,有没有不匹配"61213-1762a-50bf790757204"这个串的

  2. 服务器:(检查ETag…)

  3. 服务器:Hey,我这里的版本也是"61213-1762a-50bf790757204",你已经是最新的版本了

  4. 浏览器:好,那就可以使用本地缓存了