良好的缓存策略可以降低资源的重复加载提高网页的整体加载速度
浏览器与服务器通信的方式为应答模式,即:浏览器发起HTTP请求 – 服务器响应该请求。
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。如果缓存中有且未失效,则直接从缓存取数据,否则就重新发起请求。 浏览器每次拿到返回的请求结果都会根据缓存标识决定是否缓存,是则将请求结果和缓存标识存入浏览器缓存中。
根据是否需要向服务器重新发起HTTP请求将缓存过程分为强制缓存和协商缓存。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:
-
不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致)。
-
存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存(暂不分析)。
-
存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果。
那么强制缓存的缓存规则是什么? 当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Control优先级比Expires高。
缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。 Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
Expires的缺点?
Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
public:所有内容都将被缓存(客户端和代理服务器都可缓存)。
private:所有内容只有客户端可以缓存,Cache-Control的默认取值。
no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定。
no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存。
max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效。
s-maxage(单位为s):同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。
max-stale:能容忍的最大过期时间。
min-fresh:能够容忍的最小新鲜度。
可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。
from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
-
内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
- 时效性:一旦该进程关闭,则该进程的内存则会清空。
-
硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
-
浏览器会把哪些文件丢进内存中?哪些丢进硬盘中? (from memory cache / from disk cache)
对于大文件来说,大概率是不存储在内存中的,反之优先。
当前系统内存使用率高的话,文件优先存储进硬盘。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢? 此时我们需要用到协商缓存策略。
协商缓存需要配合强缓存使用,如果不启用强缓存的话,协商缓存根本没有意义。 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
1.协商缓存生效,返回304。
2.协商缓存失效,返回200和请求结果结果。
协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先 级比Last-Modified / If-Modified-Since高。
Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间。
If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since 字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资 源无更新,可继续使用缓存文件。
但是 Last-Modified 存在一些弊端: 如果在服务器上面打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内(如ms)修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成), 只要资源有变化,Etag就会重新生成。
If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据 If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200。
注:Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
Etag和Last-Modified两者之间对比:
- 在精确度上,Etag要优于Last-Modified。 Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的 Last-Modified也有可能不一致。
- 在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
- 在优先级上,服务器校验优先考虑Etag
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决 定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。
如果什么缓存策略都没设置,那么浏览器会怎么处理? 对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
1.频繁变动的资源
Cache-Control: no-cache
对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数 据大小。
2.不常变化的资源
Cache-Control: max-age=31536000
通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。 在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。
所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:
- 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
- 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
- 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。