缓存原理
- 先去内存查找,找到直接加载
- 内存找不到,硬盘中找,找到直接加载
- 硬盘找不到就发起网络请求
- 请求获取的资源缓存到硬盘和内存
缓存的作用
- 减少重复数据请求,避免通过网络再次加载资源,节省流量。
- 降低服务器的压力,提升网站性能。
- 加快客户端加载网页的速度, 提升用户体验。
缓存类型
- 数据库缓存: 将查询后的数据放在内存中进行缓存,下次再查询,直接从内存缓存中获取,提高响应速度
- CDN 缓存 : 发送web请求,cdn计算出路径短且快的路。管理员部署,把经常访问的放入cnd加快响应
- 代理服务器缓存: 与浏览器缓存机制类似,但代理服务器面向更广大的群体,规模更大,为一群用户服务。
- 浏览器缓存:每个浏览器都实现HTTP缓存。浏览器使用HTTP协议与服务器交互,根据与服务器约定的规则进行缓存工作
浏览器缓存位置:
Server Worker
Server Worker 是运行在浏览器背后的独立线程,属于外建”的缓存机制,脱离浏览器窗体,无法访问DOM。
借鉴了 Web Worker,主要用于实现:离线缓存、消息推送、网络代理等。
-
Server Worker 涉及请求拦截,传输协议必须是HTTPS 来保障安全。
-
Server Worker 可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,
-
且是 持续性 的缓存,这是与其他内建缓存机制的区别。
Memory Cache
内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等,
占据该进程一定的内存资源,但是缓存持续性很短,会随着进程的释放而释放。
- 读取速度快
- 时效性短,关闭 Tab 页面时内存中的缓存随即被释放了
- preloader 和 preload 请求资源都能进入 memory cache
preloader
preloader 是页面优化的常见手段之一,它的主要作用是 在浏览器打开一个网页的时候,能够一边解析执行 js/css,一边去请求下一个资源,而这些被 preloader 请求来的资源就会被放入 memory cache 中,供之后的解析执行操作使用。
preload
preload 和 preloader 长得非常相似,它能够显式指定预加载的资源,这些资源也会被放进 memory cache 中
<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">
//当浏览器解析到这行代码就会去加载 href 中对应的资源但不执行,待到真正使用到的时候再执行。
Disk Cache
硬盘中的缓存,在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的,绝大部分的缓存都来自 Disk Cache。
HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。
硬盘缓存比内存缓存读取速度慢,读取需要对硬盘进行I/O操作,会导致重新解析缓存内容,造成读取路的复杂。
-
持续时间长,是实际存在于文件系统中的缓存
-
比内存缓存慢,但是优势在于存储容量大
那些文件会进硬盘内存?
使用率高、文件大优先进硬盘
大文件或者内存使用高的情况下,资源会被丢进磁盘
当前系统内存使用率高的话,文件优先存储进硬盘
Memory Cache 和 Disk Cache 区别
| Memory Cache | Disk Cache | |
|---|---|---|
| 相同点 | 只能存储一些派生类资源文件 | 只能存储一些派生类资源文件 |
| 不同点 | 退出Tag进程时数据会被清除 | 退出Tag进程时数据不会被清除 |
| 存储资源 | 一般 JS、图片、字体 | 一般非脚本如 css 等 |
CSS文件加载一次就可以渲染,不会频繁的读取,存储在Disk Cache;
js脚本可能会随时会被执行,存储在Memory Cache,若存储在硬盘中,会因为I/O开销大导致浏览器失去响应。
普通html和js文件使用内存缓存, css文件和大的文件使用硬盘缓存,
Push Cache
推送缓存,是HTTP/2的内容,并没有严格执行HTTP头部的缓存指令。在Server Worker、Memory Cache、Disk Cache都没有命中的时候,它会被使用。在Session中存在,Session结束就会被释放,缓存时间短暂。
浏览器缓存过程:
浏览器缓存分为 强缓存和协商缓存, 内容将被缓存在 memory 和 disk 中,而大部分 web 默认使用协商缓存
强缓存
浏览器并不会向服务器发送任何请求,直接从本地缓存中读取文件,命中缓存返回200,无法感知资源是否更新
强制缓存通过在 HTTP 响应中设置 "Expires" 或 "Cache-Control" 头来实现。
Expires 头表示缓存的过期时间,浏览器在该时间之后会再次向服务器发送请求。
200 from memory cache: 不访问服务器!直接从内存中获取。浏览器关闭,资源将被释放,数据将不存在。下次打开页面不是from memory cache
200 from disk cache: 不访问服务器!直接从硬盘中获取。浏览器关闭,资源不会被释放,数据依然存在,下次打开页面依然是from disk cache
协商缓存
向服务器发起请求,命中缓存返回304,失效返回200和请求结果
协商缓存是另一种缓存策略,它使用服务器和浏览器之间的协商,由服务器来决定是否使用缓存,在协商缓存的过程中客户端与服务器之间存在一次通信。
当浏览器请求一个页面时,它会在请求中附加 "If-Modified-Since" 和 "If-None-Match" 头。
这些头中包含了浏览器上一次请求的时间和资源的 ETag 值。
last-modifiend => if-modifiend-since,时间不一致,则说明资源更新了
etag 这个资源唯一标识 => 不一致 ,则资源更新了
向服务器发起请求时,服务器根据这个请求的 Request header 的一些参数来判断是否命中协商缓存;
- 资源未更新: 命中缓存返回304状态码并且会带上新的response header告诉浏览器可以从缓存中读取资源。
- 资源失效:没有命中返回200状态码和请求的结果。
当浏览器再次访问资源
浏览器在第一次请求后缓存资源,再次请求时:
- 看是否命中强缓存,若强缓存未过期则使用本地缓存
- 若没有命中强缓存(表示强缓存过期),则发送请求到服务器看是否命中协商缓存
- 命中协商缓存后,根据请求返回状态码进行判断
-
- 服务器返回304告诉浏览器可以使用本地缓存
- 服务器返回200 以及最新的资源,则使用请求资源
用户行为对缓存的影响
| 用户操作 | Expires/Cache-Control | Last-Modied/Etag |
|---|---|---|
| 地址栏回车 | 有效 | 有效 |
| 页面链接跳转 | 有效 | 有效 |
| 新开窗口 | 有效 | 有效 |
| 前进回退 | 有效 | 有效 |
| F5刷新 | 无效(有争议,不同浏览器反馈不一致) | 有效 |
| Ctrl+F5强制刷新 | 无效 | 无效 |
当f5刷新网页时,跳过强缓存,检查协商缓存
当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存
浏览器地址栏中写入URL、页面链接跳转、新窗口打开,不用请求直接走缓存,最快的速度
prefetch、preload
首屏时间是衡量网站性能的重要指标,所以在页面渲染的过程中对资源加载进行优化就需要用到以下两个属性:
preload 加载资源一般是当前页面需要的,prefetch 一般是其它页面将来可能用到的资源。
- preload 是告诉浏览器预先请求当前页面需要的资源(关键的脚本,字体,主要图片等)。
- prefetch 应用场景稍微又些不同 —— 用户将来可能跳转到其它页面需要使用到的资源。
在preload或prefetch的资源加载时,没有同域名的限制且两者均存储在http-cache。
如果资源是可以被缓存的,那么其被存储在http-cache中等待后续使用(存在有效的 cache-control 和 max-age);如果资源不可被缓存,那么其在被使用前均存储在memory cache;
prefetch 预取缓存
html代码的link标签带有prefetch,prefetch是预加载的一种方式,被标记为prefetch的资源会在浏览器会在空闲时加载,优先级低,最后加载。
链接预取是一种浏览器机制,其利用浏览器空闲时间来下载或预取用户在不久的将来可能访问的文档。
网页向浏览器提供一组预取提示,并在浏览器完成当前页面的加载后开始静默地拉取指定的文档并将其存储在缓存中。当用户访问其中一个预取文档时,便可以快速的从浏览器缓存中得到。
当页面跳转时,未完成的prefetch请求不会被中断。
<!-- link 模式 -->
<link rel="prefetch" href="/path/to/style.css" as="style">
<!-- HTTP 响应头模式 -->
Link: <https://example.com/other/styles.css>; rel=prefetch; as=style
preload
perload解析当前页面需要用到的资源
它通过声明向浏览器声明一个需要提交加载的资源,只有当资源被使用时立即执行,就无需等待网络的消耗。
- 优先级较高;不会阻塞渲染和document的onload事件
- preload对资源的加载和执行是分离的。即preload会预加载相应的脚本代码,待到需要时自行调用
它可以通过 Link 标签进行创建:
<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">
//当浏览器解析到这行代码就会去加载 href 中对应的资源但不执行,待到真正使用到的时候再执行。
<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
</script>
另一种方式方式就是在 HTTP 响应头中加上 preload 字段:
prefetch 与 preload 的区别
共同点:
- 没有域名限制,可获取跨域资源
- 获取到的资源被储存在 memory cache 中
不同点:
prefetch
-
主要用于加载将来页面可能需要的资源;
-
在页面关闭时请求不会中断,仍会继续发起请求,
-
不论资源是否可以缓存,prefecth会存储在net-stack cache中至少5分钟;
preload
- 主要用于预加载当前页面需要的资源;
- 在页面关闭时请求中断,停止获取资源;
- preload需要使用as属性指定特定的资源类型以便浏览器为其分配一定的优先级,并能够正确加载资源;