源宝导读:互联网Web应用大行其道的今天,浏览器已经成为Web应用运行的重要平台。而Web应用对浏览器缓存机制的高效利用,可以大幅提升应用性能和用户体验。本文将对浏览器缓存机制进行系统化的梳理,分享我们的经验。
一、背景
计算机读取数据的速度逐步递减:缓存>内存>硬盘, 为了提高数据处理性能,其中一个策略就是对缓存的高效利用。类似的,利用浏览器缓存是Web应用性能优化的重要手段之一。优先的缓存策略可以缩短网页请求资源的距离,减少延迟,并且可以缓存文件进行复用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为“发起请求”、“后端处理”、“浏览器响应”3个阶段,对缓存的高效利用,可以大幅提升前2步的性能。
二、对浏览器缓存机制的研究
2.1、缓存类型
Memory Cache
memory cache 是内存中的缓存,主要包含当前页面中已经抓取到的资源。Disk Cache
disk cache 是硬盘中的缓存。
通常浏览器缓存分为2种:强缓存和协商缓存,并且都是通过设置HTTP Header实现的。
2.2、缓存过程
浏览器每次发起请求,都会先在浏览器缓存中查找请求的结果和标识。浏览器每次拿到返回的请求都会将该结果和缓存标识存入缓存中。
先检查内存,如果有,直接返回。
如果内存没有,则去硬盘获取,如果有直接返回。
如果硬盘也没有,那么就进行网络请求。
加载到的资源缓存到硬盘和内存。
2.3、强缓存
强缓存:不会向服务器发生请求,直接从缓存中读取资源。在 chrome 控制台可以看到该请求返回 200 的状态码,并且 size 显示 from disk cache 或 from memory cache。
强缓存可以通过设置HTTP Header 实现:expires 和 Cache-Control。
expires
缓存过期时间,用来指定资源到期时间,是服务器的具体的时间点。expires=max-age + 请求时间,需要和 last-modified 结合使用。expires 是 HTTP/1 的产物,受限于本地时间,修改本地时间,可能会导致缓存失效。cache-control
cache-control 可以在请求头或响应头中设置,可以组合使用多种指令。public:所有内容都会被缓存。
private: 所有内容只有客户端可以缓存。
no-cache: 不是说浏览器不使用缓存,而是先确认下数据和服务器是否一致。也就是使用 etag 或者 last-modified 控制缓存。
no-store:所有内容都不缓存,不使用强缓存也不使用协商缓存。
max-age: 表示缓存内容在多久后失效。
s-maxage: 同 max-age,但是只在代理服务器生效(比如 cdn)。
max-stale: 能容忍的最大过期时间。
max-fresh: 能容忍的最小新鲜度。
expires 是 HTTP 1.0 的产物,cache-control 是 HTTP 1.1 的产物,两者同时存在时,cache-control 优先级更高。从下图中可以发现强缓存浏览器加载速度超快,没有与服务器发生交互。
2.4、协商缓存
强缓存判断缓存是否超出某个时间或者范围,但是不关心服务器是否已经更新内容。为了获取服务器更新,就需要使用协商缓存。
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器决定是否使用缓存。主要有 2 种情况:
协商缓存生效,返回304和 no modified。
协商缓存失效,返回200和请求结果。
协商缓存通过 2 中 HTTP Heeader 设置:last-modified 和 etag。
2.4.1、last-modified和if-modified-since
浏览器在第一次访问资源时,服务器返回资源的同时,在 响应头添加 last-modified,值是这个资源在服务器上的最后修改时间。
浏览器下一次请求,服务器检测到 last-modified 的值,再添加个 if-modified-since 值是 last-modified 的值。服务器再次收到请求,会根据 last-modified-since 的值与服务器中这个资源的最后修改时间对比。相同 304,如果 if-modified-since 的值比服务器上资源最后修改时间小,返回新的资源和 200。
last-modified 存在的弊端:
如果在本地打开缓存文件,即使没有改内容,last-modified 也会被修改,服务器就不能命中缓存。
last-modified 只能以秒计时,如果在不可感知的时间内修改,服务器还是会认为命中缓存了。
有的服务器文件没有修改,可能也会更新修改时间。
2.4.2、etag和if-none-match
etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成,通常是文件的内容md5加上修改时间算法计算得出),只要资源有变化,etag 就会重新生成。
浏览器下一次请求时,只需要把上一次 etag 的值写到 if-none-match 中,服务器再比较 if-none-match 和当前资源的 etag 是否一致。
从下图中可知,协商缓存即使没有失效,也要请求一次服务器。
2.5、对比
精度上,etag优于last-modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
在性能上,Etag肯定要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
在优先级上,服务器校验优先考虑Etag。
2.6、小结
强缓存优先于协商缓存,若强缓存生效则直接使用缓存,若不生效使用协商缓存(last-modified/if-modified-since 和 etag/if-none-match)。协商缓存由服务器决定是否使用缓存。使用缓存返回 304,不使用 返回 200 和 新的资源。用一张图片总结:
三、应用
3.1、频繁变动的资源
cache-control: no-cache
通过设置 no-cache 使浏览器每次都请求服务器,然后配合使用 etag 或者 last-modified 协商缓存。
3.2、不常变化的缓存
cache-control: max-age=60480000
通过设置一个很大的过期时间,浏览器就会使用强缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
3.3、SPA页面
现在前端三大框架项目通过打包工具打包出来的资源名通常都是资源的md5值计算出来的hash,如果资源不变hash都不会变,可以让我们有效的利用缓存。但是通常我们的静态资源都是通过放在nginx下,而一般我们的nginx项目对静态资源的缓存配置缓存了html页面的静态资源,大概配置如下:
location ~\.(css|js|png|jpeg|jpg|wepg|mp3|mp4|ogg|html)$ {expires 7d;
}
这样会导致如果我们更改了文件内容,导致hash变化,但是由于html还是取得缓存,这样我们在发版的时候,缓存还未失效的用户可能访问的还是旧的缓存。导致用户不用第一时间看见最新版本,或者存在某些引用的资源缓存已经失效,但是文件内容被更改,此时资源路径可能在服务器上已经不存在,就会导致资源404。
其实,通常SPA的首页index.html一般非常小,而其它页面都是前端路由,只是JS文件,我们针对SPA项目,可以不缓存html资源,针对其它文件则使用强缓存即可,这样可以提高页面加载速度。
location ~\.(html)$ {add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
location ~\.(css|js|png|jpeg|jpg|wepg|mp3|mp4|ogg)$ {expires 7d;
}
------ END ------
作者简介
李同学: 研发工程师,目前负责天际-移动平台的研发工作。
也许您还想看
记AWSS3在iOS端的一次改造事件
明源云创CI/CD技术演进
微前端架构在容器平台的应用
AI云店小程序演变之路
天眼探针基于rrweb实现前端异常视频录制与回放功能