前端资源缓存机制

前言

缓存原理:在首次请求后保存一份请求资源的响应副本,当用户再次发起相同请求后,如果判断缓存命中则拦截请求,将之前缓存的响应副本返回给用户,从而避免重新向服务器发起资源请求。

常见的缓存技术? 代理缓存 浏览器缓存 网关缓存 负载均衡 内容分发网络 等

大致可分为两类

  • 共享缓存 可被多个用户共同使用,如公司内部架设的web代理
  • 私有缓存 只能被单个用户使用,如浏览器缓存

前端最常接触的为http缓存

Http缓存

主要目的:提升网站的性能

强缓存

浏览器判断所请求的目标资源有效命中则可直接从强制缓存中返回请求响应,无需与服务器进行通信

相关字段

expires — 目前只做兼容使用

控制缓存失效日期的时间戳字段 由服务器指定,通过响应头告诉浏览器,浏览器在收到带有该字段的响应体后进行缓存

存在问题
对本地时间戳过分依赖,如果客户端本地的时间与服务器时间不同步,或者客户端时间被修改,那么缓存过期的判断可能就无法和预期相符。

cache-control — ❀ expires的完全替代方案

值为maxage=xxx来控制响应资源的有效期,以秒为单位表示该资源在被请求到的一段时间内有效,可避免服务端和客户端时间戳不同步而造成的问题。

'Cache-Control': 'maxage=1000' 响应资源的有效期

其他参数
'Cache-Control': 's-maxage=1000,public' 缓存在代理服务器上的有效时长,配合public使用(涉及代理服务器缓存的大型项目)
'Cache-Control': 'no-cache' 强制进行协商缓存,对于每次发起的请求不会再去判断强制缓存是否过期,而是直接进行协商缓存
'Cache-Control': 'no-store' 禁止使用任何缓存策略,客户端的每次请求都直接从服务器获取
'Cache-Control': 'private' 响应资源只能被浏览器缓存
'Cache-Control': 'public' 响应资源既可被浏览器缓存又可以被代理服务器缓存

PS:响应资源即可被浏览器缓存又可以被代理服务器缓存

协商缓存

在使用本地缓存前,向服务器发起一次GET请求,与之协商当前浏览器保存的本地缓存是否已经过期。
协商缓存主要解决的问题就是在强制缓存下资源不更新的问题

last-modified-since

过程:

(客户端发起请求,服务器返回相应内容和内容的最后修改时间)

  1. (客户端再次访问,)客户端获取到本地缓存
  2. 向服务端发送一个get请求 (请求头包括 last-modified-since 值是响应头的last-modified的值-资源最后修改的时间)
  3. 服务器接收到请求,对比传来的last-modified-since字段和资源修改的时间,相同-未过期–告诉浏览器直接使用缓存的文件; 不同-过期-返回对应的文件+新的修改日期

代码实现:

