HTTP Cache 緩存機(jī)制

1. 前言

之前以為只有靜態(tài)資源,瀏覽器才會(huì)使用到緩存庆揪。但最近在做 api 流量優(yōu)化時(shí)拔第,發(fā)現(xiàn) API 請(qǐng)求也會(huì)有緩存來減少服務(wù)器端的帶寬壓力的拌喉。

其中發(fā)生作用的就是 ETag,也是這里重點(diǎn)講述的內(nèi)容押框。順便通過該知識(shí)點(diǎn)岔绸,把 HTTP Cache 的知識(shí)點(diǎn)也過了一下。

2. Browser & Server 通訊

image

瀏覽器與服務(wù)器通訊,其中 request & response 的內(nèi)容分別包含了:

Browser Request

瀏覽器請(qǐng)求信息盒揉,一般分為以下部分:

  • General
    主要包含了 URL晋被、HTTP Method,如:
Request URL: https://www.baidu.com/img/bd_logo1.png?where=super
Request Method: GET
  • Headers
    定義了信息來源刚盈、瀏覽器支持羡洛、緩存策略等。這里的信息一般是給 WEB 應(yīng)用服務(wù)器解析的藕漱。
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7
Cache-Control: no-cache # 這是因?yàn)槭褂昧藶g覽器強(qiáng)刷欲侮,所以 no-cache
Connection: keep-alive
Cookie: PSTM=1545632734;省略...
Host: www.baidu.com
Referer: https://www.baidu.com/
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
  • Parameters
    Headers 更多是通訊標(biāo)準(zhǔn)定義的信息,自定義的信息內(nèi)容(類似投遞的信封內(nèi)的信息)一般放在 Parameters肋联。這信息更多是給程序解析的威蕉。
where=super

Server Response

服務(wù)器收到了瀏覽器請(qǐng)求,處理然后響應(yīng)橄仍,一般分為以下部分:

  • General
Status Code: 200 OK
  • Headers
    一般包含信息的類型韧涨、大小、緩存策略侮繁、WEB應(yīng)用服務(wù)器等信息虑粥。
Accept-Ranges: bytes
Cache-Control: max-age=315360000
Connection: Keep-Alive
Content-Length: 7877
Content-Type: image/png
Date: Mon, 15 Jul 2019 06:50:14 GMT
Etag: "1ec5-502264e2ae4c0"
Expires: Thu, 12 Jul 2029 06:50:14 GMT
Last-Modified: Wed, 03 Sep 2014 10:00:27 GMT
Server: Apache
  • Body
    響應(yīng)回復(fù)的信息內(nèi)容。

HTTP 通訊過程中宪哩,瀏覽器 與 服務(wù)器主要是解讀和處理 headers 部分娩贷,Request parameters & Response body 主要是服務(wù)于具體業(yè)務(wù)部分,由程序解讀斋射。

是否應(yīng)用 cache育勺,也是通過 headers 的信息解析確定。

3. Cache

Cache 臺(tái)灣翻譯為 快取罗岖,內(nèi)地叫 緩存涧至。
通過 cache,可以節(jié)省流量桑包、時(shí)間等南蓬,減少資源的消耗。

舉例子哑了,一個(gè)電子商城赘方,首頁有上百張圖 和 API請(qǐng)求,如果沒有緩存弱左,每次進(jìn)來都需要重新加載所有內(nèi)容窄陡,流量是相當(dāng)驚人的,會(huì)加重服務(wù)器的帶寬壓力同時(shí)也會(huì)導(dǎo)致用戶下載等待過久影響體驗(yàn)拆火。

3.1 Cache 如何發(fā)生作用跳夭?

瀏覽器通過檢測(cè)請(qǐng)求的資源沒有==過時(shí)== 或者 ==內(nèi)容變化==涂圆,則從 cache 中取 response body。

這里可能有些人會(huì)有誤區(qū)币叹,緩存策略是不是意味著不請(qǐng)求服務(wù)器润歉?其實(shí)并不是,是會(huì)根據(jù)上面寫的觸發(fā) cache 的原因而有不同颈抚。

a) 按資源的有效期判斷

image

如圖所示踩衩,有部分資源服務(wù)器第一次響應(yīng)時(shí)會(huì)返回 max-age 或者 expires 告知瀏覽器,該資源在該時(shí)間范圍內(nèi)不產(chǎn)生變化贩汉。瀏覽器收到該信息后驱富,下次請(qǐng)求時(shí),會(huì)先檢查 max-age 和 expires雾鬼,如果在有效時(shí)間內(nèi)萌朱,則直接從 cache 中讀取。

