HTTP缓存-http强制缓存与协商缓存

http缓存

缓存介绍

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

缓存的技术种类:代理缓存、浏览器缓存、网关缓存、负载均衡器及内容分发网络等,大致分为两类:共享缓存和私有缓存。

共享缓存指的是缓存内容可被多个用户使用,如公司内部架设的Web代理;

私有缓存指的是只能单独被用户使用的缓存,如浏览器缓存。

HTTP 缓存可细分为强制缓存与协商缓存。
强制缓存:不用判断缓存是否过期
协商缓存:获取缓存资源时询问服务器缓存是否过期,过期则重新请求资源,未过期则使用缓存内容

强制缓存

与强制缓存相关的两个字段:expires、cache-control 。

Expires的强制缓存应用

Expires: 响应头包含日期/时间, 即在此时候之后,响应过期。
在Cache-Control响应头设置了 “max-age” 或者 “s-max-age” 指令,那么 Expires 头会被忽略。

http.createServer((req,res) => {
	res.writeHead(200, {
	  Expires: new Date('2022-1-1 12:00:00').toUTCString()
	})
}))

请添加图片描述

expires缺陷:依赖客户端时间戳,客户端时间和服务器时间不一致时,缓存判断会出错

Cache-Control 通用消息头字段,被用于在http请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

Cache-Control的强制缓存应用

设置缓存过期时间,5秒之内缓存有效,超过5秒需要重新请求资源

http.createServer((req,res) => {
	res.writeHead(200, {
	  'Cache-Control': 'max-age=5'
	})
}))

请添加图片描述

Cache-Control的其他参数

no-cache:强制使用协商缓存, 每次都需要和服务器确认缓存是否有效。

客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。每次都会发起 HTTP 请求,当缓存内容有效时跳过 HTTP 响应体的下载。

no-store:禁止使用任何缓存

private:响应资源可以被代理服务器缓存。

public:响应资源可以被浏览器代理服务器缓存。

一般不容易变动的资源可以设置public属性。例如:图像、css文件和js文件。

max-age:设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。

s-maxage: 当设置public属性时生效,表示代理服务器缓存失效时间。
请添加图片描述
cache-control能作为expires的完全替代方案,并且拥有其所不具备的一些缓存控制特性,在项目实践中使用它就足够了,目前expires还存在的唯一理由是考虑可用性方面的向下兼容。

pragma(了解)

pragma值有no-cache与no-store
优先级大于cache-control即:

pragma -> cache-control -> expires

协商缓存

协商缓存:使用本地缓存前,向服务器发起get请求,与服务器协商当前本地缓存是否过期。

主要通过last-modified和etag值判断。

基于last-modified判断缓存修改时间

设置last-modified字段为缓存内容最后修改时间。

客户端再次请求该资源时携带if-modified-since字段,if-modified-since字段值为上次请求的last-modified值。

服务端判断两个字段值,两者一致时返回304表示缓存有效。
两者不一致时返回最新的资源。

请求流程图

客户端 服务端 你好!我需要data资源! 1 好的!给你data,请记下last-modified哦 2 记录last-modified值 赋值给if-modified-since 老板!再来一份上次的资源! 3 携带if-modified-since获取资源 对比if-modified-since与资源修改时间 稍等我对比一下 4 if-modified-since与资源修改时间一致 返回304告知客户端缓存未过期 304! 你的资源还能用啊! 5 好的那我用之前的吧。 6 200!我重新给你一份吧。 7 if-modified-since与资源修改时间不一致 返回200重新发送资源和新的last-modified 万分感谢 客户端 服务端

node代码实现

const fs = require("fs")
const http = require("http");
const url = require("url")
http.createServer((req,res) => {
  const {pathname} = url.parse(req.url);
  if(pathname === '/img/3.jpg') {
    //读取图片文件
    const data = fs.readFileSync("./img/3.jpg")
    //获取文件修改时间
    const { mtime } = fs.statSync('./img/3.jpg')
    // 获取请求头中使用的缓存最后修改时间
    const ifModifiedSince = req.headers['if-modified-since']
    // 对比缓存最后修改时间与文件最后修改时间
    if(ifModifiedSince === mtime.toUTCString()) {
      // 缓存生效 返回304
      res.statusCode = 304
      res.end()
      return
    }
    // 添加响应文件最后修改时间
    res.setHeader("Last-modified", mtime.toUTCString())
    // 设置为协商缓存
    res.setHeader("Cache-Control", 'no-cache')
    res.end(data)
  } 
}))

last-modified缺点

1.根据资源修改时间戳判断,资源内容可能没有变更。这时就会重新请求资源,浪费网络带宽。
2.时间单位精确到秒,资源在一秒内完成修改,就会出现资源没有更新的情况。

基于etag判断资源修改内容

为了解决last-modified的两个问题,http1.1添加了etag的头信息,即实体标签。
etag是服务器根据资源内容通过哈希运算生成的唯一标识字符串。不同资源将生成不同的etag。

请求流程图

请求过程与之前类似

客户端 服务端 你好!我需要data资源! 1 好的!给你data,请记下etag哦 2 记录etag值 赋值给if-none-match 老板!再来一份上次的资源! 3 携带if-none-match获取资源 对比if-none-match与资源的etag值 稍等我对比一下 4 if-none-match与资源的etag值一致 返回304告知客户端缓存未过期 304! 你的资源还能用啊! 5 好的那我用之前的吧。 6 200!我重新给你一份吧。 7 if-none-match与资源修改时间不一致 返回200重新发送资源和新的etag 万分感谢 客户端 服务端

node实现

