node中的緩存機制

緩存是node開發中一個很重要的概念,它應用在不少地方,例如:瀏覽器有緩存、DNS有緩存、包括服務器也有緩存。html

1、緩存做用

那緩存是爲了作什麼呢?node

1.爲了提升速度,提升效率。算法

2.減小數據傳輸,節省網費。數據庫

3.減小服務器的負擔,提升網站性能。瀏覽器

4.加快客戶端加載網頁的速度。緩存

2、緩存分類

那緩存有幾種策略呢?bash

強制緩存:

一、概念:

客戶端訪問服務器請求資源,請求成功以後客戶端會緩存到本地,緩存到本地以後,若是之後客戶端再請求該資源此時不須要請求服務器了,直接訪問本地的就能夠。服務器

二、特色:

強制緩存不須要與服務器發生交互性能

三、客戶端訪問強制緩存的流程圖解

1)緩存命中 客戶端請求數據,如今本地的緩存數據庫中查找,若是本地緩存數據庫中有該數據,且該數據沒有失效。則取緩存數據庫中的該數據返回給客戶端。

2)緩存未命中 客戶端請求數據,如今本地的緩存數據庫中查找,若是本地緩存數據庫中有該數據,且該數據失效。則向服務器請求該數據,此時服務器返回該數據和該數據的緩存規則返回給客戶端,客戶端收到該數據和緩存規則後,一塊兒放到本地的緩存數據庫中留存。以備下次使用。網站

四、如何實現強制緩存?

一、瀏覽器會將文件緩存到Cache目錄,第二次請求時瀏覽器會先檢查Cache目錄下是否含有該文件,若是有,而且還沒到Expires設置的時間,即文件尚未過時,那麼此時瀏覽器將直接從Cache目錄中讀取文件,而再也不發送請求 二、Expires是服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過時時間前瀏覽器能夠直接從瀏覽器緩存取數據,而無需再次請求,這是HTTP1.0的內容,如今瀏覽器均默認使用HTTP1.1,因此基本能夠忽略 三、Cache-Control與Expires的做用一致,都是指明當前資源的有效期,控制瀏覽器是否直接從瀏覽器緩存取數據仍是從新發請求到服務器取數據,若是同時設置的話,其優先級高於Expires

把資源緩存在客戶端,若是客戶端再次須要此資源的時候,先獲取到緩存中的數據,看是否過時,若是過時了。再請求服務器 若是沒過時,則根本不須要向服務器確認,直接使用本地緩存便可

Cache-Control private 客戶端能夠緩存 public 客戶端和代理服務器均可以緩存 max-age=60 緩存內容將在60秒後失效 no-cache 須要使用對比緩存驗證數據,強制向源服務器再次驗證. 禁用強制緩存 no-store 全部內容都不會緩存,強制緩存和對比緩存都不會觸發。兼用強制緩存和對比緩存å

/**
* 1. 第一次訪問服務器的時候,服務器返回資源和緩存的標識,客戶端則會把此資源緩存在本地的緩存數據庫中。
* 2. 第二次客戶端須要此數據的時候,要取得緩存的標識,而後去問一下服務器個人資源是不是最新的。
* 若是是最新的則直接使用緩存數據,若是不是最新的則服務器返回新的資源和緩存規則,客戶端根據緩存規則緩存新的數據。
*/
let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('crypto');
/**
* 強制緩存
* 把資源緩存在客戶端,若是客戶端再次須要此資源的時候,先獲取到緩存中的數據,看是否過時,若是過時了。再請求服務器
* 若是沒過時,則根本不須要向服務器確認,直接使用本地緩存便可
*/
http.createServer(function (req, res) {
   let { pathname } = url.parse(req.url, true);
   let filepath = path.join(__dirname, pathname);
   console.log(filepath);
   fs.stat(filepath, (err, stat) => {
       if (err) {
           return sendError(req, res);
       } else {
           send(req, res, filepath);
}
});
}).listen(8080);
function sendError(req, res) {
   res.end('Not Found');
}
function send(req, res, filepath) {
   res.setHeader('Content-Type', mime.getType(filepath));
   //expires指定了此緩存的過時時間,此響應頭是1.0定義的,在1.1裏面已經再也不使用了
   res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString());
   res.setHeader('Cache-Control', 'max-age=30');
   fs.createReadStream(filepath).pipe(res);
}
複製代碼

