缓存这功能随处可见,深入了互联网的方方面面,每款软件都有充分的使用缓存的特性,缓存能够极大的节省互联网资源,提升传输效率,增强使用体验。
浏览器是访问网站的工具之一,作为互联网的主要门户,浏览器也会将网站发送的数据保存在缓存文件中,等到下次访问时又会拿出来,当然前提是缓存没有过期。网站的数据是否缓存到浏览器,缓存多久,都要靠网站与浏览器相互协商约定好,不能网站说一套浏览器做一套。当然是否缓存还得看用户的意见,用户不让缓存,那也是强龙斗不过地头蛇的事。
缓存区域
Service Worker
一个基于 web worker 运行在浏览器背后的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程,如果网站中注册了 Service worker 那么它可以拦截当前网站所有的请求,进行判断(需要编写相应的判断程序),如果需要向服务器发起请求的就转给服务器,如果可以直接使用缓存的就直接返回缓存不再转给服务器,从而大大提高浏览体验。使用 Service Worker 的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker 是区别于主线程的,所以 Service worker 运行在 worker 上下文,不能访问 DOM,并且同步API(如 XHR 和 localStorage )不能在 Service worker 中使用,其生命周期与页面无关,在某些浏览器的特殊设置下,Service worker 是不可用的。当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。本文只讲一些 Service worker 的概念性内容,更加深入的还是建议去了解有道团队一篇关于Service worker 的详解。
特点:
- 独立的线程,完全异步,不接受同步;
- 基于 web worker 的,不能访问 DOM;
- 可用于代理拦截,过滤请求;
- 安全,可以自由的控制持续性的缓存。
- 是 PWA(渐进式 web app)的核心;
- 有自己的生命周期,与网页隔离,可以做一些离线的操作。
memory cache
就是将资源缓存到内存中,等待下次访问时不需要重新下载资源,而直接从内存中获取。Webkit 早已支持 memory cache。目前 Webkit 资源分成两类,一类是主资源,比如 HTML 页面,或者下载项,一类是派生资源,比如 HTML 页面中内嵌的图片或者脚本链接,分别对应代码中两个类:MainResourceLoader 和SubresourceLoader 。虽然 Webkit 支持 memoryCache,但是也只是针对派生资源,它对应的类为 CachedResource,用于保存原始数据(比如JS 等),以及解码过的图片数据。内存里的数据是短期临时的,受到浏览器分配内存大小的限制,不管有没有保存下来,退出页面进程时或者在内存中存放过久时这类数据就会被清除。memory cache 机制保证了一个页面中相如果有两个同的请求(例如两个外部资源链接有相同的地址)都实际只会被请求最多一次,避免浪费。
特点:
- 容量受到分配内存的大小限制;
- 临时的,时间短,受到新缓存的影响,内存缓存越多、存放过久或者关闭页面就会清除;
- 速度快,因为直接与内存打交道,没有 io 输出;
- 能够有效的防止多次请求相同的资源。
disk cache
又叫 HTTP 缓存,就是将资源缓存到磁盘中,等待下次访问时不需要重新下载资源,而直接从磁盘中获取,是可以操控的缓存,它的直接操作对象为 CurlCacheManager。存在磁盘中的数据是持久化的,除非磁盘损坏或者磁盘上的数据被清除。
特点:
- 硬盘空闲缓存空间有多大,就有多大的容量;
- 持久的,不受其他缓存影响;
- 速度较 memory cache 慢,依赖 io 输入输出;
- 如果网页数据要求缓存但是没有存在内存中,多半就存在 disk cache 中。
缓存类型
强缓存
从名称上理解,是服务端发送的数据被浏览器有强度的、长久的缓存为文件的意思。在 chrome 浏览器控制台的 Network 选项中可以看到该请求返回200的状态码,并且 Size 一栏显示 disk cache 或 memory cache 的。
![](https://img-blog.csdnimg.cn/be0101e6b0d244589d4c9a10d3b1190a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZmp5XzEwMTI=,size_20,color_FFFFFF,t_70,g_se,x_16)
![](https://img-blog.csdnimg.cn/35bda262ac04443281167033ef273bb5.jpg?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZmp5XzEwMTI=,size_20,color_FFFFFF,t_70,g_se,x_16)
Expires:time
HTTP/1.0 中规范的指令,服务器传这个指令给浏览器,表示缓存在具体时间值 time 的时候过期失效,就要重新获取。Expires 指令是会对比客户端与服务器的时间,所以当本地客服端时间被修改(比如带着电脑出国换了时区),那么 Expires 指令将失去作用。
Pragma:no-cache
HTTP/1.0 中规范的指令,禁用缓存,只有 IE 浏览器识别,其他浏览器都用 Cache-Control:no-store,Pragma 的优先级比 Expires 高,Pragma 与 Expires 同时存在时以 Pragma 为准。
Cache-Control:max-age=60
HTTP/1.1 中规范的指令,浏览器收到这个指令时,会把服务器传的数据保存起来,保存的时间长度是60秒,优先级比 Pragma 和 Expires 指令高,也就是 HTTP/1.0 的指令和 Cache-Control 都有值的时候优先以 Cache-Control 为准。在使用 Cache-Control 时,其标准的格式为 Cache-Control : cache-directive,cache-directive 在作为请求头和响应头时都分别有不同的取值。
![](https://img-blog.csdnimg.cn/9fbfc2aa44154c1f8b8ab8f78ecf4b68.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAZmp5XzEwMTI=,size_20,color_FFFFFF,t_70,g_se,x_16)
实际上为了兼容 HTTP/1.0 和 HTTP/1.1,一般服务器都会配置全部的缓存指令,完整的配置请查阅 HTTP/1.1 Header Field。
协商缓存
协商缓存(HTTP/1.1)的前提是要有缓存,要配合 Cache-Control 使用,当浏览器本地缓存的数据要过失效期了,就会带着这些缓存数据去跟服务器协商讨论,还能不能继续使用这些缓存数据。如果可以,服务器会返回 304 状态码带回 Not Modified 并重定向请求带回新的缓存指令,浏览器就能更新缓存时间继续使用缓存的内容;如果不可以,服务器会返回一个 200 状态码的响应内容并设置新的缓存。
参与协商缓存的指令有 Last-Modify / If-Modified-Since / If-Unmodified-Since(或 ETag / If-Match / If-None-Match) 。
Last-Modify 是在浏览器第一次请求一个资源时,在服务器返回的响应头中带给浏览器的,标识着这个资源最后的修改时间是否与服务器的一致(If-Unmodified-Since 是比较是否不一致)。浏览器会把这个参数对应的值保存下来,下次请求该资源时会在请求头中把 Last-Modify 的值放在 If-Modified-Since 中传给服务器,服务器再根据 If-Modified-Since 的值去判断浏览器缓存的资源有没有过期,然后走返回 304 还是返回 200 的那条路子。Last-Modify / If-Modified-Since 的更新在于每次资源的修改,所以只要资源修改了,浏览器的 If-Modified-Since 与服务器的值就会对不上。但是在资源修改了后又还原的话,以人类的角度去看这个资源在浏览器的缓存还是可用的,因为他们的内容一模一样,然而因为资源的修改导致浏览器的 If-Modified-Since 与服务器的值对不上,所以服务器认为该资源是需要重新获取的,这种场景在大批量的数据误操作然后需要恢复中经常可见(比如黑客的攻击等等)。
特点:
- Last-Modify 的精度是秒,是根据资源修改的时间来标识的,适合用于不经常更新的资源;
- 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modify 被修改;
- 因为 Last-Modify 的精确到秒,所以在一秒以下的资源操作是无法引发 Last-Modify 的更新的,这会导致服务器的误判;
- 在不同的资源服务器上,受到网络传输的影响,相同资源的 Last-Modify 可能不同(比如负载均衡)。
ETag / If-Match 跟 Last-Modify / If-Modified-Since 不同,ETag 是资源的唯一标识码,就像资源的身份证号码一样,具有唯一性。服务器把资源的 ETag 传给浏览器,浏览器再次请求该资源时会传回 If-Match 给服务器(If-Match 是比较 ETag 值是否一致,If-None-Match 是比较 ETag 值是否不一致),服务器在校验 If-Match 字段的值时,如果资源对应不上,就会将新的 ETag 响应给浏览器,响应的状态也是返回 304 或者 200。
特点:
- ETag 的是根据资源的内容来标识的,每次资源的更新必定引发 ETag 的更新;
- 只要生成 ETag 的算法是可靠的,那么 ETag 就是可靠的,将 ETag 用于页面,就能避免页面的重复请求;
- ETag 的生成比 Last-Modify 更加消耗性能,虽然每个资源消耗的性能比较小,但是一旦访问量多起来,就很可观;
- ETag 的值有强弱之分,弱 ETag(Weak Tag)只能作模糊匹配,在差异达到一定阈值时才起作用,比如 W/"xxxxx" 格式的;
- 服务器校验优先考虑 ETag,再考虑 Last-Modify。
三级缓存
*三级缓存有很多种,这里指的是浏览器请求资源时的缓存访问流程。
- 请求资源时优先访问 memoryCache;
- memoryCache 没有找到该资源,去 diskCache 中找;
- 强缓存优先于协商缓存,当存储缓存的地方中强缓存都没有了就开始协商缓存发起网络请求;
- 将协商缓存后的资源缓存到 memoryCache 和 diskCache 中。
资源缓存实践
- 部分浏览器在点击刷新按钮或按F5时会自行加上“Cache-Control:max-age=0”请求字段;
- html 文件中使用 meta 标签也能控制缓存,但是服务器不能解析 html 文件里的内容,并且大部分浏览器也不识别,所以控制缓存主要使用请求头传参;
- 对于长时间不作修改的静态资源,可以设置 Cache-Control(如 Cache-Control:max-age=30000000)和 Expires(如 Expires 设置为当前的 GMT 时间的一个月后)为较长的时间;
- 经常变动的资源使用算法生成 ETag;
- 针对不同客户端浏览器使用不同的缓存策略,响应头使用 vary 字段来使代理服务器区别缓存;