image
  • 應(yīng)用場(chǎng)景:靜態(tài)資源策菜,如圖片晶疼、js、css又憨、html
  • Status Code:200 OK (from memory cache)
  • 緩存讀取方式:memory cache (有生命周期的)

b) 按資源的內(nèi)容判斷

image

當(dāng)資源不滿足 (a) 的緩存策略翠霍,無法從 memory cache 中獲取數(shù)據(jù)時(shí),并不意味著緩存策略失效蠢莺,還可以通過與服務(wù)器溝通來進(jìn)一步確定寒匙。(a) 中的活動(dòng)圖按(b)進(jìn)一步拓展為 (注意活動(dòng)圖的 Before max-age 與 Before expired date 不一定存在,response 存在時(shí)才需要判斷):


image
  • 應(yīng)用場(chǎng)景:動(dòng)態(tài)API躏将、動(dòng)態(tài)頁面锄弱、過期的靜態(tài)資源
  • Status Code: 304 Not modified (from disk cache)
  • 緩存讀取方式:disk cache

3.2 影響 cache 的 headers 因素

上一章節(jié)已經(jīng)講述了 cache 生效的機(jī)制,這里進(jìn)一步對(duì)生效中判斷的因素展開講解祸憋。

為了進(jìn)行學(xué)習(xí)会宪,特意找了一個(gè) API 進(jìn)行個(gè)例分析(去掉了一些非相關(guān)的項(xiàng)):

第一次請(qǐng)求:

General:
    Request URL: https://cd3.lcola.cn/backend/charge_stations
    Request Method: GET
    Status Code: 200 OK
    Referrer Policy: no-referrer-when-downgrade

Response Headers:
    Cache-Control: max-age=0, private, must-revalidate
    Date: Mon, 08 Jul 2019 09:05:48 GMT
    # 請(qǐng)求有返回 Entity Tag
    ETag: W/"25d6a32c560a93be6f4f3ba69f6353a6"
    
Request Headers:
    Cache-Control: no-cache
    Connection: keep-alive
    Pragma: no-cache

第二次請(qǐng)求:

General:
    Request URL: https://cd3.lcola.cn/backend/charge_stations
    Request Method: GET
    # 第二次狀態(tài)碼不同
    Status Code: 304 Not Modified
    Referrer Policy: no-referrer-when-downgrade
    
Response Headers:
    Cache-Control: max-age=0, private, must-revalidate
    Date: Mon, 08 Jul 2019 09:06:06 GMT
    # Entity Tag 不變
    ETag: W/"25d6a32c560a93be6f4f3ba69f6353a6"

Request Headers:
    # 請(qǐng)求的頭部信息,有對(duì)于 ETag 的判斷
    If-None-Match: W/"25d6a32c560a93be6f4f3ba69f6353a6"

通過上面的可以看到核心關(guān)鍵的 Response ETag 與 Request If-None-Match蚯窥。

開始來看看 瀏覽器 與 服務(wù)器如何通過 request 與 response 的 Pragma, Cache-Control, Etag, Last-Modified, Expires 進(jìn)行緩存機(jī)制掸鹅。

a) Expires

Response Headers:
    Expires: Wed, 21 Oct 2017 07:28:00 GMT

Cache 的到期時(shí)間(具體的日期與時(shí)間),由服務(wù)器端定義拦赠。

瀏覽器收到 response header 后巍沙,會(huì)將資源存儲(chǔ)起來,然后等到下一次訪問同一請(qǐng)求時(shí)荷鼠,會(huì)檢查【當(dāng)前時(shí)間】是否超過這個(gè)【Expires】句携。如果沒有超過,則瀏覽器不發(fā)請(qǐng)求允乐,直接從 disk cache 直接取务甥,Status code 會(huì)標(biāo)記為:Status code 200 (from disk cache)牡辽。

?? 瀏覽器對(duì)該時(shí)間的判斷會(huì)依賴于客戶端,如果把時(shí)間改為 2999年敞临,那么這個(gè)請(qǐng)求的緩存機(jī)制就失效了。

b) Cache-Control max-age

Response Headers:
    Cache-Control: max-age=30

Cache 失效的時(shí)間(秒)麸澜,通過計(jì)算下一次請(qǐng)求與第一次請(qǐng)求的的相隔時(shí)間挺尿,確定是否使用 cache。
可以有效解決 Expires 的客戶端時(shí)間依賴問題炊邦。

