HTTP 缓存

http 缓存分为强制缓存与协商缓存,二者最大的区别在与判断缓存命中时候,浏览器是否需要向服务器端进行缓存 以协商缓存的相关信息,进而判断是否就响应内容进行重新请求。

1.强制缓存

1.1 expires过期绝对时间

Expires:过期时间,在服务器请求的返回中加上请求头 Expires,未超过此时间采用缓存静态资源,超过此时间则请求新的资源

res.writeHead(200, {
  Expires: new Date("2023-10-15 15:52:00").toUTCString(),
});

http 服务端返回

Expires:Sun, 15 Oct 2023 07:52:00 GMT

缺点:

服务器时间与客户端的时间不同步,或者对客户端的时间进行主动修改,那么对于缓存的判断可能就无法和预期相符合

为了解决 Expires 判断的局限性。http1.1协议新增cache-control

1.2 Cache-Control

1.2.1 max-age(秒)

"Cache-Control": "max-age=3000"设置Cache-Controlmax-age=3000意为最大私有缓存时间为 3000s

res.writeHead(200, {
  "Cache-Control": "max-age=3000",
});

http 服务端返回:

Cache-Control:max-age=3000

1.2.2 no-cache

设置Cache-Control:no-cache并非像字面意思不使用缓存,其表示为强制进行协商缓存,即对于每次发起的请求都不会再去判断强制缓存是否过期 ,而是直接与服务器协商来验证缓存的有效性,若设置未过期,则会使用本地缓存。

1.2.3 no-store

设置Cache-Control:no-store表示禁止使用任何缓存策略,客户端的每次请求都需要再服务器端的给予全新的响应。

no-cacheno-store是两个互斥的属性值,不能同时设置

1.2.4 public

publicprivate是一组互斥属性值,他们用以明确响应资源是否被代理服务器进行缓存

public表示响应资源既可以被浏览器缓存,又可以被代理服务器缓存。

1.2.5 private

private表示响应资源只能被浏览器缓存,若未显式指定则默认为private

1.2.6 max-ages-maxage

max-age属性值会比s-maxage更常用,它表示服务器告知客户端浏览器响应资源的过期时长。

在一般的项目使用场景中基本够用,对于大型架构的项目通常会涉及使用各种代理服务器的情况,

s-maxage在缓存为public时才有意义,代表公有缓存时间 s-maxagemax-age 都是用于控制 HTTP 缓存的Cache-Control指令,但它们有不同的作用和范围:

max-age

  • max-age 是一个客户端缓存指令,用于指定资源在客户端缓存中的最长存储时间,以秒为单位。
  • 当浏览器接收到具有 max-age 的响应后,它会将资源缓存在本地,然后在指定的时间内不再向服务器请求资源,而是直接使用缓存。
  • 这个指令是与特定用户代理(浏览器)相关的,资源仅在特定用户代理的缓存中有效。

示例:

Cache-Control: max-age=3600

这表示资源在客户端缓存中可以存储 1 小时。

s-maxage

  • "s-maxage" 中的 "s" 是 "shared" 的缩写
  • s-maxage 是一个代理服务器缓存指令,用于指定资源在共享缓存中的最长存储时间,以秒为单位。
  • 这个指令主要用于代理服务器(如 CDN 或反向代理),用于控制代理服务器缓存资源的时间,而不是客户端浏览器的缓存。
  • 如果代理服务器收到具有s-maxage的响应,它将在共享缓存中存储资源,并在指定的时间内提供给所有客户端,而不必每个客户端都向源服务器请求资源。
Cache-Control: s-maxage=3600

这表示资源在代理服务器的共享缓存中可以存储 1 小时,而不必每个客户端都重新请求资源。

总之,max-age 控制客户端缓存,而s-maxage 控制代理服务器缓存。这两个指令可以一起使用,以便同时控制客户端和代理服务器的缓存行为。

