详解页面静态资源的缓存策略,搞懂强缓存和协商缓存再做性能优化

这篇文章我们来聊一聊静态资源的缓存策略,如果你准备去做页面的优化,那么这个知识点你就必须得了解。

首先明确一下静态资源的概念,静态可以理解为不变的。页面中像js、css、img等文件都是静态文件,因为此类文件你上线什么内容,所有的用户都会获取一样的内容,它是不会变化的,基于这样的特点,这样的文件可以做缓存,提高加载速度。

像一般的html文件,在你开发完还需要套各类模板,因为数据不同,展现的内容也不同,最典型的就是头条的页面,千人千面,每个页面都不一样,你很难从整体上做缓存,所以这样的文件不能算是静态资源。

回想一下,在你开发完一个项目上线时,一般都是先上静态资源后上模板,二者的上线方式会有所不同。静态资源一般都会推到cdn服务器上去,而模板都是上到后端服务的机器上,前者就是想利用cdn的缓存策略让用户有更好的页面加载体验。

强缓存

1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在响应头中加上Expires,如:

2、浏览器在接收到这个资源后,会把这个资源连同所有响应头一起缓存下来;

3、浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,拿出它的Expires跟当前的请求时间比较,如果请求时间在Expires指定的时间之前,就能命中缓存,否则就不行;

4、如果缓存没有命中,浏览器会将请求发往服务器,Expires Header在重新加载的时候会被更新。

Expires是较老的强缓存管理header,由于它是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改下客户端时间,就能影响缓存命中的结果。所以在http1.1的时候,提出了一个新的header,就是Cache-Control,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示,如:Cache-Control:max-age=315360000,它的缓存原理如下:

1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在响应头加上Cache-Control的header,如:

2、浏览器在接收到这个资源后,会把这个资源连同所有响应头一起缓存下来;

3、浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,根据它第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行。

4、如果缓存没有命中,浏览器直接从服务器加载资源时,Cache-Control Header在重新加载的时候会被更新。

Cache-Control描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires,Cache-Control的缓存管理更有效,安全一些。这两个header可以只启用一个,也可以同时启用,当响应头中,Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires。

协商缓存

如果没有命中强缓存,请求就会来到服务器,服务器检查HTTP请求头是否包含缓存验证信息,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串。协商缓存利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的,原理如下:

1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在响应头加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间;

2、浏览器再次跟服务器请求这个资源时,在请求头上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值;

3、服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间作对比判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容。如果有变化,就正常返回资源内容。当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变。

4、浏览器收到304的响应后,就会从缓存中加载资源。

5、如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值。

【Last-Modified,If-Modified-Since】都是根据服务器时间返回的header,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header配合起来管理协商缓存是非常可靠的,但是有时候服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来,而当这种情况出现的时候,就会影响协商缓存的可靠性。所以就有了另外一对header来管理协商缓存,这对header就是【ETag、If-None-Match】,它们的缓存管理的方式如下:

1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在响应头中加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充Last-Modified的问题;

2、浏览器再次跟服务器请求这个资源时,在请求头上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值:

3、服务器再次收到资源请求时,根据浏览器传过来的If-None-Match和再根据资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,响应头中还会把这个ETag返回,即使这个ETag跟之前的没有变化。

4、浏览器收到304的响应后,就会从缓存中加载资源。

【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】一般都是同时启用,这是为了处理Last-Modified不可靠的情况。有一种场景需要注意:

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

总结

这篇文章主要介绍了两种缓存策略——强缓存和协商缓存,其实它是从浏览器地址栏输入url到显示页面过程中的一部分。几年之前,我在我的博客中就已经详细介绍过了,这次再拿出来说,一是为了分享给更多的同学,二是为了下一篇开发一个静态请求处理的node服务中间件做理论支撑。

喜欢我的文章就关注我吧,有问题可以发表评论,我们一起学习,共同成长!

原文链接:,转发请注明来源!