對比緩存:

一、概念:

瀏覽器第一次請求數據時,服務器會將緩存標識與數據一塊兒返回給客戶端,客戶端將兩者備份至緩存數據庫中。 再次請求數據時,客戶端將備份的緩存標識發送給服務器,服務器根據緩存標識進行判斷,判斷成功後,返回304狀態碼,通知客戶端比較成功,可使用緩存數據。

二、特色:須要進行比較判斷是否可使用緩存
三、對比緩存流程圖解

1)客戶端第一次發請求

客戶端第一次請求數據,發現本地緩存中沒有,就向服務器發起請求,而後服務器把請求的數據返回給客戶端,並和客戶端商量你要保存到本地緩存中的規則,便是否緩存 緩存時間 有沒有標示 最後修改時間等信息。 2)客戶端第二次發請求
客戶端發起請請求 --->查看本地的緩存數據庫中是否有緩存---> 沒有---> 向服務器發起請求--->服務器返回200和響應內容--->顯示

--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存沒有過時(本地)---> 緩存中讀取--->顯示

--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存已過時(本地)---> 本地的緩存中有沒有Etag和Last-Modified --->有--->發給服務器對應的字段 if-none-match 和if-modified-since ---> 服務器策略。若是這兩個字段和服務器上的這兩個字段相同 ---> 說明數據沒有更新--->返回304--->服務器從它的緩存庫中獲取到數據給客戶端--->顯示

--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存已過時(本地)---> 本地的緩存中有沒有Etag和Last-Modified --->有--->發給服務器對應的字段 if-none-match 和if-modified-since ---> 服務器策略。若是這兩個字段和服務器上的這兩個字段不相同 --->說明數據有更新--->返回200--->從新獲取--->顯示

四、如何實現對比緩存?

/**

    1. 第一次訪問服務器的時候,服務器返回資源和緩存的標識,客戶端則會把此資源緩存在本地的緩存數據庫中。
    1. 第二次客戶端須要此數據的時候,要取得緩存的標識,而後去問一下服務器個人資源是不是最新的。
  • 若是是最新的則直接使用緩存數據,若是不是最新的則服務器返回新的資源和緩存規則,客戶端根據緩存規則緩存新的數據。 */ 咱們經過標示字段來判斷緩存中的數據是否有效 這個標示有兩種形式:
第一種是最後修改時間,Last-Modified

一、Last-Modified:響應時告訴客戶端此資源的最後修改時間 二、If-Modified-Since:當資源過時時(使用Cache-Control標識的max-age),發現資源具備Last-Modified聲明,則再次向服務器請求時帶上頭If-Modified-Since。 三、服務器收到請求後發現有頭If-Modified-Since則與被請求資源的最後修改時間進行比對。若最後修改時間較新,說明資源又被改動過,則響應最新的資源內容並返回200狀態碼; 四、若最後修改時間和If-Modified-Since同樣,說明資源沒有修改,則響應304表示未更新,告知瀏覽器繼續使用所保存的緩存文件。

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
    let {pathname} = url.parse(req.url);
    let filepath = path.join(__dirname,pathname);
    console.log(filepath);
    fs.stat(filepath,function (err, stat) {
          if (err) {
              return sendError(req,res)
          } else {
              // 再次請求的時候會問服務器自從上次修改以後有沒有改過
              let ifModifiedSince = req.headers['if-modified-since'];
              console.log(req.headers);
              let LastModified = stat.ctime.toGMTString();
              console.log(LastModified);
              if (ifModifiedSince == LastModified) {
                  res.writeHead('304');
                  res.end('')
              } else {
                  return send(req,res,filepath,stat)
              }
          }
    })

}).listen(8080)

function send(req,res,filepath,stat) {
    res.setHeader('Content-Type', mime.getType(filepath));
    // 發給客戶端以後,客戶端會把此時間保存下來,下次再獲取此資源的時候會把這個時間再發給服務器
    res.setHeader('Last-Modified', stat.ctime.toGMTString());
    fs.createReadStream(filepath).pipe(res)
}

function sendError(req,res) {
    res.end('Not Found')
}
複製代碼
最後修改時間存在問題

