强缓存和协商缓存

发布于 2024-02-13 18:58:00 字数 6945 浏览 29 评论 0

为什么要采用缓存

在任何一个前端项目中,访问服务器获取数据都是很常见的事情,但是如果相同数据被重复请求了不止一次,那么多余的请求次数必然会浪费网络带宽,以及延迟浏览器渲染所要处理的内容,从而影响用户的体验。因此考虑使用缓存技术对以获取的资源进行重用,是一种提升网站性能与用户体验的有效策略

缓存的原理

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

HTTP 缓存

HTTP 缓存是前端开发中最常接触的缓存机制,可以分为 强制缓存协商缓存 ,⼆者最⼤ 的区别在于判断缓存命中时,浏览器是否需要向服务器端进⾏询问以协商缓存的相关信息,进⽽判断是否需要就响应内容进⾏重新请求。

强制缓存

对于强制缓存⽽⾔,如果浏览器判断所请求的⽬标资源有效命中,则可直接从强制缓存中返回请求响应, ⽆须与服务器进⾏任何通信。强制缓存有效,返回的是 200

expires

expires: Web, 14 Fed 2021 12:23:42 GMT

expires 是在 HTTP 1.0 协议中 声明的⽤来控制缓存失效⽇期时间戳的字段,它由服务器端指定后通过响应头告知浏览器,浏览器在接收到带有该字段的响应体后进⾏缓存。若之后浏览器再次发起相同的资源请求,便会对⽐ expires 与本地当前的时间戳,如果当前请求的本地时间戳⼩于 expires 的值,则说明浏览器缓存的响应还未过期,可以直接使⽤⽽⽆须向服务器端再次发起请求。只有当本地时间戳⼤于 expires 值发⽣缓存过期时,才允许重新向服务器发起请求。

  • 不足:如果客户端本地的时间与服务器端的时间不同步,或者对客户端时间进⾏主动修改,那么对于缓存过期的判断可能就⽆法和预期相符。
    因此从 HTTP 1.1 协议开始新增了 cache-control 字段来对 expires 的功能进⾏扩展和完善。

cache-control

Cache-Control:public, max-age=31536000

设置了 maxage=31536000 的属性值来控制响应资源的有效期,它是⼀个以秒为单位的时间⻓度,表示该资源在被请求到后的 31536000 秒内有效,如此便可避免服务器端和客户端时间戳不同步⽽造成的问题。
除此之外, cache-control 还可配置⼀些其他属性值来更准确地控制缓存,下⾯来具体介绍:

  • no-cache(或 max-age=0 ):跳转强制缓存,直接与服务端进行协商缓存
  • no-store :​完全不采用缓存,每次都要服务端给予全新的响应
  • private :​只能被浏览器缓存,默认就是 private
  • public :​可以被浏览器或代理服务器缓存
  • max-age :​浏览器缓存的时长(单位秒)
  • s-maxage :​代理服务器缓存的时长(单位秒),且仅当设置了 public 属性值时才有效

协商缓存

协商缓存就是在使⽤本地缓存之前,需要向服务器端发起⼀次 GET 请求,与之协商当前浏览器 保存的本地缓存是否已经过期。
协商缓存有效时,返回的响应状态为 304 not modified

// 协商缓存有效的响应头
Status Code: 304 Not Modified

响应头里,必须带上 cache-control: no-cache 强制不采用缓存,每次都需要服务器给出响应

last-modified 与 if-modified-since

last-modified 与 if-modified-since 是 HTTP 1.0 的产物

last-modified: Thu, 29 Apr 2021 03:09:28 GMT 5 
cache-control: no-cache

如上要记得响应头了除了 last-modified ,还要设置 cache-control: no-cache ,告诉浏览器要使用协商缓存。

为了便于理解,举个例子:

客户端要向服务器请求一张 03.jpg 图片,为了让该资源被再次请求时能通过协商缓存的机制使用本地缓存,而不用每次都返回该图片,那么需要实现如下功能:

  • 首次访问该图片时,在响应头中应包含 last-modified 字段,该字段的值为该图片最近一次修改的时间戳,并且要带上 Cache-Control ,值为 no-cache ,表示采用协商缓存
  • 再次请求时,客户端会向服务端发送一次 GET 请求,进行缓存有效性的协商,请求头中会带上 if-modified-since 字段,字段值为上次响应头里的 last-modified 的字段值。
  • 服务器收到该请求后,会获取头部里的 if-modified-since ,并将该字段值与图片当前的修改时间戳进行对比,如果相同则说明缓存未过期,可继续使用本地缓存,返回状态码 304 ,否则服务器重新返回新的图片资源。

