[面經(jīng)]淺析 HTTP 緩存
1. 為什么需要緩存
通過緩存機(jī)制,可以在相應(yīng)場景下復(fù)用以前獲取的資源。 顯著提高網(wǎng)站的性能和響應(yīng)速度 減少網(wǎng)絡(luò)流量和等待渲染時間 降低服務(wù)器壓力
2. HTTP 緩存類型
強(qiáng)緩存 協(xié)商緩存
3. 強(qiáng)緩存
對于強(qiáng)緩存,服務(wù)器返回的靜態(tài)資源響應(yīng)頭會設(shè)置一個強(qiáng)制緩存的時間,在緩存時間內(nèi),如刷新瀏覽器請求相同資源,在緩存時間未過期的情況下,則直接使用已緩存資源。如緩存資源已過期,執(zhí)行協(xié)商緩存策略。
以下為與強(qiáng)緩存相關(guān)的 HTTP 頭部字段

3.1 Expires
響應(yīng)頭 Expires字段包含強(qiáng)緩存資源的過期時間值為 0 表示資源已過期或非強(qiáng)緩存
3.2 Cache-Control
通用消息頭字段,通過指令來實現(xiàn)緩存機(jī)制。說明一下容易弄混的兩個字段,其他指令參考指令大全[1]。
no-cache
在發(fā)布緩存副本之前,強(qiáng)制要求緩存把請求提交給原始服務(wù)器進(jìn)行驗證(協(xié)商緩存驗證)。
no-store
緩存不應(yīng)存儲有關(guān)客戶端請求或服務(wù)器響應(yīng)的任何內(nèi)容,即不使用任何緩存。
3.3 Expires 和 Cache-Control 的區(qū)別
時間區(qū)別 Expires過期時間為絕對時間,指未來某個時間點緩存過期。Cache-Control為相對時間,相對于當(dāng)前時間,如 60s 后緩存過期優(yōu)先級 Expires的優(yōu)先級低于Cache-Control字段同時存在 Cache-Control和Expires時,以Cache-Control指令為準(zhǔn)HTTP 版本 Expires是 HTTP/1.0 提出的,其瀏覽器兼容性更好Cache-Control是 HTTP/1.1 提出的,瀏覽器兼容性不佳,所以Expires和Cache-Control可以同時存在,在不支持Cache-Control的瀏覽器則以Expires為準(zhǔn)
4. 協(xié)商緩存
協(xié)商緩存即和服務(wù)器協(xié)商是否使用緩存,通過判斷后決定重新加載資源 or HTTP StatusCode 304 以下字段決定是否使用協(xié)商緩存,而非強(qiáng)緩存:

4.1 Pragma
Pragma是一個 HTTP1.0 中規(guī)定的通用首部,如果Cache-Control不存在的話,它的行為與Cache-Control: no-cache一致。強(qiáng)制要求緩存服務(wù)器在返回緩存的版本之前將請求提交到源頭服務(wù)器進(jìn)行協(xié)商驗證。Pragma的值就只有一個,no-cache,并且它的優(yōu)先級比Cache-Control高。
4.2 Cache-Control
上文介紹過 Cache-Control,它的指令既可以用于強(qiáng)緩存又可應(yīng)用于協(xié)商緩存策略中其中 Cache-Control: no-cache和Cache-Control: max-age=0的作用一樣,強(qiáng)制要求發(fā)起請求給服務(wù)器進(jìn)行驗證 (協(xié)商資源驗證)。
5. 協(xié)商策略
當(dāng)出現(xiàn)Pragma字段或者Cache-Control:no-cache時,就需要使用協(xié)商策略,常見的兩對協(xié)商緩存字段如下
ETag/If-None-MatchLast-Modified/If-Modfied-Since

