浏览器缓存

前端缓存|后端缓存

  • 后端缓存主要集中于“处理”步骤,通过保留数据库连接,存储处理结果等方式缩短处理时间,尽快进入“响应”步骤。
  • 前端缓存则可以在两步:“请求”和“响应”中进行。在“请求”步骤中,浏览器也可以通过存储结果的方式直接使用资源,直接省去了发送请求;而“响应”步骤需要浏览器和服务器共同配合,通过减少响应内容来缩短传输时间。这些都会在下面进行讨论。

按缓存位置分类

它们的优先级是:(由上到下寻找,找到即返回;找不到则继续)

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache
  • 网络请求获取资源

在这里插入图片描述

缓存过程

  • 浏览器第一次发起HTTP请求, 在浏览器缓存中没有发现请求的缓存结果和缓存标识
  • 因此向服务器发起HTTP请求, 获得该请求的结果还有缓存规则(也就是Last-Modified 或者ETag)
  • 浏览器把响应内容存入Disk Cache, 把响应内容的引用存入Memory
  • Cache把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put())

下一次请求相同资源的时候:

  • 调用Service Worker 的fetch事件响应
  • 查看memory Cache
  • 查看disk Cache.

memory cache

memory cache 是内存中的缓存,(与之相对 disk cache 就是硬盘上的缓存)。按照操作系统的常理:先读内存,再读硬盘。

  • 浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。

    • 一旦关闭Tab标签页,内存中的缓存也就释放了,所以容量和存储时效上差些
    • 当渲染进程结束后,内存缓存也就不存在了。
  • 是浏览器为了加快读取缓存速度而进行的自身的优化行为,不受开发者控制,也不受 HTTP 协议头的约束,算是一个黑盒。

  • 几乎所有的请求资源都能进入memory Cache, 细分来说主要分为preloader和preload这两块.

    • preloader:作用主要是用于在浏览器打开一个网页的时候,能够一边解析执行js/css, 一边去请求下一个资源, 而这些被 preloader 请求来的资源就会被放入 memory Cache 中,供之后的解析执行操作使用
    • preload:能显式指定预加载的资源, 这些资源也会被放进memory Cache中, 例如
  • 在从memory Cache读取缓存时, 浏览器会忽视Cache-Control中的一些max-age、no-cache等头部配置, 除非设置了no-store这个头部配置.

disk cache

也叫 HTTP cache,顾名思义是存储在硬盘上的缓存,因此它是持久存储的,是实际存在于文件系统中的。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。

存储容量更大, 且存储时长更长.

  • disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。
  • 强制缓存,协商缓存都来自于此
  • 即使是跨域站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次请求。

Memory Cache与Disk Cache两者的对比:

  • 比较大的JS、CSS文件会被丢硬盘中存储, 反之则存储在内存中
  • 当前系统内存使用率比较高的时候,文件优先进入磁盘

Service Worker

是运行在浏览器背后的独立线程, 也就是说它脱离了浏览器的窗体, 无法直接访问DOM.功能上主要是能实现: 离线缓存、消息推送、网络代理等.比如离线缓存就是Service Worker Cache.

但 Service Worker 的出现,给予了我们另外一种更加灵活,更加直接的操作方式。我们现在可以绕开银行职员,自己把钱放进去或者取出来。因此我们可以选择放哪些钱(缓存哪些文件),什么情况把钱取出来(路由匹配规则),取哪些钱出来(缓存匹配并返回)。当然现实中银行没有给我们开放这样的服务。

  • 使用Service Worker会涉及到请求拦截, 所以需要用HTTPS协议来保证安全, 传输协议必须是HTTPS
  • Service Worker同时也是PWA的重要实现机制
  • Service Worker 是由开发者编写的额外的脚本,与浏览器其它内建的缓存机制不同, 它可以让我们自由的控制缓存哪些文件、如何匹配读取缓存, 且缓存是持续性的
  • 我们可以从 Chrome 的 F12 中,Application -> Cache Storage 找到这个单独的“小金库”。
  • 这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。
  • 如果 Service Worker 没能命中缓存,一般情况会使用 fetch() 方法继续获取资源。这时候,浏览器就去 memory cache 或者 disk cache 进行下一次找缓存的工作了。注意:经过 Service Worker 的 fetch() 方法获取的资源,即便它并没有命中 Service Worker 缓存,命中其他缓存甚至实际走了网络请求,也会标注为 from ServiceWorker

Push Cache

