在我们使用第三方库(比如jQuery)时,一种推荐的把第三方库添加到我们的页面中的方法就是使用CDN链接添加,一个原因是CDN离用户更近,加载更快;但是更重要的原因是如果所有使用这个库的网站都使用这种方式添加第三方库时,当我们的网站被访问的时候,也许这个库已经被缓存了,直接从缓存加载将极大地提高性能。

当我们的网站没有明确对缓存进行设置时,浏览器也会对静态文件进行缓存,只不过缓存的生命周期只是当前会话。

只要我们合理设置缓存的响应头就可以让浏览器和代理对静态文件进行缓存,并且在我们指定的之间内都有效。在有效期间内,相关资源可以直接从缓存加载,提升性能。

适合缓存的资源

通常可用于缓存的资源包括CSS、JavaScript、图像文件和其他二进制文件(比如多媒体文件)。因为HTML通常是动态生成,所以一般不认为HTML文件是静态文件,不对其进行缓存。

了解了适合缓存的文件类型,接下来就来对缓存进行设置吧。

设置缓存的“保质期”

在购买食品时,我们通常会看一下保质期。保质期通常有两种形式:一种是标明保质期是多久,另一种是标明在xx日期前食用。

指定缓存的“保质期”也有两种方式

  1. Expires响应头,HTTP/1.0添加,通过指定一个未来时间作为缓存的失效时间
  2. Cache-Control响应头,HTTP/1.1添加,通过指定max-age=<number>(s)来表示请求该资源后number秒失效

同时指定max-ageExpires时,将使用max-age的值,而忽视Expires的值。

在缓存失效前,缓存会直接从缓存中加载资源。而当缓存失效时,浏览器会向服务器发出请求对资源进行验证(关于验证请参考下一节),并且获取新的缓存“保质期”。

Cache-Control响应头除了可以通过max-age指定缓存时效外,还可以指定很多关于缓存的特性,其中用处最大的是下面3个

  • public/private,标明只是浏览器进行缓存(private)还是代理也进行缓存(public
  • no-cache,表示必须先和服务器对缓存的资源进行验证,再确定是使用缓存还是使用更新的资源
  • no-store,表示直接禁止浏览器和代理对资源进行缓存

从上面的描述中可以看出,Cache-Control响应头比Expires响应头功能要强大很多。

现在所有浏览器都支持HTTP/1.1,在实际中推荐使用Cache-Control进行设置,除了它功能更强大外,更重要的是有以下优势:

  1. 使用相对时间,不存在考虑服务器和浏览器的时间是否一致的问题
  2. 可以分散验证/更新请求,避免当缓存过期时,大量的验证请求对服务器造成压力

    max-age指定的缓存过期时间是相对于用户第一次请求资源的时间的,这个时间相对分散,结果是当缓存过期时,对资源的验证请求也相对分散。但是Expires指定的是一个确定的时间,如果用户量达到一定数量,缓存过期时,可能引发大量的验证请求,带来巨大的服务器压力

  3. 缓存过期时,如果资源没有修改,不用更改响应头

    缓存失效时,验证缓存后会得到新的“保质期”,max-age因为使用的是相对时间,无需更改就可以使用。但是Expires使用的绝对时间,必须在过期后进行修改,如果忘了修改将会导致后续访问时每次都进行验证

对于变化小的资源,指定的保质期越长越好,但是不可以超过一年,否则会因为违反规范而无效。

To mark a response as “never expires,” an origin server sends an Expires date approximately one year from the time the response is sent. HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future.

指定缓存过期时的验证条件

当缓存过期时,浏览器会向服务器发出请求对资源是否已经更新进行验证,如果没有更新,服务器会响应304和新的“保质期”,浏览器将继续使用缓存;如果已经更新,服务器会响应200,更新的资源和新的“保质期”。

指定验证条件有多种方式,最常用的是If-Modified-SinceIf-None-Match请求头,其中If-Modified-Since指定资源的上次修改时间(通过请求资源时的响应头Last-Modified获取),而If-None-Match指定资源的验证令牌(通过请求资源时的响应头ETag获取)。

这两种方法都有各自的问题

  • If-Modified-Since检查的是资源的修改时间,但是修改时间改变不代表资源的内容改变,比如对一个文件执行touch命令
  • 默认生成的ETag通常含有与资源所在机器特定的信息,这使得资源存储在不同服务器上时,即使内容相同也会被验证为已经更新

既然两个都有问题,那我们应该使用哪一个呢?

这需要回到我们要验证的是什么这个问题上,我们真正要验证的是资源的内容是否改变,所以使用ETag更好。

至于默认生成的ETag含有机器特定信息的问题可以通过不使用默认的ETag避免,比如使用资源的MD5校验和作为ETag的值。

更新过时的缓存

缓存在失效前是不会向服务器发出请求的,这就带来了一个问题:如果在此期间资源有更新,用户将使用过期的资源。

解决这个问题的常用方法是在资源名中包含版本号或者指纹,当资源有更新时就更新资源名。

没有最佳缓存方案

和很多性能优化指标一样,缓存并不存在最佳的方案,具体的缓存方案需要根据我们资源更新要求等因素共同确定。