HTTP缓存机制与原理详解

1.1 - 缓存

  • 缓存可以重用已获取的资源能够有效的提升网站与应用的性能。
  • Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。
  • 借助 HTTP 缓存,Web 站点变得更具有响应性。
  • 缓存分为两点:强制缓存和协商缓存

1.2 - 强制缓存

  • 概念
    • 强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程。
    • 简单来讲就是强制浏览器使用当前缓存
    • 所请求的数据在缓存数据库中尚未过期时,不与服务器进行交互,直接使用缓存数据库中的数据。当缓存未过期时基本流程如下:
      强制缓存.jpg
  • 实现:通过服务器端设置响应头字段来控制强制缓存的过期时间
    • expires (http1.0) 其指定了一个日期/时间, 在这个日期/时间之后,HTTP响应被认为是过时的。但是它本身是一个HTTP1.0标准下的字段,所以如果请求中还有一个置了 “max-age” 或者 “s-max-age” 指令的Cache-Control响应头,那么 Expires 头就会被忽略。
    • cache-control (http1.1) 通用消息头用于在http 请求和响应中通过指定指令来实现缓存机制。
    • cache-control 优先级比 expires 高
  • expires
    • 日期(new Date().toGMTString()) 缓存的最大有效时间
  • cache-control
    • max-age(单位s) 缓存的最大有效时间
    • no-cache 使用协商缓存
    • no-store 不使用任何缓存
    • public (客户端、代理服务器)缓存所有资源
    • private 默认值,只有客户端缓存所有资源
  • 比如 jQuery 用强制缓存(长久不变,读取不了新东西),因为变化慢,还有一些图片,一直变,有些广告变动很快,就走协商缓存。

1.3-协商缓存

  • 概念
    • 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
    • 当强缓存过期未命中或者响应报文Cache-Control中有must-revalidate标识必须每次请求验证资源的状态时,便使用协商缓存的方式去处理缓存文件。
      协商缓存主要原理是从缓存数据库中取出缓存的标识,然后向浏览器发送请求验证请求的数据是否已经更新,如果已更新则返回新的数据,若未更新则使用缓存数据库中的缓存数据,具体流程如下,当协商缓存命中:

协商缓存.jpg- 实现:

  • Last-Modified / If-Modified-Since
  • Etag / If-None-Match
  • Etag / If-None-Match 优先级比 Last-Modified / If-Modified-Since 高。
  • Last-modified
    • 记录服务器返回资源的最后修改时间
    • 由客户端发送给服务器
  • If-Modified-Since
    • 记录服务器返回资源的最后修改时间
    • 由服务器发送给客户端
  • Etag
    • 当前文件的唯一标识(由服务器生成)
    • 由客户端发送给服务器
  • If-None-Match
    • 当前文件的唯一标识(由服务器生成)
    • 由服务器发送给客户端
  • 工作流程:
    • 第一次:由服务器返回 If-None-Match 和 If-Modified-Since 字段通过响应头方式返回
    • 第二次:下次浏览器请求时,携带了Etag(值为上一次的If-None-Match的值)和 Last-modified(值为上一次的If-None-Since的值)发送给服务器
    • 服务器先检查Etag是否等于最新的If-None-Match的值,如果相等直接走浏览器本地缓存,不相等就返回新的资源
    • 如果没有Etag,才看Last-modified的值,检查Last-modified是否等于最新的If-None-Since的值,如果相等直接走浏览器本地缓存,不相等就返回新的资源

1.4 - 缓存返回值

  • 200(from memory cache)
    • 命中强制缓存
    • 缓存来自于内存
  • 200(from disk cache)
    • 命中强制缓存
    • 缓存来自于磁盘
  • 304 Not Modified
    • 命中协商缓存
  • 200
    • 没有命中缓存

1.5-浏览器缓存机制详解流程图

在这里插入图片描述

1.6-总结缓存策略

  1. 强制缓存(优先级更高)
    • Expires cache-control
    • 不会重新发送请求,直接走缓存
  2. 协商缓存
    • last-modified和etag if-modified-since和if-None-Match
    • 通常和cache-control配合使用,no-cached,会再次发送请求,由服务器判断请求资源是否走缓存,
      • 如果走 返回 304 Not Modefined
      • 如果不走缓存 返回新的资源