const fs = require('fs');
const url = require(''url');
http.creatServer((req, res) => {
    const { pathname } = url.parse(req.url);
    // 获取文件日期
    fs.stat(`www/${pathname}`, (err, stat) => {
      if (err) {
        res.writeHeader(404);
        res.write('Not Found');
        res.end();
      } else {
        if (req.headers['if-modified-since']) {
          const oDate = new Date(req.headers['if-modified-since']);
          const time_client = Math.floor(oDate.getTime() / 1000);
          const time_server = Math.floor(stat.mtime.getTime() / 1000);
          if (time_server > time_client) { // 服务器的文件时间大于客户端
            sendFileToClient();
          } else {
            res.writeHeader(304);
            res.write('Not Modified');
            res.end();
          }
        } else {
          sendFileToClient();
        }

        function sendFileToClient() {
          let rs = fs.createReadStream(`www/${pathname}`);
          res.setHeader('Last-Modifyed', state.mtime.toGMTString());
          rs.pipe(res);
          rs.on('error', err => {
            res.writeHeader(404);
            res.write('Not Found');
            res.end();
          })
        }
      }
    })
}).listen(8080);
复制代码

存在问题

  • 只根据资源的最后修改时间进行判断,文件没变更也可以只保存了更改时间
  • 标识时间是秒,毫秒级内完成修改,无法识别缓存过期

服务器无法仅根据资源修改时间戳识别出真正的更新。导致更新不准确

解决

为解决上述问题 HTTP1.1规范开始新增了一个ETag的头信息, 实体标签

Etag

Etag描述:服务器为不同资源进行哈希运算生成字符串,类似于文件指纹,只要文件内容编码存在差异,对应的Etag值就会不同。 能精准感知文件资源的变化

const etag = require('etag')
res.setHeader('etag', etag(data));
复制代码

基于ETag发送的请求会在请求头中以If-None-Match传递给服务器。

ETag并非last-modified的替代方案而是一种补充方案,ta也存在一些问题存在问题

  • 服务器对生成文件资源的ETag需要付出额外的计算开销,如果资源体积较大,数量较多且修改较频繁,生成ETag的过程会影响服务器的性能
  • ETag的值分为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同。弱验证则根据资源的部分属性值来生成,生成速度快但无法确保每次字节都相同。并且在服务器集群场景下,也会因为不够准确而降低协商缓存的有效性和校验的成功性。

根据具体的资源使用场景选择恰当的缓存校验方式

缓存策略

理想情况:缓存触发率高,存留时间长(受限于客户端缓存容量),Etag资源更新高效(受限于服务器计算能力)
最优解:选择合适的缓存策略,利用有效资源达到最优性能

如何兼顾?

将网站资源按照不同类型拆解 制定不同的缓存策略

  • HTML文件 是包含其他文件的主文件,为保证当其发生改变能及时更新,应该设置为协商缓存.
  • 图片文件 修改基本都是替换,同时考虑图片文件的数量及大小可能对客户端缓存空间造成不小的开销,可采用强制缓存且过期时间不宜过长。
  • css样式表/js脚本文件 属于文本文件,内容可能不定期修改,可在文件的命名中增加指纹或版本号(一般为hash值),这样发生修改后不同的文件便会有不同的文件指纹。 建议强制缓存,缓存时间设置长一些(一年) 另外:js如果包含用户的私人信息而不想让中间代理缓存,可添加private属性

缓存策略考虑情况

  1. 拆分源码 分包加载

对于大型项目来说,代码里是非常庞大的,如果发生修改的部分集中在几个重要的模块中,那么进行全量的代码更新显然比较冗杂
方式:构建过程中按模块拆分打包。
好处:每次修改后更新提取时仅需拉取发生改变的模块代码包,从而大大降低了需要下载内容的大小

  1. 预估资源的缓存时效

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

  1. 控制中间代理的缓存

对涉及用户隐私信息的尽量避免中间代理的缓存
对所有用户响应的资源,考虑让中间代理进行缓存。

  1. 避免网址的冗余

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

  1. 规划缓存的层次结构

文件资源的层次结构也会对制定缓存策略有一定的影响。

CDN缓存

简介

构建在现有网络基础上的虚拟智能网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡,调度及内容分发等功能模块,使用户在请求所需访问的内容时能够就近获取,以此来降低网络阻塞,提高资源对用户的响应速度。

一般CDN会和主域名区分,好处:

  • 避免静态资源携带不必要的cookie信息

cookie的访问遵循同源策略,同一域名下的所有请求都会携带全部cookie信息,虽然cookie存储空间并不大,但是如果所有资源都放在主站域名下所有的请求全部携带数据量也是很大的。所以将CDN服务器的域名和主站域名进行区分是非常有价值的

  • 浏览器对同一域名并发请求有限制

浏览器对于同域名下的并发请求存在限制,通常Chrome的并发限制是6。可以通过增加类似域名的方式来提高并发请求数。当然这种方式对缓存命中是不友好的,如果并发请求了相同的资源使用了不同的域名,那么之前的缓存就失去了意义。

工作原理

就近响应。 当资源放在CDN上的时候,北京的用户访问网站的时候,资源首先会进行DNS解析,询问CDN有没有就近的服务器,如果有就就近连接服务器的IP地址获取资源。

由于DNS服务器将CDN的域名解析权交给了CNAME指向的专用DNS服务器,所以用户输入域名的解析最终是在CDN专用的DNS服务器上完成的。解析出的IP地址并非确定的CDN缓存服务器地址,而是CDN负载均衡器的地址。浏览器会重新向该负载均衡器发起请求,经过对用户IP地址的距离,所请求资源内容的位置及各个服务器状态的综合计算,返回给用户确定的缓存服务器IP地址。如果这个过程发生所需资源未找到的情况,那么此时便会依次向上一级缓存服务器继续请求查询,直至追溯到网站所在的跟服务器并将资源拉取到本地进行缓存、

缓存资源

CDN主要针对的是静态资源。所谓静态资源就是不需要业务服务器参与计算的资源,比如第三方的库,js脚本文件,css样式文件,图片等。如果是动态资源比如依赖服务端渲染的html就不适合放在CDN上。

核心功能

  • 缓存

将所有静态资源复制一份到CDN上

  • 回源

未在CDN缓存服务器上找到目标资源,或者资源已过期 则重新追溯到网站跟服务器获取相关资源

优化点

CDN性能优化
静态资源边缘化
域名合并优化
多级缓存架构优化

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享