采用 node 编码如下:

  const { mtime } = fs.statSync('./img/03.jpg')
  const ifModifiedSince = req.headers['if-modified-since']
  if (ifModifiedSince === mtime.toUTCString()) {
    // 缓存生效
    res.statusCode = 304
    res.end()
    return
  }
  const data = fs.readFileSync('./img/03.jpg')
  res.setHeader('last-modified', mtime.toUTCString())
  res.setHeader('Cache-Control', 'no-cache')
  res.end(data)

不足:

  • 是根据资源最后的修改时间戳进⾏判断的,所以就算只是对文件进行了编辑(比如文件重命名之后再改回来),但内容并没有发生改变,修改时间也会更新,导致协商缓存失效。
  • 标识文件资源修改的时间戳单位为秒,这样如果修改的速度过快,在毫秒之内改了内容,无法识别出来。

etag 与 if-none-match

为了弥补通过时间戳判断的不⾜,从 HTTP 1.1 规范开始新增了⼀个 ETag 的头信息。
是服务器为不同资源进⾏哈希运算所⽣成的⼀个字符串,该字符串类似于⽂件指纹,只要⽂件内容编码存在差异,对应的 ETag 标签值就会不同,因此可以使⽤ ETag 对⽂件资源进⾏更精准的变化感知。

Cache-Control: no-cache
etag: "12274-tEuUYy8halvEHeM+olO/cV8mQ8A"

为了便于理解,举个例子:

客户端要向服务器请求一张 04.jpg 图片,为了让该资源被再次请求时能通过协商缓存的机制使用本地缓存,而不用每次都返回该图片,那么需要实现如下功能:

  • 首次访问该图片时,在响应头中应包含 etag 字段,该字段的值为该图片最近一次的文件标识,并且要带上 Cache-Control ,值为 no-cache ,表示采用协商缓存
  • 再次请求时,客户端会向服务端发送一次 GET 请求,进行缓存有效性的协商,请求头中会带上 if-none-match 字段,字段值为上次响应头里的 etag 的字段值。
  • 服务器收到该请求后,会获取头部里的 if-none-match ,并将该字段值与图片当前的文件标识进行对比,如果相同则说明缓存未过期,可继续使用本地缓存,返回状态码 304 ,否则服务器重新返回新的图片资源。

采用 node 编码如下:

const data = fs.readFileSync('./img/04.jpg')
const etagContent = etag(data)
const ifNoneMatch = req.headers['if-none-match']
if (ifNoneMatch === etagContent) {
    res.statusCode = 304
    res.end()
    return
}
res.setHeader('etag', etagContent)
res.setHeader('Cache-Control', 'no-cache')
res.end(data)

last-modified 共同存在时, etag 优先级更高

不足:

  • 生成文件资源的 etag 需要付出额外的开销,但文件内容过大时,会影响服务器的性能
  • etag 字段值的生成分为强验证和弱验证,强验证能够保证每个字节都相同,但速度慢;而弱验证只对部分属性值生成,速度快但无法保证每个字节都相同
  •  

所以 etag 只是对于 last-modified 的一个补充,并不是替换

  • ⾸先根据资源内容的属性判断是否需要使⽤缓存,如果不希望对该资源开启缓存(⽐如涉及⽤户的⼀些敏感信息),则可直接设置 cache-control 的属性值为 no-store 来禁⽌任何缓存策略,这样请求和响应的信息就都不会被存储在对⽅及中间代理的磁盘系统上。
  • 如果希望使⽤缓存,那么接下来就需要确定对缓存有效性的判断是否要与服务器进⾏协商,若需要与服务器协商则可为 cache-control 字段增加 no-cache 属性值,来强制启⽤协商缓存。
  • 否则接下来考虑是否允许中间代理服务器缓存该资源,可通过为 cache-control 字段添加 private 或 public 来进⾏控制
  • 如果之前未设置 no-cache 启⽤协商缓存,那么接下来可设置强制缓存的过期时间,即为 cache-control 字段配置 max-age=… 的属 性值
  • 最后如果启⽤了协商缓存,则可进⼀步设置请求资源的 last-modified 和 ETag 实体标签等参 数

缓存设置注意事项

  • 拆分源码,分包加载,这样内容更改,仅需拉取发生修改的模块代码包,如果是采用 webpack ,可以使用 splitChunks 进行分块打包,或者使用动态加载,比如 import() 函数
  • 预估资源的缓存时效,根据文件内容,设置不同的缓存过期时间
  • 控制中间代理的缓存,涉及用户隐私的不要采取中间代理
  • 避免网址的冗余,相同资源采用同一个网址
  • 规划缓存的层次结构

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文