推送缓存, 它是浏览器缓存的最后一段防线, 当以上三种缓存都没有命中的时候, 它才会被使用,是HTTP/2中的内容。它只会在会话(Session)中存在, 一旦会话结束它就会被释放, 并且缓存时间也很短暂, 在Chrome浏览器中只有5分钟.

另外由于它是 HTTP/2 中的内容, 因此在国内不是很普及

  • 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
  • 可以推送 no-cache 和 no-store 的资源
  • 一旦连接被关闭,Push Cache 就被释放
  • 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
  • Push Cache 中的缓存只能被使用一次
  • 浏览器可以拒绝接受已经存在的资源推送
  • 可以给其他域名推送资源

强制缓存

可以造成强制缓存的字段是 Cache-control(http1.1) 和 Expires(http1.0)强缓存是不需要发送HTTP请求的,返回的状态码都是200, 而协商缓存需要

两者同时存在, Cache-control的优先级更高;

适用场景

对于不常变化的资源使用

  • 静态资源:
    • 图片(.jpg, .png, .svg等)
    • 视频(.mp4, .avi等)
    • 字体文件(.ttf, .woff等)
  • 样式文件(.css)和脚本文件(.js):
    • 背景JS和样式表文件,这些文件很少变化,适合使用长强缓存来减少加载时间
  • 模板文件(.html):
    • 网站或应用的一些模板文件,例如网站首页、关于我们页面等。
  • 小部件或图标文件:
    • 社交分享按钮、网站导航栏图标等

Expires

  • 这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间),如Expires: Thu, 10 Nov 2017 08:45:11 GMT
  • 在响应消息头中,设置这个字段之后,就可以告诉浏览器,在未过期之前不需要再次请求
  • 缺点:
    • 由于是绝对时间,用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效,重新请求该资源。此外,即使不考虑客户端修改,时差或者误差等因素也可能造成客户端与服务端的时间不一致,致使缓存失效
    • 使用了 Expires 头的资源会被缓存下来,但是如果是跨域的情况,HTTP头部缓存可能不会工作,因为这需要服务器特定的 CORS(跨源资源共享)配置来允许共享缓存

Cache-Control

  • cache-control 优先级高于 Expires
  • 已知 Expires 的缺点之后,在HTTP/1.1中,增加了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不需要向服务器发送请求
  • 相比 Expires 优化:
    • max-age 相比 Expires 指定的绝对时间变成了相对时间,客户端通过比较系统当前时间与资源副本被缓存时的时间,计算是否已超过 max-age 指定的时间,避免了时区差异和客户端时钟不准确的问题

Cache-Control请求头常见属性
在这里插入图片描述
Cache-Control响应头常见属性
在这里插入图片描述

  • 其中no-cache:表示下次请求不要直接使用缓存而需要比对,并不对本次请求进行限制,不进行强缓存验证, 而是用协商缓存来验证,使得浏览器每次都请求服务器, 然后配合ETag或者Last-Modified来验证资源是否有效.
  • public: 客户端和代理服务器都可以缓存. 响应可以被中间任何的一个节点缓存, 比如一个请求要经历 Browser -> proxy1 -> proxy2 -> Server, 中间的代理(proxy)可以缓存资源. 下次再请求同一资源的时候, 浏览器就会直接到proxy1中拿缓存的东西而不必向proxy2拿.

在这里插入图片描述

协商缓存(也叫对比缓存)

当强制缓存失效(超过规定时间)时,就需要使用对比缓存,由服务器决定缓存内容是否失效

  • 对比缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 在响应体体积上的节省是它的优化点。
  • 返回304和Not Modified(空的响应体)

适用场景

对于常变化的资源使用,设置Cache-Control: no-cache

  • API响应:
    • 从后端获取的数据通常包括用户信息、文章内容等。这些数据可能会随着用户状态或内容更新而变化,因此适合使用协商缓存
  • 动态内容生成的资源:
    • 根据用户活动实时生成的资源,如用户生成的内容(UGC)、实时更新的信息流等
  • 用户数据文件:
    • 用户设置、用户个性化资源、头像等,虽然用户可能不经常更改这些资源,但出于安全考虑,最好使用协商缓存

对比缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。当强缓存失效后采用协商缓存来判断是否使用缓存
在这里插入图片描述

对比缓存有 2 组字段:

Last-Modified & If-Modified-Since

  • 浏览器第一次向服务器请求这个资源
  • 服务器在返回这个资源的时候, 在 response header 中添加 Last-Modified 告知客户端,资源最后一次被修改的时间,例如 Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
  • 浏览器将这个值和内容一起记录在缓存数据库中。
  • 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段
  • 服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304和一个空的响应体, 告诉浏览器从自己(浏览器)的缓存中拿;反之,则表示修改了,响应 200 状态码,并返回数据
    在这里插入图片描述

缺点:

  • 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
  • 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
  • 如果本地打开了缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务器端不能命中缓存导致发送相同资源

Etag & If-None-Match
服务器会根据当前文件的内容, 给文件生成一个唯一的标识, 若是文件发生了改变, 则这个标识就会改变.

  • 对于 Etag 分为强 ETag和弱 Etag
    • 强 ETag:强 ETag 表示资源的字节级别的完全相等。当两个资源的强 ETag 值相同时,意味着这两个资源在内容和属性(如最后修改时间)上完全相同。强 ETag 值直接使用双引号括起来的字符串(如 “12345”)
    • 弱 ETag:弱 ETag 表示资源在语义上是相等的,但在字节级别上可能存在差异。当两个资源的弱 ETag 值相同时,意味着这两个资源在功能和表现上是相同的,但在内容和属性(如最后修改时间)上可能有细微差别。弱 ETag 值以 W/ 开头,后面跟一个双引号括起来的字符串(如 W/“12345”)
      • 弱 ETag 通常用于动态生成的资源(如 HTML 页面)的缓存控制,以提高性能,同时允许一定程度的差异
etag: W/"132489-1627839023000"
if-none-match: W/"132489-1627839023000"
  • Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
  • 服务器会将这个标识 ETag 放到响应体的 header 中与请求的资源一起返回给浏览器, 而浏览器同样也会缓存文件与这个 header
  • 在下一次再次加载该资源时, 浏览器会将刚刚缓存的 ETag 放到请求体头部( request header)的 If-None-Match 里发送给服务器.
  • Etag 的优先级高于 Last-Modified
    在这里插入图片描述

作用场景

  • 协商缓存一般存储:HTML

ETag 和 Last-Modified 区别

  • 若是本地打开了缓存文件, 并没有进行修改, 也还是会改变最后修改时间, 导致缓存失败;
  • 由于 Last-Modified 是以秒来计时的, 若是某个文件在一秒内被修改了很多次, 那么这时候的 Last-Modified 并没有体现出修改了
  • 准确度上 ETag 更强;
  • 性能上 Last-Modified 更好;
  • ETag 优先级更高

浏览器的行为

  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache)。服务器直接返回 200 和最新内容。

实际页面效果

  • 首次请求.js、.css 和 .jpg
    在这里插入图片描述

  • 再次请求 (F5)
    在这里插入图片描述

  • 关闭tab后打开
    在这里插入图片描述

缓存相关问题

缓存穿透

  • 缓存穿透是指查询一个不存在的数据(既不在缓存中,也不在数据库中),由于缓存中没有该数据,导致请求穿透到数据库,从而对数据库造成压力
    • 布隆过滤器:对于可能访问的数据,可以将它们的 key 存储在布隆过滤器中。当用户请求数据时,首先检查请求的 key 是否在布隆过滤器中。如果不在,说明数据肯定不存在,可以直接返回错误信息。如果在,再继续访问缓存层和数据库。
    • 缓存空对象:在缓存中设置一个空值,并设置一个较短的过期时间,下次请求时直接从缓存中返回空数据,避免请求穿透到数据库。

缓存击穿

  • 缓存击穿是指一个热点数据在缓存中过期或者被清除,导致大量请求同时访问数据库,从而对数据库造成压力。
    • 热点数据永不过期:将热点数据设置为永久不过期,避免缓存失效导致请求穿透到数据库。
    • 加锁:在缓存失效的时候,使用分布式锁或者互斥锁,避免大量请求同时访问数据库。
    • 限流:使用限流算法对请求进行限制,避免大量请求同时访问数据库。

缓存雪崩

  • 缓存雪崩是指缓存中大量的数据在同一时间失效,导致大量请求同时访问数据库,从而对数据库造成压力。
    • 数据过期时间随机:将缓存数据的过期时间设置为随机时间,避免大量缓存同时失效。
    • 数据预热:在系统启动时,将热点数据加载到缓存中,避免缓存失效时大量请求访问数据库。
    • 分布式部署:将缓存部署在多个节点上,避免某个节点失效导致缓存雪崩。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值