const fs = require("fs")
const http = require("http")
const url = require("url")
const etag = require('etag')
http.createServer((req,res) => {
  const {pathname} = url.parse(req.url);
  if (pathname === '/img/4.jpg') {
    // 读取文件内容
    const data = fs.readFileSync("./img/4.jpg")
    // 生成etag标识字符串
    const etagControl = etag(data)
    // 获取请求头标识字符串
    const ifNoneMatch = req.headers['if-none-match']
    // 判断是否为同一个文件
    if(ifNoneMatch === etagControl) {
      res.statusCode = 304
      res.end()
      return
    }

    res.setHeader("etag", etagControl)
    res.setHeader("Cache-Control", "no-cache")
    res.end(data)
  }
}))

etag的弊端

1.生成etag需要额外的计算资源。

当资源尺寸大或者数量多的时候影响服务器性能。

2.etag字段分为强验证和若验证。

强验证:根据资源内容生成。资源有任何变化都会生成不同的etag

弱验证:根据资源的部分属性值生成。生成速度快但是无法保证每个字节都相同。

3.分布式场景下依赖算法:计算etag的算法不一致会导致浏览器从一台服务器获取资源到另一台服务器效验时出现etag不匹配情况。

缓存策略

缓存策略流程图

首先考虑是否需要缓存。如果想启用协商缓存需要给cache-control字段添加no-cache属性值
接下来考虑是否允许代理服务器缓存资源,通过cache-control字段添加private和public控制。
如果之前未设置no-cache启用协商缓存,那么接下来可设置强制缓存的过期时间,即为 cache-control字段配置 max-age=… 的属性值。
最后如果启用了协商缓存,则可进一步设置请求资源的last-modified和ETag实体标签等参数。

Created with Raphaël 2.3.0 开始 是否使用缓存? 是否进行协商缓存? no-cache 是否能被代理服务器缓存 public 配置强制缓存过期时间 配置协商缓存过期时间 结束 private no-store yes no yes no yes no

缓存策略示例

在使用缓存技术优化性能体验的过程中,有一个问题是不可逾越的:我们既希望缓存能在客户端尽可能久的保存,又希望它能在资源发生修改时进行及时更新。

这是两个互斥的优化诉求,使用强制缓存并定义足够长的过期时间就能让缓存在客户端长期驻留,但由于强制缓存的优先级高于协商缓存,所以很难进行及时更新;若使用协商缓存,虽然能够保证及时更新,但频繁与服务器进行协商验证的响应速度肯定不及使用强制缓存快。那么如何兼顾二者的优势呢?

我们可以将一个网站所需要的资源按照不同类型去拆解,为不同类型的资源制定相应的缓存策略,以下面的HTML文件资源为例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>http缓存</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <img width="100" src="./img/2.png" alt="2.png">
  <img width="100" src="./img/3.jpg" alt="3.jpg">
  <img width="100" src="./img/4.jpg" alt="4.jpg">
</body>
<script src="script.js"></script>
</html>

该 HTML 文件中包含了一个 JavaScript 文件 script.js、一个样式表文件 style.css和图片文件,若要展示出该 HTML 中的内容就需要加载出其包含的所有外链文件。据此我们可针对它们进行如下设置。

首先 HTML 在这里属于包含其他文件的主文件,为保证当其内容发生修改时能及时更新,应当将其设置为协商缓存,即为 cache-control 字段添加 no-cache 属性值;其次是图片文件,因为网站对图片的修改基本都是更换修改,同时考虑到图片文件的数量及大小可能对客户端缓存空间造成不小的开销,所以可采用强制缓存且过期时间不宜过长,故可设置cache-control 字段值为 max-age=86400。

接下来需要考虑的是样式表文件 style.css,由于其属于文本文件,可能存在内容的不定期修改,又想使用强制缓存来提高重用效率,故可以考虑在样式表文件的命名中增加文件指纹或版本号(比如添加文件指纹后的样式表文件名变为了 style.51ad84f7.css),这样当发生文件修改后,不同的文件便会有不同的文件指纹,即需要请求的文件 URL 不同了,因此必然会发生对资源的重新请求。同时考虑到网络中浏览器与 CDN 等中间代理的缓存,其过期时间可适当延长到一年,即cache-control:max-age=31536000。

最后是 JavaScript 脚本文件,其可类似于样式表文件的设置,采取文件指纹和较长的过期时间,如果 JavaScript 中包含了用户的私人信息而不想让中间代理缓存,则可为 cache-control添加private属性值。

从这个缓存策略的示例中我们可以看出,对不同资源进行组合使用强制缓存、协商缓存及文件指纹或版本号,可以做到一举多得:及时修改更新、较长缓存过期时间及控制所能进行缓存的位置。

缓存设置注意事项

在前面的内容中虽然给出了一种制定缓存决策的思路与示例,但需要明白的一点是:不存在适用于所有场景下的最佳缓存策略。凡是恰当的缓存策略都需要根据具体场景下的请求资源类型、数据更新要求及网络通信模式等多方面因素考量后制定出来,所以下面列举一些缓存决策时的注意事项,来作为决策思路的补充。

拆分源码,分包加载

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

预估资源的缓存时效

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

控制中间代理的缓存

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

避免网址的冗余

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

规划缓存的层次结构

参考缓存决策中介绍的示例,不仅是请求的资源类型,文件资源的层次结构也会对制定缓存策略有一定影响。

网页访问刷新与缓存

访问和刷新分为三种情况:

标签进入、输入ulr回车进入
按刷新按钮、f5刷新、网页右键“重新加载”
ctrl + f5强制刷新

TODO

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值