1.7-cache缓存案例

const express = require('express');
const { resolve } = require('path');
const { readFile, stat, watchFile } = require('fs');
const etag = require('etag');
const app = express();
/*
  缓存:
    强制缓存
      http 1.1 cache-control
      http 1.0 expires
      特点:在缓存的期限内,不会发送请求,直接读取缓存
    
    协商缓存
      响应头(服务器发给浏览器)
        etag 文件唯一标识
        last-modified 文件最近一次修改时间
      请求头
        if-none-match 文件唯一标识
        if-modified-since 文件最近一次修改时间
      
      流程:
        第一次浏览器访问服务器资源: 服务器设置响应头并返回资源给浏览器
          etag xxx
          last-modified xxx
        浏览器接受到服务器返回的响应头,就会存储起来。
        第二次浏览器访问服务器资源,自动携带上之前存的响应头,作为请求头发送过去(会改名字)
          etag --> if-none-math
          last-modified --> if-modified-since
        服务器
          判断服务器保存的etag和浏览器发送过来if-none-math的值是否相等
          判断服务器保存的last-modified和浏览器发送过来if-modified-since的值是否相等
          如果都相等,走协商缓存  304
          如果有一个不相等,说明资源发生过变化,返回新资源

      特点:一定会访问服务器(一定发送请求),由服务器决定是否走缓存

      当强制缓存和协商缓存都存在:
        先看强制缓存。命中了,就直接读取缓存
        如果强制缓存失效(过期了),看协商缓存
 */


app.get('/', (req, res) => {
  // 访问当前路由,返回index.html
  // sendFile方法默认设置强制缓存、协商缓存
  // res.sendFile(resolve(__dirname, 'public/index.html'));
  const filePath = resolve(__dirname, 'public/index.html');
  readFile(filePath, (err, data) => {
    if (!err) {
      // 返回响应
      res.end(data);
    } else {
      console.log(err);
    }
  });
});

// 强制缓存
app.get('/js/index.js', (req, res) => {
  const filePath = resolve(__dirname, 'public/js/index.js');
  readFile(filePath, (err, data) => {
    if (!err) {
      // 设置强制缓存  设置响应头
      res.set('cache-control', 'max-age=20');
      // res.set('expires', new Date(Date.now() + 10000).toGMTString());
      // 返回响应
      res.end(data);
    } else {
      console.log(err);
    }
  });
});


let etagName = '';
let lastModified = 0;

const filePath = resolve(__dirname, 'public/css/index.css');

// 监视文件的变化
watchFile(filePath, (curr, prev) => {
  // 说明文件发生了变化,修改etagName/lastModified
  etagName = etag(curr);
  lastModified = new Date().toGMTString();
});
// 读取文件,获取初始化etagName, lastModified
stat(filePath, (err, stats) => {
  if (!err) {
    etagName = etag(stats);
    lastModified = new Date().toGMTString();
  }
});

// 协商缓存
app.get('/css/index.css', (req, res) => {

  // 就要和etag对比
  const ifNoneMatch = req.headers['if-none-match'];
  // 就要和lastModified对比
  const ifModifiedSince = req.headers['if-modified-since'];

  if (ifNoneMatch === etagName && ifModifiedSince === lastModified) {
    // 都相等,走缓存
    res.status(304).end();
    return
  }
  // 返回新资源
  readFile(filePath, (err, data) => {
    if (!err) {
      // 设置协商缓存
      // 接受浏览器发送过的etag和last-modified。 与当前的值对比。 如果一样,走缓存,不一样就返回最新的资源
      res.set('etag', etagName);
      res.set('last-modified', lastModified);
      // 返回响应
      res.end(data);
    } else {
      console.log(err);
    }
  });
});

app.listen(3000, (err) => {
  if (!err) console.log('服务器启动成功了~');
  else console.log(err);
});

1.8举个栗子看缓存

在这里插入图片描述
在这里插入图片描述

发布了27 篇原创文章 · 获赞 44 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/q761830908/article/details/103226401