http緩存

前言

http的緩存是老生常談的問題热芹,基本面試專用的,看到的文章挺多的桐臊,但都是一些原理性的文章胎撤,基本沒有真正實(shí)踐過怎么緩存,就會(huì)形成一種道理大家都懂断凶,但誰真正實(shí)踐過呢伤提,從而到用的時(shí)候,卻發(fā)現(xiàn)不會(huì)怎么做緩存认烁,如何配置呢肿男,也一無所知。所以本文從理論和實(shí)踐上試了一下怎么認(rèn)識(shí)緩存砚著。這里有借鑒前輩們的經(jīng)驗(yàn)文章次伶,但對(duì)于生產(chǎn)環(huán)境上服務(wù)器的緩存還是懵懂的,請(qǐng)各位大佬賜教稽穆,同時(shí)歡迎各位大佬指正冠王。

緩存的規(guī)則

我們知道HTTP的緩存屬于客戶端緩存,后面會(huì)提到為什么屬于客戶端緩存舌镶。所以我們認(rèn)為瀏覽器存在一個(gè)緩存數(shù)據(jù)庫(kù)柱彻,用于儲(chǔ)存一些不經(jīng)常變化的靜態(tài)文件(圖片、css餐胀、js等)哟楷。我們將緩存分為強(qiáng)制緩存和協(xié)商緩存。下面我將分別詳細(xì)的介紹這兩種緩存的緩存規(guī)則否灾。

強(qiáng)制緩存

當(dāng)緩存數(shù)據(jù)庫(kù)中已有所請(qǐng)求的數(shù)據(jù)時(shí)卖擅。客戶端直接從緩存數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)墨技。當(dāng)緩存數(shù)據(jù)庫(kù)中沒有所請(qǐng)求的數(shù)據(jù)時(shí)惩阶,客戶端的才會(huì)從服務(wù)端獲取數(shù)據(jù)。

協(xié)商緩存

又稱對(duì)比緩存扣汪,客戶端會(huì)先從緩存數(shù)據(jù)庫(kù)中獲取到一個(gè)緩存數(shù)據(jù)的標(biāo)識(shí)断楷,得到標(biāo)識(shí)后請(qǐng)求服務(wù)端驗(yàn)證是否失效(新鮮),如果沒有失效服務(wù)端會(huì)返回304崭别,此時(shí)客戶端直接從緩存中獲取所請(qǐng)求的數(shù)據(jù)冬筒,如果標(biāo)識(shí)失效恐锣,服務(wù)端會(huì)返回更新后的數(shù)據(jù)。

小貼士:

我們可以看到兩類緩存規(guī)則的不同舞痰,強(qiáng)制緩存如果生效土榴,不需要再和服務(wù)器發(fā)生交互,而對(duì)比緩存不管是否生效匀奏,都需要與服務(wù)端發(fā)生交互鞭衩。
兩類緩存規(guī)則可以同時(shí)存在学搜,強(qiáng)制緩存優(yōu)先級(jí)高于對(duì)比緩存娃善,也就是說,當(dāng)執(zhí)行強(qiáng)制緩存的規(guī)則時(shí)瑞佩,如果緩存生效聚磺,直接使用緩存,不再執(zhí)行對(duì)比緩存規(guī)則炬丸。

緩存的方案

上面的內(nèi)容讓我們大概了解了緩存機(jī)制是怎樣運(yùn)行的瘫寝,但是,服務(wù)器是如何判斷緩存是否失效呢稠炬?我們知道瀏覽器和服務(wù)器進(jìn)行交互的時(shí)候會(huì)發(fā)送一些請(qǐng)求數(shù)據(jù)和響應(yīng)數(shù)據(jù)焕阿,我們稱之為HTTP報(bào)文。報(bào)文中包含首部header和主體部分body首启。與緩存相關(guān)的規(guī)則信息就包含在header中暮屡。boby中的內(nèi)容是HTTP請(qǐng)求真正要傳輸?shù)牟糠帧Ee個(gè)HTTP報(bào)文header部分的例子如下:

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 09 Mar 2019 02:59:02 GMT
Content-Type: application/javascript; charset=utf8
Content-Length: 1544092
Last-Modified: Sat, 09 Mar 2019 00:52:32 GMT
Connection: keep-alive
ETag: "5c830e50-178f9c"
Accept-Ranges: bytes
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Host: xlink-iot.qinyuan.cn
If-Modified-Since: Fri, 08 Mar 2019 06:56:18 GMT
If-None-Match: "5c821212-178f9c"
Referer: https://xlink-iot.qinyuan.cn/iot/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36

