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舉個栗子看緩存

在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章