一、某些服務器不能精確獲得文件的最後修改時間, 這樣就沒法經過最後修改時間來判斷文件是否更新了。 二、某些文件的修改很是頻繁,在秒如下的時間內進行修改. Last-Modified只能精確到秒。 三、一些文件的最後修改時間改變了,可是內容並未改變。 咱們不但願客戶端認爲這個文件修改了。 四、若是一樣的一個文件位於多個CDN服務器上的時候內容雖然同樣,修改時間不同。

第二種是Etag

ETag是實體標籤的縮寫,根據實體內容生成的一段hash字符串,能夠標識資源的狀態。當資源發生改變時,ETag也隨之發生變化。 ETag是Web服務端產生的,而後發給瀏覽器客戶端。

一、客戶端想判斷緩存是否可用能夠先獲取緩存中文檔的ETag,而後經過If-None-Match發送請求給Web服務器詢問此緩存是否可用。 二、服務器收到請求,將服務器的中此文件的ETag,跟請求頭中的If-None-Match相比較,若是值是同樣的,說明緩存仍是最新的,Web服務器將發送304 Not Modified響應碼給客戶端表示緩存未修改過,可使用。 三、若是不同則Web服務器將發送該文檔的最新版本給瀏覽器客戶端

let http = require('http');
let url = require('url');
let path = require('path');
let fs = require('fs');
let mime = require('mime');
let crypto = require('let crypto = require(\'mime\');\n');
// http://localhost:8080/index.html
http.createServer(function (req, res) {
    let {pathname} = url.parse(req.url);
    let filepath = path.join(__dirname,pathname);
    console.log(filepath);
    fs.stat(filepath,function (err, stat) {
        if (err) {
            return sendError(req,res)
        } else {

            let ifNoneMatch = req.headers['if-none-match'];
            // 1、顯然當咱們的文件很是大的時候經過下面的方法就行不通來,這時候咱們能夠用流來解決,能夠節約內存
            let out = fs.createReadStream(filepath);
            let md5 = crypto.createHash('md5');
            out.on('data',function (data) {
                md5.update(data)
            });
            out.on('end',function () {
                let etag = md5.update(content).digest('hex');
                // md5算法的特色 1. 相同的輸入相同的輸出 2.不一樣的輸入不通的輸出 3.不能根據輸出反推輸入 4.任意的輸入長度輸出長度是相同的
                if (ifNoneMatch == etag) {
                    res.writeHead('304');
                    res.end('')
                } else {
                    return send(req,res,filepath,stat, etag)
                }
            });
            
            // 2、再次請求的時候會問服務器自從上次修改以後有沒有改過
            // fs.readFile(filepath,function (err, content) {
            //     let etag = crypto.createHash('md5').update(content).digest('hex');
            //     // md5算法的特色 1. 相同的輸入相同的輸出 2.不一樣的輸入不通的輸出 3.不能根據輸出反推輸入 4.任意的輸入長度輸出長度是相同的
            //     if (ifNoneMatch == etag) {
            //         res.writeHead('304');
            //         res.end('')
            //     } else {
            //         return send(req,res,filepath,stat, etag)
            //     }
            // };
            // 可是上面的一方案也不是太好,讀一點緩存一點,文件很是大的話須要好長時間,並且咱們的node不適合cup密集型,即不適合來作大量的運算,因此說還有好多其餘的算法
            // 3、經過文件的修改時間減去文件的大小
            // let etag = `${stat.ctime}-${stat.size}`; // 這個也不是太好
            // if (ifNoneMatch == etag) {
            //     res.writeHead('304');
            //     res.end('')
            // } else {
            //     return send(req,res,filepath,stat, etag)
            // }
        }
    })

}).listen(8080)

function send(req,res,filepath,stat, etag) {
    res.setHeader('Content-Type', mime.getType(filepath));
    // 第一次服務器返回的時候,會把文件的內容算出來一個標示發送給客戶端
    //客戶端看到etag以後,也會把此標識符保存在客戶端,下次再訪問服務器的時候,發給服務器
    res.setHeader('Etag', etag);
    fs.createReadStream(filepath).pipe(res)
}

function sendError(req,res) {
    res.end('Not Found')
}
複製代碼
存在問題

都須要向服務器端發請求與服務器端發生交互

相關文章
相關標籤/搜索