當(dāng)前 google 的首頁圖片就是通過 expires 與 max-age 進(jìn)行設(shè)置 cache 策略:

General:
    Request URL: https://www.google.com.hk/images/nav_logo299.webp
    Request Method: GET
    Status Code: 200  (from disk cache)
    
Response Headers:
    cache-control: private, max-age=31536000
    date: Thu, 30 May 2019 17:11:47 GMT
    expires: Thu, 30 May 2019 17:11:47 GMT
    last-modified: Tue, 23 Apr 2019 01:00:00 GMT

那么相同同時(shí)設(shè)置编矾,并且假設(shè) max-age 與 expires 的失效時(shí)間不統(tǒng)一,那么以哪個(gè)為準(zhǔn)馁害?

根據(jù)RFC2616的定義:

If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive

max-age 的設(shè)置會(huì)覆蓋 Expires窄俏,同時(shí)設(shè)置時(shí),生效的只有 max-age碘菜。

c) Last-Modified 與 If-Modified-Since

a & b 在“有效時(shí)間的緩存策略”判斷中發(fā)揮作用凹蜈,c & d 講到的會(huì)在“內(nèi)容變化的緩存策略”判斷中發(fā)揮作用。

Response Headers:
    Last-Modified: 2017-01-01 13:00:00
    Cache-Control: max-age=31536000

Server 端通過 response 返回該資源的修改時(shí)間忍啸。

那么下次訪問該請(qǐng)求時(shí)仰坦,瀏覽器會(huì)通過 request If-Modified-Since,將第一次訪問的時(shí)間 與 Last-Modified 對(duì)比计雌,如果沒有更新悄晃,服務(wù)器會(huì)回復(fù) ==Status code: 304 (Not Modified)==,客戶端會(huì)繼續(xù)使用該資源凿滤。

Request Headers:
    If-Modified-Since: Fri, 05 Jul 2019 02:14:23 GMT

Last-Modified 一般會(huì)結(jié)合 ETag 一起使用妈橄,確保是同一資源沒有被修改。

image

ETag 與 If-None-Match

對(duì)于靜態(tài)資源翁脆,可以通過 Last-Modified 來識(shí)別圖片是否被修改眷蚓,但是對(duì)于API請(qǐng)求,內(nèi)容基本都是根據(jù)請(qǐng)求參數(shù)動(dòng)態(tài)生成鹃祖,那么 Last-Modified 肯定是不適用溪椎。

動(dòng)態(tài)API請(qǐng)求,假設(shè)內(nèi)容相同恬口,是否可以應(yīng)用 cache 機(jī)制校读?
答案是:Yes。主要的實(shí)現(xiàn)是依賴于 reponse 的 ETag祖能。

image
Response Headers:
    ETag: W/"1d6f97d3adad0e99400af20a1421a974"

ETag 是 Entity Tag 的簡寫歉秫,是一個(gè)資源版本的唯一標(biāo)識(shí),類似于一個(gè)資源的對(duì)應(yīng)的 hash code养铸。

如果一個(gè) response 有 ETag雁芙,那么瀏覽器下一次訪問該資源時(shí)轧膘,會(huì)在請(qǐng)求的頭部加上 If-None-Match,如果 Server Response 返回的 ETag 一樣兔甘,那么表示內(nèi)容沒有變化谎碍,返回 ==Status code: 304 (Not Modified)==。瀏覽器就通過從 cache 中重新獲取數(shù)據(jù)洞焙。

Request Headers:
    If-None-Match: W/"a5759fbb890a0d32c4de53eea2264c03"

Strong v/s Weak ETags

"543b39c23d8d34c232b457297d38ad99"    – Strong ETag
W/"543b39c23d8d34c232b457297d38ad99"  – Weak ETag

ETag 分為 Strong 與 Weak

Strong ETag indicates that resource content is same for response body and the response headers.

Weak ETag indicates that the two representations are semantically equivalent. It compares only the response body.

Rails 4 默認(rèn)使用的是 Strong ETag, Rails5 默認(rèn)使用的是 Weak ETag蟆淀。

4. 案例講解

如何不使用 cache?

如果某些機(jī)密的信息澡匪,不希望留在客戶端熔任,可以 response 設(shè)置:

Response Headers:
    Cache-Control: no-store

上面是指完全不適用快照,每次都需要重新像服務(wù)器請(qǐng)求唁情。

而 ==Cache-Control: no-cache== 還是會(huì)請(qǐng)求服務(wù)器疑苔,只有當(dāng)檢查到頁面信息變化了,才不獲取緩存甸鸟。等價(jià)于設(shè)置 ==max-age=0==