接下來我們將對(duì)HTTP報(bào)文中出現(xiàn)的與緩存規(guī)則相關(guān)的信息做出詳細(xì)解釋毅桃。(我們依舊分為強(qiáng)制緩存和協(xié)商緩存兩個(gè)方面來介紹)

強(qiáng)制緩存

對(duì)于強(qiáng)制緩存褒纲,服務(wù)器響應(yīng)的header中會(huì)用兩個(gè)字段來表明——Expires和Cache-Control

Expires

Exprires的值為服務(wù)端返回的數(shù)據(jù)到期時(shí)間。當(dāng)再次請(qǐng)求時(shí)的請(qǐng)求時(shí)間小于返回的此時(shí)間钥飞,則直接使用緩存數(shù)據(jù)莺掠。但由于服務(wù)端時(shí)間和客戶端時(shí)間可能有誤差,這也將導(dǎo)致緩存命中的誤差读宙,另一方面彻秆,Expires是HTTP1.0的產(chǎn)物,故現(xiàn)在大多數(shù)使用Cache-Control替代结闸。

Cache-Control

Cache-Control有很多屬性唇兑,不同的屬性代表的意義也不同。
private:客戶端可以緩存
public:客戶端和代理服務(wù)器都可以緩存
max-age=t:緩存內(nèi)容將在t秒后失效
no-cache:需要使用協(xié)商緩存來驗(yàn)證緩存數(shù)據(jù)
no-store:所有內(nèi)容都不會(huì)緩存膀估。

協(xié)商緩存

協(xié)商緩存需要進(jìn)行對(duì)比判斷是否可以使用緩存幔亥。瀏覽器第一次請(qǐng)求數(shù)據(jù)時(shí),服務(wù)器會(huì)將緩存標(biāo)識(shí)與數(shù)據(jù)一起響應(yīng)給客戶端察纯,客戶端將它們備份至緩存中帕棉。再次請(qǐng)求時(shí)针肥,客戶端會(huì)將緩存中的標(biāo)識(shí)發(fā)送給服務(wù)器,服務(wù)器根據(jù)此標(biāo)識(shí)判斷香伴。若未失效逃顶,返回304狀態(tài)碼,瀏覽器拿到此狀態(tài)碼就可以直接使用緩存數(shù)據(jù)了稼钩。
對(duì)于協(xié)商緩存來說谭网,緩存標(biāo)識(shí)我們需要著重理解一下,下面我們將著重介紹它的兩種緩存方案低斋。

Last-Modified

Last-Modified
服務(wù)器在響應(yīng)請(qǐng)求時(shí)蜂厅,會(huì)告訴瀏覽器資源的最后修改時(shí)間。

if-Modified-Since:
瀏覽器再次請(qǐng)求服務(wù)器的時(shí)候膊畴,請(qǐng)求頭會(huì)包含此字段掘猿,后面跟著在緩存中獲得的最后修改時(shí)間。服務(wù)端收到此請(qǐng)求頭發(fā)現(xiàn)有if-Modified-Since唇跨,則與被請(qǐng)求資源的最后修改時(shí)間進(jìn)行對(duì)比稠通,如果一致則返回304和響應(yīng)報(bào)文頭,瀏覽器只需要從緩存中獲取信息即可买猖。
從字面上看改橘,就是說:從某個(gè)時(shí)間節(jié)點(diǎn)算起,是否文件被修改了

  1. 如果真的被修改:那么開始傳輸響應(yīng)一個(gè)整體玉控,服務(wù)器返回:200 OK
  2. 如果沒有被修改:那么只需傳輸響應(yīng)header飞主,服務(wù)器返回:304 Not Modified

Last-Modified 有個(gè)問題,因?yàn)槿绻诜?wù)器上奸远,一個(gè)資源被修改了既棺,但其實(shí)際內(nèi)容根本沒發(fā)生改變,會(huì)因?yàn)長(zhǎng)ast-Modified時(shí)間匹配不上而返回了整個(gè)實(shí)體給客戶端(即使客戶端緩存里有個(gè)一模一樣的資源)懒叛。為了解決這個(gè)問題丸冕,HTTP1.1推出了Etag。

Etag

Etag:
服務(wù)器響應(yīng)請(qǐng)求時(shí)薛窥,通過此字段告訴瀏覽器當(dāng)前資源在服務(wù)器生成的唯一標(biāo)識(shí)(生成規(guī)則由服務(wù)器決定)