優(yōu)缺點
如果服務(wù)端修改了一段代碼,然后又改回去了。 此時資源文件的修改時間變了 實際上文件沒有發(fā)生改變 這樣緩存就失效了,產(chǎn)生了不必要的傳輸 而 ETag 可以根據(jù)內(nèi)容生成的 hash 來比較的,只要資源文件內(nèi)容不變,就會應(yīng)用客戶端的緩存,減少不必要的傳輸。 所以 ETag 比 Last-Modified 緩存更精確、高效和節(jié)省帶寬。
6. ETag
6.1 什么是 ETag?
Etag 是 Entity tag 的縮寫,可以理解為“被請求資源的摘要標(biāo)識”,Etag 是服務(wù)端的一個資源的標(biāo)識,在 HTTP 響應(yīng)頭中將其傳送到客戶端,類似這樣,ETag:W/"50b1c1d4f775c61:df3"
6.2 ETag 格式
ETag:W/"xxxxxxxx"ETag:"xxxxxxx"
強(qiáng)類型驗證
比對資源每個字節(jié)都要一樣。
W/前綴代表使用弱類型驗證
不需要每個字節(jié)都一樣,例如頁腳的時間 or 展示的廣告不一樣,都可以認(rèn)為是一樣的。構(gòu)建應(yīng)用于弱驗證類型的標(biāo)簽(etag)體系可能會比較復(fù)雜,因為這會涉及到對頁面上不同的元素的重要性進(jìn)行排序,但是會對緩存性能優(yōu)化相當(dāng)有幫助。
6.3 ETag 生成需要滿足什么條件?
當(dāng)文件更改時,ETag 值必須改變 盡量便于計算,不會特別耗 CPU。 利用摘要算法生成(MD5, SHA128, SHA256)需慎重考慮,這些為 CPU 密集型運(yùn)算。 不是不能用。沒有最好的算法,只有適合對應(yīng)場景的算法。 必須橫向擴(kuò)展,分布式部署時多個服務(wù)器節(jié)點上生成的 ETag 值保持一致。
6.4 ETag 是怎么生成的(Nginx)
Nginx[2]的源碼中 ETag 由 last_modified 和 content_length 拼接而成
etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
r->headers_out.last_modified_time,
r->headers_out.content_length_n)
- etag->value.data;
翻譯為以下偽代碼
etag = header.last_modified + "-" + header.content_lenth
總結(jié):Nginx 中 ETag 由響應(yīng)頭的 Last-Modified和Content-Length表示為十六進(jìn)制組合而成。
Lodash 網(wǎng)站請求檢驗

const LAST_MODIFIED = new Date(parseInt('5fc4907d', 16) * 1000).toJSON()
const CONTENT_LENGTH = parseInt('f48', 16)
console.log(LAST_MODIFIED) // 2020-11-30T06:26:05.000Z
console.log(CONTENT_LENGTH) // 3912
輸出結(jié)果

既然在 nginx中ETag由Last-Modified和Content-Length組成,那它便算是一個加強(qiáng)版的Last-Modified了,那加強(qiáng)在什么地方呢?Last-Modified只能作用于秒級的改變,而nginx中的ETag添加了文件大小的附加條件,不僅和修改時間有關(guān),也和內(nèi)容有關(guān),使之更加精確。
6.5 Last-Modified 是怎么生成的
在 linux 中
mtime:modified time指文件內(nèi)容改變的時間戳ctime:change time指文件屬性改變的時間戳,屬性包括mtime。而在windows上,它表示的是creation time而 HTTP 服務(wù)選擇 Last-Modified時一般會選擇mtime,表示文件內(nèi)容修改的時間,來兼容 Windows 和 Linux。以下為nginx 源碼[3]
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;
6.6 如果 http 響應(yīng)頭中 ETag 值改變了,是否意味著文件內(nèi)容一定已經(jīng)更改?
不一定 文件在一秒內(nèi)發(fā)生了改變而且文件大小不變 這種情況非常極端,概率很低 因此在正常情況下可以容忍一個不太完美但是高效的算法。
參考資料
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control
[2]https://github.com/nginx/nginx/blob/6c3838f9ed45f5c2aa6a971a0da3cb6ffe45b61e/src/http/ngx_http_core_module.c#L1582
[3]https://github.com/nginx/nginx/blob/4bf4650f2f10f7bbacfe7a33da744f18951d416d/src/http/modules/ngx_http_static_module.c#L217
掃碼即可進(jìn)內(nèi)推群
