
先来一个总体流程图
大致过程如下:
解析URL,检测浏览器缓存
DNS解析:将域名解析成IP地址
TCP三次握手建立连接
发送http请求
服务器处理请求并返回HTTP报文
浏览器解析渲染页面
断开连接:TCP四次挥手
那么,天天输入的URL到底是个啥?
- URL(Uniform Resource Locator),顾名思义,统一资源定位符,也就是我们俗称的网址,它是通过一种语法规则让我们能从互联网上得到资源位置和访问这些资源的方法。
那么它的语法规则是啥样嘞
- <协议>://<主机>:<端口>/<路径>?<参数>#<锚点>
解析URL
如果我们输入的是一个非URL结构的字符串,浏览器会用默认的搜索引擎搜索该字符串。输入格式正确的话,浏览器会解析出协议、主机、端口、路径、参数、锚点等信息,并构造一个HTTP请求。
在浏览器发送请求之前,为了减少发送的请求数,更快获取数据呈现给用户,会有一个命中强缓存和协商缓存的策略。
浏览器缓存
啥是浏览器缓存啊
跟图示一样,浏览器缓存其实就是浏览器根据发起或响应请求头中指定的缓存策略将得到的响应数据保存在本地,方便将来直接获取的一种行为。
资源保存到本地究竟是保存到哪里?
由图片可以看出,缓存位置主要分memory和disk两种,但并不是只有这两种方式
Service Worker
Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
Memory Cache
Memory Cache是将资源缓存到内存中,读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭Tab页面,内存中的缓存也就被释放了。
Webkit早已支持memory Cache,目前Webkit资源分为主资源和派生资源两类。主资源包括HTML页面或者下载项;派生资源包括HTML页面内嵌的图片或脚本链接,分别对应代码中MainResourceLoader和SubresourceLoader两个类。虽然Webkit支持memoryCache,但是也只是针对派生资源,它对应的类为CachedResource,用于保存原始数据(比如CSS,JS等,以及解码过的图片数据。
需要注意的事情是,内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。
Disk Cache
Disk Cache是将资源缓存到硬盘中,虽然读取速度慢,但是容量和存储时效都比Memory Cache好。
它也只能存储一些派生类资源文件,一般存储CSS文件。
因为css文件加载一次就可以渲染出来,我们不会频繁的去读取它,所以它不适合存在内存当中。
但js之类的脚本随时可能会执行,如果脚本在磁盘中,我们在执行脚本的时候需要从磁盘取到内存中,这样IO开销比较大,有可能导致浏览器失去响应。
Push Cache
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
- 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
- 可以推送 no-cache 和 no-store 的资源
- 一旦连接被关闭,Push Cache 就被释放
- 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
- Push Cache 中的缓存只能被使用一次
- 浏览器可以拒绝接受已经存在的资源推送
- 你可以给其他域名推送资源
访问缓存优先级
电脑缓存是当cpu在读取数据的时候,先是从缓存文件中查找,然后找到之后会自动读取,再输入到cpu进行处理,当然如果没有在缓存中找到对应的缓存文件的话,那么就会从内存中读取并且传输给cpu来处理。当然这样的话需要一定的时间所以会很慢。等cpu处理之后,就很快把这个数据所在的数据块保存在缓存文件中,这样的话在以后读取这项数据的时候就直接在缓存中进行,不要重复在内存中调用并读取数据了。
浏览器访问缓存也有优先级
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
强缓存
之前就一直知道强缓存和协商缓存的鼎鼎大名,但一直没有花时间去真正的琢磨它。现在,咱就来好好学学。
强缓存就是让浏览器缓存该请求的响应资源,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
强缓存又分为两种Expires和Cache-Control,两者同时存在的话,Cache-Control优先级高于Expires;
Expires
- 版本:HTTP/1.0
- 来源:存在于服务端返回的响应头中
- 语法:Expires: Wed, 22 Nov 2019 08:41:00 GMT
- 使用方式: 需要和Last-modified结合使用
- 缺点:Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效,Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
Cache-Control
- 版本:HTTP/1.1
- 来源:响应头和请求头
- 语法:Cache-Control:max-age=3600
- 使用方式: 可以组合使用多种指令
- 缺点:时间最终还是会失效
协商缓存
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
Last-Modified(响应头),If-Modified-Since(请求头)
在浏览器第一次给服务器发送请求后,服务器会在响应头中加上Last-Modified这个字段。 浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。 服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间Last-Modified对比,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。
但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag。
ETag(响应头)、If-None-Match(请求头)
ETag是服务器根据当前文件的内容,给文件生成的唯一标识,只要资源有变化,Etag就会重新生成。服务器通过响应头把这个值给浏览器。 浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。
服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
对比
- 首先在精确度上,Etag要优于Last-Modified。
Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
- 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
- 第三在优先级上,服务器校验优先考虑Etag
实际场景应用缓存策略
频繁变动的资源
Cache-Control: no-cache
对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
不常变化的资源
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 和最新内容。