• 前端网络请求性能优化之缓存


    什么是缓存?

    当第一次访问网站的时候,电脑会把网站中加载的图片与数据下载到电脑上,当我们再次访问该网站的时候,网站就会直接加载出来而并不是重新下载。在前端性能优化的方式中,缓存占据一定的地位,使用好了缓存会对前端有很大帮助。

    缓存有哪些好处?

    1. 缓解服务器的压力,不需要每次请求重复数据
    2. 提升性能,打开本地资源肯定要比从服务端获取数据来的快
    3. 减少带宽消耗,当我们使用缓存的时候只会产生很小的网络消耗,至于为什么打开本地资源也会产生网络消耗,下面会说明。

    Web缓存的类型

    1. 数据库缓存:当 Web 应用关系复杂时,可以将查询后的结果放到内存中进行缓存,下次再查询时就直接从内存中获取,从而提高响应速度。例如 memcached 高性能内存数据存储方案。
    2. CDN 缓存:网关缓存、反向代理缓存,当我们发送一个 Web 请求时,CDN 会帮我们计算去哪得到这些内容的路径短且快,网站是管理员部署的,他们也会将大家经常访问的内容放到 CDN 中从而加快响应。将源站的资源缓存到位于全球各地的 CDN 节点上,用户请求资源时,就近返回节点上缓存的资源,而不需要每个用户的请求都回您的源站获取,避免网络拥塞、缓解源站压力,保证用户访问资源的速度和体验。
    3. 代理服务器缓存:浏览器和源服务器之间的中间服务器,浏览器会先向中间服务器发起 Web 请求,经过权限验证、缓存匹配等处理后,再将请求转发到源服务器。它的原理跟浏览器缓存原理类似,只是规模较大,可以理解为一个共享缓存,不只是为一个用户服务,一般为大量用户提供服务。
    4. 浏览器缓存:浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户。每个浏览器都实现了 HTTP 缓存,当我们在浏览器中点击前进和后退按钮时,利用的便是浏览器的缓存机制。

    浏览器缓存

    其实就是在本地开辟一个内存区,同时也开辟一个硬盘区作为数据传输的缓冲区,然后用这个缓存区暂时保存用户以前访问过的信息。

    缓存过程:强缓存、协商缓存

    缓存位置:Service Worker -> Memory Cache -> Disk Cache -> Push Cache

    Service Worker

    它是运行在浏览器背后的独立进程,一般用来实现缓存功能。使用 Service Worker 的话,传输协议必须是HTTPS 协议,因为这个进程涉及到请求拦截,所以必须使用 HTTPS 协议来保障其安全。它与其他缓存机制不同,它可以让我自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

    //1.注册一个ServiceWorker
    (async function regist() {
      try {
        let registration = await navigator.serviceWorker.register('service-worker.js', {scope: './'});
        //register的第一个参数是脚本文件的路径,第二个参数中的scope(暂时只有这个属性)是这个脚本影响的作用域范围,如果像上面这样写,那么在当前路径/子路径下的页面都会受到影响。如果需要判断一个页面是否受service worker控制,可以检测navigator.serviceWorker.controller这个属性是否为null或者一个service worker实例。
      } catch (e) {
        console.error(e);
      }
    })()
    
    
    //2.绑定install事件,当事件触发时就可以缓存需要的文件
    self.addEventListener('install', function(event) {
      // Perform install steps
      event.waitUntil(
        new Promise((resolve, reject) => {
          setTimeout(resolve, 1000);
        })
      );
    });
    
    //3.下次用户在请求的时候就可以通过请求拦截的方式查询是否有缓存,存在有缓存的话就直接读取缓存中的数据,否则就去请求数据。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    当 Service Worker 没有命中缓存的话,它会按照缓存优先级依次查找是否存在 Memory Cache、disk Cache、Push Cache,但不管从何处取得的缓存,浏览器都会显示我们从 Service Worker 中获取的内容。

    Memory Cache

    它是保存在内存中的缓存,主要包含的是当前页面中的静态资源(图片、样式、脚本),从内存中读取数据肯定快于硬盘,但它的持续时间不长,关闭这个tab标签页后缓存就失效了。

    需要注意的是,内存缓存在缓存资源的时候并不关心返回资源的 HTTP Header 中 Cache-Control 是什么值,同时资源的匹配也并非对 URL 做匹配,当然也有可能对 Content-Type 或 CORS 等做出校验。

    Disk Cache

    存储在硬盘中的缓存,读取速度较慢,也是常见的一种缓存方式,主要是根据 HTTP 请求头中的字段判断哪些资源需要缓存、哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。即使在跨站点的情况下,相同地址的资源一旦被缓存下来,就不会再次请求数据。

    Push Cache 推送缓存

    它是 HTTP2 里面的内容,如果以上三种缓存没有命中的话,它才会被使用。它只存在 Session 会话中,一旦会话结束就被释放,并且缓存的时间也很短暂,在谷歌浏览器中只有5分钟左右,同时它也并非严格执行 HTTP 头中的缓存指令。

    非 HTTP 协议定义的缓存机制

    浏览器中的缓存机制大多数都是 HTTP 协议定义的缓存机制,但也有非 HTTP 定义的缓存机制,如使用 HTML meta 标签。

    //cache模式,是否从缓存中访问网页内容,no-cache 禁止浏览器从本地计算机的缓存中访问页面

    //网页到期时间,告诉浏览器不缓存页面每次都去服务器拉取

    使用上比较简单,但只有部分浏览器支持,而且所有缓存代理服务器都不支持,主要是因为代理并不解析 HTML 本身,广泛应用的还是 HTTP 头信息来控制缓存

    HTTP 协议定义的缓存机制

    通过 HTTP 协议头里的 cache-control(或 expires)和 last-modified(或 etag)等字段来控制文件缓存的机制。这应该是 Web 中最早的缓存机制了,是在 HTTP 协议中实现的,有点不同于 Dom Storage、AppCache 等缓存机制,但本质上是一样的。可以理解为一个是协议层实现的,一个是应用层实现的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWik6beH-1663316047946)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/040dfc8eb7c14ba09621da850b17d4f7~tplv-k3u1fbpfcp-zoom-1.image)]

    强缓存

    当我们访问 url 的时候,不会向服务器发送请求,直接从缓存中读取资源,但是会返回 200 的状态码,并且 Size 显示 from disk cache 或 from memory cache。可以通过两种 HTTP Header 实现:expires 和 cache-control

    expires

    它是 HTTP1.0的产物,表示缓存过期时间,值为一个时间戳,用来指定资源到期的时间,准确来讲是格林尼治时间(Thu Oct 16 07:13:48 GMT 2015)。时间是相对于服务器的时间而言的,但是如果客户端修改了本地时间导致与服务器时间不一致,可能会造成缓存失效。

    cache-control

    针对“expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,HTTP1.1增加了 cache-control 来来定义缓存过期时间。若同时出现了 expires 和 cache-control,则以 cache-control 为准。 它可以用在请求头或响应头中,并且可以组合多种指令:

    1. max-age = 600表示文件在本地缓存,且有效时长是600s(从发起请求算起)。(在接下来600秒内,如果有请求这个资源,浏览器不会发出 HTTP 请求,而是直接使用本地缓存的文件)
    2. no-cache:实际上是被缓存的,只不过每次在客户端提供响应数据时,缓存都要向服务器评估缓存响应的有效性。
    3. no-store:响应不被缓存。
    4. public:资源客户端和服务器都可以缓存。
    5. privite:资源只有客户端可以缓存。
    6. s-maxage = 600覆盖 max-age 作用一样,只在代理服务器生效
    7. max-stale = 30 即使缓存过期也会使用该缓存
    8. min-fresh = 30 希望在30s内获取最新的响应
    Pragma

    它同样是 HTTP1.0 的产物,只有一个值 no-cache 也就是不适用强缓存,走协商缓存,该字段只适用于请求头,甚至可以乎忽略该字段的存在。

    expires与cache-control区别:
    1. 协议不同:expires 是 HTTP1.0 的产物,cache-control 是 HTTP1.1 的产物
    2. 优先级:两者同时存在的话,cache-control 优先级高于 expires;在某些不支持 cache-control 的环境下,expires 就会发挥用处,但 expires 其实是一个过时的产物,它的存在只是一种兼容性写法。

    强缓存判断是否缓存的依据来自于是否超出了某个时间或时间段,而不关心服务器端文件是否已经进行更新,这可能导致加载文件不是服务器端的新内容。

    协商缓存

    强缓存失效后,浏览器携带缓存标识向服务器发起请求,服务器根据缓存标识来决定是否使用缓存的过程。主要解决强缓存下资源不更新的问题,当协商缓存生效时存在 304 Not Modified;当协商缓存失效时存在 200 请求结果。通过设置两种 HTTP Header实现:last-modified 和 etag

    last-modified 和 if-modified-since

    浏览器在第一次访问资源,服务器在返回资源的同时,在 Response Headers 中添加 last-modified,它的值为这个资源在服务器上的最后修改时间,浏览器接收到缓存文件和 Header。下一次请求这个资源时,检测到有last-modified,就会添加 if-modified-since 值就是 last-modified 中的值;服务器再次收到这个资源请求,会根据 if-modified-since 中的值与浏览器中这个资源的最后修改时间比,如果没有变化则会返回 304和 空的响应体,直接从缓存中读取;如果 if-modified-since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回 200 和新的资源.

    弊端:

    • 如果打开了本地缓存文件,即使没有对文件进行修改,还是会造成 last-modified 被修改,服务端不能命中缓存导致发送相同的资源
    • last-modified 以秒计时,如果在不可感知的时间内(修改在毫秒内完成)修改文件,那么服务端还是会认为资源命中了,不会返回正确的资源
    etag 和 if-none-match

    为了解决上述问题,HTTP1.1增加了 etag。etag 是服务端响应请求时,返回当前资源文件的唯一标识(由服务器通过 Hash 运算生成,类似文件指纹),只要资源发生了变化,etag 就会重新生成。浏览器下次加载资源请求服务器时,会将上一次返回的 etag 值放到请求头的 if-none-match 中,服务器只需要比较客户端传来的 if-none-match 的值与服务器上该资源的 etag 是否一致,就能判断该资源的文件是否进行修改。若一致,则直接返回304通知客户端直接使用本地缓存即可;若不一致,则会以常规 200 的形式将新的资源发给客户端。

    两者对比:
    • 精确度上,etag 要优于 last-modified。last-modified的时间单位是秒,如果某个文件1秒内改变了多次,那么 last-modified 并没有体现出修改,但 etag 每次都会改变保证了精度;如果是负载均衡的服务器,各个服务器生成的 last-modified 也有可能不一致
    • 性能上,etag 要逊于 last-modified,毕竟 last-modified 只需要记录时间,而 etag 需要服务器通过算法来计算一个 Hash 值
    • 优先级上,服务器要优先考虑 etag

    总结

    强缓存优先于协议缓存,若强缓存(expires 和 cache-control)生效则直接使用缓存,若不生效则使用协商缓存(last-modified/if-modified-since 和 etag/if-none-match),协商缓存由服务器决定是否使用,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器中;生效则返回304,继续使用缓存。

    缓存策略

    HTTP 的缓存技术主要是为了提升网站的性能,如果不考虑客户端缓存容量和服务器计算能力的理想情况,当然希望客户端浏览器上的缓存触发率尽可能高,留存时间尽可能长,同时还要ETag实现当资源更新时进行高效的重新验证。但实际情况往往是容量和计算能力都有限,因此就需要指定合适的缓存策略,利用有效的资源达到最优的性能效果。既希望缓存能在客户端尽可能长久的保存,又希望他能在资源发生修改时进行及时更新。这时我们可以将网站所需要的资源按照不同的类型去拆解,为不同类型的资源制定相应的缓存策略。

    1. 首先 HTML 文件是包含其他文件的主文件,为保证当其发生改变能及时更新,应该将其设置为协商缓存.

    cache-control:no-cache

    1. 图片文件的修改基本都是替换,同时考虑图片文件的数量及大小可能对客户端缓存空间造成不小的开销,所以可以采用强制缓存且过期时间不宜过长。

    cache-control: max-age=86400(一天)

    1. css样式表属于文本文件,可能存在的内容不定期修改,还想使用强制缓存来提高重用效率,故可以考虑在样式表文件的命名中增加指纹或版本号(一般为 Hash 值),这样发生修改后不同的文件便会有不同的文件指纹,也就是请求的 url 不同。所以css的缓存时间可以设置长一些, 一年。

    cache-control: max-age=315360000(一年)

    1. js脚本文件可以类似样式表的设置,采用指纹和较长的过期时间,如果js中包含了用户的私人信息而不想让中间代理缓存,可添加 private 属性。

    cache-control: private, max-age=315360000

    缓存策略就是为不同的资源进行组合使用强制缓存,协商缓存及文件指纹或版本号,做到一举多得,及时修改更新,合理使用缓存过期时间及控制所能进行缓存的位置。

    缓存设置

    缓存设置需要注意不存在适用于所有场景下的最佳缓存策略,凡是恰当的缓存策略都需要根据具体的场景考虑制定。缓存决策要考虑下面几种情况。

    1. 拆分源码,分包加载

    对于大型项目来说,代码里是非常庞大的,如果发生修改的部分集中在几个重要的模块中,那么进行全量的代码更新显然比较冗杂,因此可以考虑在代码构建过程中按照模块拆分将其打包成多个单独的文件。这样在每次修改后更新提取时,仅需拉取发生改变的模块代码包,从而大大降低了需要下载的内容大小。

    1. 预估资源的缓存时效

    根据不同资源的不同需求特点来规划响应的缓存更新失效,为强制缓存指定合适的 max-age,为协商缓存提供验证更新的ETag实体标签。

    1. 控制中间代理的缓存

    凡是涉及用户隐私信息的尽量避免中间代理的缓存,如果对所有用户响应相同的资源,则可以考虑让中间代理也进行缓存。

    1. 避免网址的冗余

    缓存是根据请求资源的URL进行的,不同的资源会有不同的URL,所以尽量不要将相同的资源设置为不同的URL。这会导致缓存失效。

    1. 规划缓存的层次结构

    不仅请求的资源类型,文件资源的层次结构也会对制定缓存策略有一定的影响。

  • 相关阅读:
    木舟0基础学习Java的第十六天(异常,分类,自定义异常,注意事项)
    把C# 中的集合讲的比较明白的文章
    基于springboot的疫情社区生活服务系统
    Adobe Acrobat Pro DC 2023:提升工作效率,激发创意灵感 mac/win版
    python版局域网端口扫描
    由联合体union引出的大小端问题
    PHP WebShell 免杀
    Vue 在同一个页面里在不同的router-view里展示页面信息
    java下载特定格式excel
    Rust如何开发eBPF应用?(一)
  • 原文地址:https://blog.csdn.net/qq_45731083/article/details/126892813