If-None-Match:
再次請(qǐng)求服務(wù)器時(shí)胖烛,瀏覽器的請(qǐng)求報(bào)文頭部會(huì)包含此字段,后面的值為在緩存中獲取的標(biāo)識(shí)诅迷。服務(wù)器接收到次報(bào)文后發(fā)現(xiàn)If-None-Match則與被請(qǐng)求資源的唯一標(biāo)識(shí)進(jìn)行對(duì)比佩番。

  1. 不同,說明資源被改動(dòng)過罢杉,則響應(yīng)整個(gè)資源內(nèi)容趟畏,返回狀態(tài)碼200。
  2. 相同滩租,說明資源無心修改赋秀,則響應(yīng)header利朵,瀏覽器直接從緩存中獲取數(shù)據(jù)信息。返回狀態(tài)碼304.

緩存的優(yōu)點(diǎn)

  1. 減少了冗余的數(shù)據(jù)傳遞猎莲,節(jié)省寬帶流量
  2. 減少了服務(wù)器的負(fù)擔(dān)绍弟,大大提高了網(wǎng)站性能
  3. 加快了客戶端加載網(wǎng)頁(yè)的速度
    這也正是HTTP緩存屬于客戶端緩存的原因。

問題

強(qiáng)緩存

1.服務(wù)器怎么返回?cái)?shù)據(jù)到期時(shí)間著洼,怎么緩存(需要服務(wù)器進(jìn)行配置)

Cache-Control

let http = require('http')
let url = require('url')
let fs = require('mz/fs')
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {

    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath, '來請(qǐng)求服務(wù)了')
    try{
        let statObj = await fs.stat(realPath);
        res.setHeader('Cache-Control','max-age=180')  //強(qiáng)制緩存 180s內(nèi)不需要再次請(qǐng)求服務(wù)器
        //res.setHeader('Cache-Control','no-cache')

        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
       fs.createReadStream(realPath).pipe(res)
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)
// 我們請(qǐng)求一個(gè)本地的文件index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="./index.css">
    <!-- <meta http-equiv="Cache-control" content="no-cache"> -->
</head>
<body>
    
    <img src="/he.png"/>
</body>
<script src="./index.js"></script>
</html>

返回頭:

Cache-Control: max-age=180
Connection: keep-alive
Content-Type: text/html;charset=utf8
Date: Sat, 09 Mar 2019 06:42:58 GMT
Transfer-Encoding: chunked

上述中的文件樟遣,針對(duì)強(qiáng)制緩存,除html文件外其他的資源在180s內(nèi)均在緩存中讀取身笤,另外強(qiáng)調(diào)一點(diǎn):主網(wǎng)頁(yè)只有對(duì)比緩存沒有強(qiáng)制緩存豹悬,html文件每次都是重新請(qǐng)求服務(wù)器文件即使設(shè)置了<meta http-equiv="Cache-Control" content="max-age=180" />

expires
//Expires:Sun, 22 Jul 2018 02:43:42 GMT
//備注:如果Cache-Control和Expires同時(shí)存在,Cache-Control說了算
res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強(qiáng)制緩存的另一種方式

2.客戶端需要如何設(shè)置

沒有展鸡,暫時(shí)找不到

協(xié)商緩存

//對(duì)比緩存 為了更加明顯的看到對(duì)比緩存屿衅,我們將在以下的代碼中都將強(qiáng)制緩存關(guān)閉
//res.setHeader('Cache-Control','no-cache')

//響應(yīng)頭設(shè)置了res.setHeader('Last-Modified',statObj.ctime.toGMTString())
//請(qǐng)求頭就會(huì)帶上req.headers['if-modified-since']

let http = require('http')
let url = require('url')
let util = require('util')
let fs = require('mz/fs')
let stat = util.promisify(fs.stat);
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath)
    try{
        let statObj = await fs.stat(realPath);
        console.log(statObj)
        // res.setHeader('Cache-Control','max-age=10')  //強(qiáng)制緩存  10s內(nèi)不需要再次請(qǐng)求服務(wù)器
        res.setHeader('Cache-Control','no-cache')
        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
        res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強(qiáng)制緩存 因?yàn)樯厦嬖O(shè)置了no-cache埃难,所以這里的設(shè)置其實(shí)無效
        let since = req.headers['if-modified-since'];
        if (since === statObj.ctime.toGMTString()) {
            res.statusCode = 304                      //服務(wù)器的緩存
            res.end();
        } else {
            res.setHeader('Last-Modified',statObj.ctime.toGMTString())
            fs.createReadStream(realPath).pipe(res)
        }
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)

