node中的缓存机制
緩存是node開發中一個很重要的概念,它應用在很多地方,例如:瀏覽器有緩存、DNS有緩存、包括服務器也有緩存。
一、緩存作用
那緩存是為了做什么呢?
1.為了提高速度,提高效率。
2.減少數據傳輸,節省網費。
3.減少服務器的負擔,提高網站性能。
4.加快客戶端加載網頁的速度。
二、緩存分類
那緩存有幾種策略呢?
強制緩存:
1、概念:
客戶端訪問服務器請求資源,請求成功之后客戶端會緩存到本地,緩存到本地之后,如果以后客戶端再請求該資源此時不需要請求服務器了,直接訪問本地的就可以。
2、特點:
強制緩存不需要與服務器發生交互
3、客戶端訪問強制緩存的流程圖解
1)緩存命中 客戶端請求數據,現在本地的緩存數據庫中查找,如果本地緩存數據庫中有該數據,且該數據沒有失效。則取緩存數據庫中的該數據返回給客戶端。2)緩存未命中 客戶端請求數據,現在本地的緩存數據庫中查找,如果本地緩存數據庫中有該數據,且該數據失效。則向服務器請求該數據,此時服務器返回該數據和該數據的緩存規則返回給客戶端,客戶端收到該數據和緩存規則后,一起放到本地的緩存數據庫中留存。以備下次使用。
4、如何實現強制緩存?
1、瀏覽器會將文件緩存到Cache目錄,第二次請求時瀏覽器會先檢查Cache目錄下是否含有該文件,如果有,并且還沒到Expires設置的時間,即文件還沒有過期,那么此時瀏覽器將直接從Cache目錄中讀取文件,而不再發送請求 2、Expires是服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求,這是HTTP1.0的內容,現在瀏覽器均默認使用HTTP1.1,所以基本可以忽略 3、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); }對比緩存:
1、概念:
瀏覽器第一次請求數據時,服務器會將緩存標識與數據一起返回給客戶端,客戶端將二者備份至緩存數據庫中。 再次請求數據時,客戶端將備份的緩存標識發送給服務器,服務器根據緩存標識進行判斷,判斷成功后,返回304狀態碼,通知客戶端比較成功,可以使用緩存數據。
2、特點:需要進行比較判斷是否可以使用緩存
3、對比緩存流程圖解
1)客戶端第一次發請求
客戶端第一次請求數據,發現本地緩存中沒有,就向服務器發起請求,然后服務器把請求的數據返回給客戶端,并和客戶端商量你要保存到本地緩存中的規則,即是否緩存 緩存時間 有沒有標示 最后修改時間等信息。 2)客戶端第二次發請求 客戶端發起請請求 --->查看本地的緩存數據庫中是否有緩存---> 沒有---> 向服務器發起請求--->服務器返回200和響應內容--->顯示--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存沒有過期(本地)---> 緩存中讀取--->顯示
--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存已過期(本地)---> 本地的緩存中有沒有Etag和Last-Modified --->有--->發給服務器對應的字段 if-none-match 和if-modified-since ---> 服務器策略。如果這兩個字段和服務器上的這兩個字段相同 ---> 說明數據沒有更新--->返回304--->服務器從它的緩存庫中獲取到數據給客戶端--->顯示
--->查看本地的緩存數據庫中是否有緩存---> 有 ---> 緩存已過期(本地)---> 本地的緩存中有沒有Etag和Last-Modified --->有--->發給服務器對應的字段 if-none-match 和if-modified-since ---> 服務器策略。如果這兩個字段和服務器上的這兩個字段不相同 --->說明數據有更新--->返回200--->重新獲取--->顯示
4、如何實現對比緩存?
/**
- 第一次訪問服務器的時候,服務器返回資源和緩存的標識,客戶端則會把此資源緩存在本地的緩存數據庫中。
- 第二次客戶端需要此數據的時候,要取得緩存的標識,然后去問一下服務器我的資源是否是最新的。
- 如果是最新的則直接使用緩存數據,如果不是最新的則服務器返回新的資源和緩存規則,客戶端根據緩存規則緩存新的數據。 */ 我們通過標示字段來判斷緩存中的數據是否有效 這個標示有兩種形式:
第一種是最后修改時間,Last-Modified
1、Last-Modified:響應時告訴客戶端此資源的最后修改時間 2、If-Modified-Since:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Last-Modified聲明,則再次向服務器請求時帶上頭If-Modified-Since。 3、服務器收到請求后發現有頭If-Modified-Since則與被請求資源的最后修改時間進行比對。若最后修改時間較新,說明資源又被改動過,則響應最新的資源內容并返回200狀態碼; 4、若最后修改時間和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') }最后修改時間存在問題
1、某些服務器不能精確得到文件的最后修改時間, 這樣就無法通過最后修改時間來判斷文件是否更新了。 2、某些文件的修改非常頻繁,在秒以下的時間內進行修改. Last-Modified只能精確到秒。 3、一些文件的最后修改時間改變了,但是內容并未改變。 我們不希望客戶端認為這個文件修改了。 4、如果同樣的一個文件位于多個CDN服務器上的時候內容雖然一樣,修改時間不一樣。
第二種是Etag
ETag是實體標簽的縮寫,根據實體內容生成的一段hash字符串,可以標識資源的狀態。當資源發生改變時,ETag也隨之發生變化。 ETag是Web服務端產生的,然后發給瀏覽器客戶端。
1、客戶端想判斷緩存是否可用可以先獲取緩存中文檔的ETag,然后通過If-None-Match發送請求給Web服務器詢問此緩存是否可用。 2、服務器收到請求,將服務器的中此文件的ETag,跟請求頭中的If-None-Match相比較,如果值是一樣的,說明緩存還是最新的,Web服務器將發送304 Not Modified響應碼給客戶端表示緩存未修改過,可以使用。 3、如果不一樣則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'];// 一、顯然當我們的文件非常大的時候通過下面的方法就行不通來,這時候我們可以用流來解決,可以節約內存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)}});// 二、再次請求的時候會問服務器自從上次修改之后有沒有改過// 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密集型,即不適合來做大量的運算,所以說還有好多其他的算法// 三、通過文件的修改時間減去文件的大小// 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') }存在問題
都需要向服務器端發請求與服務器端發生交互
更多專業前端知識,請上 【猿2048】www.mk2048.com
總結
以上是生活随笔為你收集整理的node中的缓存机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: node那点事(二) -- Writab
- 下一篇: 《H5 移动营销设计指南》 读书笔记整理