max-age 控制客户端缓存,而s-maxage 控制代理服务器缓存。 这两个指令可以一起使用,以便同时控制客户端和代理服务器的缓存行为。

1.3 结论

对于应用程序中不会改变的文件,你通常可以再发送响应头前添加积极缓存。

这包括例如由应用程序提供的静态文件,例如图像,css 文件和 js 文件

Cache-Control:public,max-age=3100000

2.协商缓存

2.1 使用last-modifiedif-modified-since

Cache-Control:no-cache的情况下:

第一次请求设置last-modified的时间

第二次请求时候判断if-modified-since是否与

const { mtime } = fs.statSync("./img/xxx.jpg");

const ifModifiedSince = request.headers["if-modified-since"];

if (ifModifiedSince === mtime.toUTCString()) {
  // 缓存生效,第二次请求时候返回304
  response.statusCode = 304;
  response.end();
  return;
}

// 第一次请求时候
response.setHeader("last-modified", mtime.toUTCString());
response.setHeader("Cache-Control", "no-cache");
response.end(data);

last-modified的问题

  • 1.只是根据文件的修改时间进行判断,但内容没有变化,时间戳也会更新,从而导致协商缓存时关于有效性的判断验证为失效, 需要重新进行完整的资源请求,这样会浪费贷款资源。

  • 2.标识文件资源的修改时间单位是秒,如果文件修改的速度非常快,在几百毫秒内完成,那么上述通过时间戳的方式来验证缓存的 有效性,是无法识别出该次文件的更新的。

2.2 基于 Etag 的协商缓存

新增一个 ETag 的头信息,即实体标签(Entity Tag)

内容是服务器为不同资源进行哈希运算所生成的一个字符串,该字符串类似于文件指纹,只要在文件内容编码存在差异,对应的 ETag 标签值就会不同,因此可以使用 ETag 对文件资源进行更精确的变化感知

Etag 的实现

const etag = require("etag");

const data = fs.readFileSync("./img/xxx.jpg");

// 生成etag文件指纹
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);

etag的问题

不像强制缓存的cache-control可以完全控制 expire 的功能,在协商缓存中,etag 并非last-modified的替代方案而是补充方案, 因为存在弊端:

1.服务器对于生成文件资源的 Etag 需要付出开销,如果资源的尺寸较大,数量较多且修改比较频繁,那么就会影响服务器的性能

2.Etag 生成为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同; 弱验证则根据资源的部分属性值来生成,生成速度快但是无法确保每个字节都相同;

3.缓存决策

3.1 缓存决策树

在面对一个具体的缓存需求时,到底该如何制定缓存策略呢?我们可以参照图所示的决策树来逐步确定对一个资源具体的缓存策略

Image;

3.2 常用决策方案

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>缓存策略</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <img src="plane.jpg" alt="" />
    <script src="test.js"></script>
  </body>
</html>

html 中既有 img文件, 也有 script 标签,还有css文件

  • html包含其他文件的主文件,所以为了当内容发生更改时能够及时更新,将其设置为协商缓存,即为cache-control 字段添加no-cache属性值

  • 图片文件因为图片一般都是更换修改,而且图片文件的数量和大小可能对缓存空间造成不小的开销,所以可以采用强制缓存 且过期时间不宜过长,可设置为cache-control:max-age:86400(24 小时)

  • 样式文件style.css,由于其属于文本文件,可能内容的不定期修改,又想强制缓存来提高重用效率,那么可以在文件的命名 中增加文件的版本号155445.49983489.css,相当与[nameHash].[contentHash].css, 这样文件发生修改后不同的文件就会有不用的 指纹,过期时间可以设置比较长cache-control:max-age:31536000(一年)

  • js 文件,由于其属于文本文件,那么可以在文件的命名中增加文件的版本号155445.49983489.js,相当与[nameHash].[contentHash].js, 同事可以为cache-control添加 private 属性值。

Contributors: masecho