返回頭

Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/html;charset=utf8
Date: Sat, 09 Mar 2019 07:22:17 GMT
Last-Modified: Sat, 09 Mar 2019 07:04:41 GMT
Transfer-Encoding: chunked

在瀏覽器打開localhost:3000/index.html 刷新看到就是304莹弊,我們返回狀態(tài)碼304,瀏覽器就乖乖地去讀緩存中的文件了涡尘。我們稍微改動(dòng)一下index.html就可以看到 200

Etag

//對(duì)比緩存
//Etag內(nèi)容的標(biāo)識(shí) 
// 響應(yīng)頭設(shè)置了res.setHeader('Etag',statObj.size.toString());  這里設(shè)置的是文件大小
//請(qǐng)求頭就會(huì)帶上req.headers['if-none-match'];
let http = require('http');
let util = require('util');
let fs = require('fs');
let stat = util.promisify(fs.stat);
let url = require('url');
let path = require('path');
let p = path.resolve(__dirname);

// 比較內(nèi)容 stat.size
// 第一次請(qǐng)求Etag:內(nèi)容的標(biāo)識(shí)  
// 第二次在請(qǐng)求我的時(shí)候 if-none-match 

http.createServer(async function(req,res){
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p,pathname);
    try{
        let statObj = await stat(realPath);
        console.log(realPath) 
        res.setHeader('Cache-Control','no-cache');
        let match = req.headers['if-none-match'];
        if(match){
            if(match === statObj.size.toString()){
                res.statusCode = 304;
                res.end();
            }else{
                res.setHeader('Etag',statObj.size.toString());
                fs.createReadStream(realPath).pipe(res);
            }
        }else{
            res.setHeader('Etag',statObj.size.toString());
            fs.createReadStream(realPath).pipe(res);
        }
       
    }catch(e){
        res.statusCode = 404;
        res.end(`not found`);
    }
}).listen(3000);

兩種緩存的區(qū)別

  1. 強(qiáng)制緩存
  • 設(shè)置強(qiáng)制緩存的方式就是 res.setHeader('Cache-Control','max-age=10')
  • res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString())
  • 以上兩種以第一種方式取決定作用
  1. 對(duì)比緩存
  • 通過時(shí)間對(duì)比 Last-Modified ---- if-modified-since
  • 通過標(biāo)識(shí)對(duì)比 Etag ---- if-none-match

總結(jié)

緩存只能服務(wù)端配置H坛凇!?汲细疚!,目前暫時(shí)沒有找到客戶端能做的事川梅,html5的meta標(biāo)簽好像在也沒什么用疯兼,感謝大佬指正

參考文檔

聊聊web緩存那些事!

HTTP----HTTP緩存機(jī)制

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贫途,一起剝皮案震驚了整個(gè)濱河市吧彪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌丢早,老刑警劉巖姨裸,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怨酝,居然都是意外死亡傀缩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門农猬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赡艰,“玉大人,你說我怎么就攤上這事斤葱】犊澹” “怎么了勋又?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)换帜。 經(jīng)常有香客問我楔壤,道長(zhǎng),這世上最難降的妖魔是什么惯驼? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任蹲嚣,我火速辦了婚禮,結(jié)果婚禮上祟牲,老公的妹妹穿的比我還像新娘隙畜。我一直安慰自己,他們只是感情好说贝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布议惰。 她就那樣靜靜地躺著,像睡著了一般乡恕。 火紅的嫁衣襯著肌膚如雪言询。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天傲宜,我揣著相機(jī)與錄音运杭,去河邊找鬼。 笑死函卒,一個(gè)胖子當(dāng)著我的面吹牛辆憔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播报嵌,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼虱咧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了锚国?” 一聲冷哼從身側(cè)響起腕巡,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎跷叉,沒想到半個(gè)月后逸雹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡云挟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年梆砸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片园欣。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帖世,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情日矫,我是刑警寧澤赂弓,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站哪轿,受9級(jí)特大地震影響盈魁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窃诉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一杨耙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飘痛,春花似錦珊膜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至塑猖,卻和暖如春竹祷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萌庆。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工溶褪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人践险。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吹菱,于是被迫代替她去往敵國(guó)和親巍虫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容