可以看到文章頭部的例子中惦费,強(qiáng)刷時(shí)瀏覽器會(huì)給請(qǐng)求加上 ==Cache-Control: no-cache==。

如何解決頁面緩存導(dǎo)致的外部資源引用不更新哀墓?

頁面的基本 html 內(nèi)容不變趁餐,但是里面的 css/js/image 的信息變化了(名字沒有改變),這時(shí)候很容易因?yàn)轫撁婢彺娑鴮?dǎo)致了頁面上沒有應(yīng)用到最新的 css/js/image篮绰。

為了解決這種情況后雷,css/js/image url 加上更新的時(shí)間戳作為參數(shù),這樣當(dāng)他們變化時(shí)頁面 response 也會(huì)重新生成 ETag吠各,從而重新加載所有信息臀突。

或者像 webpack 打包一樣,資源重新生成一個(gè)帶 hash 的名字贾漏。

image

獲取資源時(shí)候学,Http method 該用 get 還是 post?

如果參照 restful 標(biāo)準(zhǔn)纵散,獲取資源時(shí) http method 應(yīng)使用 get梳码,但是有時(shí)候考慮到參數(shù)過長問題,部分時(shí)候會(huì)使用 post伍掀。正常情況掰茶,只是 定義不同,獲取方式不同蜜笤,實(shí)際對(duì)緩存策略也是有影響的濒蒋。

==獲取資源能用 GET 別用 POST==
因?yàn)?GET API 請(qǐng)求有 cache 機(jī)制支持,所以這是為什么請(qǐng)求不建議使用 POST。
POST 請(qǐng)求不提交 If-None-Match沪伙,所以 cache 不生效瓮顽。另外參數(shù)不在URL,每次請(qǐng)求因?yàn)閰?shù)變化導(dǎo)致的 response 內(nèi)容變化围橡,ETag 也變化暖混,cache 基本作廢。

5. 參考資料

循序漸進(jìn)理解 HTTP Cache 機(jī)制

MDN web docs - HTTP

W3C

Developers google - HTTP caching

plantuml
附上面用到的 plantuml 源碼:

@startuml

title HTTP Communication \n planttext.com

start

:Browser request;

note left
    HTTP url & method
end note

:Check last response;

if (a. Before max-age?) then (yes)
  :Get response \n__from memory cache__;
elseif (b. Before expired date?) then (yes)
  :Get response \n__from memory cache__;
else (no)
  :Communicate with server;
  if (c. If-Modified-Since?) then (no)
    :Response with\n status code 304 & no data;
    :Get response\n__from disk cache__;
  elseif (d. If-None-Match?) then (no)
    :Response with\n status code 304 & no data;
    :Get response\n__from disk cache__;
  else (yes)
    :Response with\n status code 200 & data;
  endif
endif
:Render;

stop

@enduml
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末某饰,一起剝皮案震驚了整個(gè)濱河市儒恋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌黔漂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禀酱,死亡現(xiàn)場(chǎng)離奇詭異炬守,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)剂跟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門减途,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曹洽,你說我怎么就攤上這事鳍置。” “怎么了送淆?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵税产,是天一觀的道長。 經(jīng)常有香客問我偷崩,道長辟拷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任阐斜,我火速辦了婚禮衫冻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谒出。我一直安慰自己隅俘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布笤喳。 她就那樣靜靜地躺著为居,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莉测。 梳的紋絲不亂的頭發(fā)上颜骤,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天悄泥,我揣著相機(jī)與錄音厢洞,去河邊找鬼耕肩。 笑死插掂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸠项。 我是一名探鬼主播干跛,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼祟绊!你這毒婦竟也來了楼入?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤牧抽,失蹤者是張志新(化名)和其女友劉穎嘉熊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扬舒,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阐肤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了讲坎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孕惜。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晨炕,靈堂內(nèi)的尸體忽然破棺而出衫画,到底是詐尸還是另有隱情,我是刑警寧澤瓮栗,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布削罩,位于F島的核電站,受9級(jí)特大地震影響遵馆,放射性物質(zhì)發(fā)生泄漏鲸郊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一货邓、第九天 我趴在偏房一處隱蔽的房頂上張望秆撮。 院中可真熱鬧,春花似錦换况、人聲如沸职辨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舒裤。三九已至,卻和暖如春觉吭,著一層夾襖步出監(jiān)牢的瞬間腾供,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伴鳖,地道東北人节值。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像榜聂,于是被迫代替她去往敵國和親搞疗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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