瀏覽器緩存概述
瀏覽器緩存分為強緩存和協(xié)商緩存。當客戶端請求某個資源時,獲取緩存的流程如下:
- 先根據這個資源的一些http header判斷它是否命中強緩存(后文再解釋什么是強緩存),如果命中(cache hit),則直接從本地獲取緩存資源,不會發(fā)請求到服務器;
- 當強緩存沒有命中時,客戶端會發(fā)送請求到服務器,服務器通過另一些request header驗證這個資源是否命中協(xié)商緩存(后文再解釋什么是協(xié)商緩存),稱為http再驗證(revalidation),如果命中(revalidate hit再驗證命中),服務器將請求返回,但不返回資源,而是告訴客戶端直接從緩存中獲取,客戶端收到返回后就會從緩存中獲取資源;
- 強緩存和協(xié)商緩存共同之處在于,如果命中緩存,服務器都不會返回資源;區(qū)別是,強緩存不對發(fā)送請求到服務器,但協(xié)商緩存會。
- 當協(xié)商緩存也沒命中時,服務器就會將資源發(fā)送回客戶端。
瀏覽器緩存分類
- 強緩存
直白點的方式描述下:客戶端第一次問服務器要某個資源時,服務器丟還給客戶端所請求的這個資源同時,告訴客戶端將這個資源保存在本地,并且在未來的某個時點之前如果還需要這個資源,直接從本地獲取就行了,不用向服務器請求。這種方式緩存下來的資源稱為強緩存。
強緩存利用http的返回頭部的中Expires(實體首部字段)或者Cache-Control(通用首部字段)兩個字段來控制的,用來表示資源的緩存時間。服務器通過這兩個首部字段告知客戶端資源的緩存過期時間和緩存最大生命周期。客戶端得知資源的緩存過期時間和最大生命周期后,即可自行判斷是否可不建立與服務器的鏈接,直接從瀏覽器緩存中獲取資源。
命中強緩存時,瀏覽器同樣會受到status=200的response,chrome中可通過size區(qū)分從服務器返回的資源還是強緩存獲得的資源。
- Expires
該字段是http1.0時的規(guī)范,值為一個絕對時間的GMT格式的時間字符串,代表緩存資源的過期時間,在這個時點之前,即命中緩存。其過程如下:
-
瀏覽器第一次跟服務器請求一個資源時,服務器在返回這個資源時,在相應頭部會加上Expires,如圖:
1111111.png
- 瀏覽器接收到該資源后,會把這個資源連同response header一起緩存下來;
- 瀏覽器再次請求這個資源時,會先從緩存中找到這個資源,然后獲取Expires時間與當前的請求時間比較,如果Expires時間比當前瀏覽器的請求時間晚,說明緩存未過期,即命中緩存;
- 如果當前請求時間比Expires晚,說明緩存過期,即未命中緩存,瀏覽器就會發(fā)送請求到服務器申請獲取資源。
缺點:服務器返回的Expires時點是服務器上的時間,可能與客戶端有時間差,時間差太大時可能造成緩存混亂。
Cache-Control:max-age
該字段是http1.1的規(guī)范,強緩存利用其max-age值來判斷緩存資源的最大生命周期,它的值單位為秒,Cache-Control : max-age=3600代表緩存資源有效時間為1小時,即從第一次獲取該資源起一小時內的請求都被認為可命中強緩存。其過程如下:
-
瀏覽器第一次跟服務器請求一個資源時,服務器在返回這個資源時,在相應頭部會加上Cache-Control:max-age=XXXXXXXX,如圖:
2222222222.png 瀏覽器接收到資源后,連同response header一起緩存下來;
- 瀏覽器再次跟服務器請求同一個資源時,會先從緩存中找到這個資源,獲取date(第一次資源返回的響應時間)和max-age并計算出一個有效期(date + max-age),然后再和瀏覽器請求時間比較最后判斷是否命中緩存(邏輯同Expires);
- 如果沒有命中緩存,瀏覽器直接從服務器請求資源,Cache-Control會在重新獲取到服務器返回資源時更新。
Cache-Control描述的是相對時間,采用本地時間來計算資源的有效期,所以相比Expires更可靠。
這兩個Header可以只用其一,也可以一起使用。一起使用時以Cache-Control為準。
- 協(xié)商緩存
直白點的方式描述下:客戶端第一次問服務器要某個資源時,服務器丟還給客戶端所請求的這個資源同時,將該資源的一些信息(文件摘要、或者最后修改時間)也返回給客戶端,告訴客戶端將這個資源緩存在本地。當客戶端下一次需要這個資源時,將請求以及相關信息(文件摘要、或者最后修改時間)一并發(fā)送給服務器,由服務器來判斷客戶端緩存的資源是否需要更新:如不需要更新,就直接告訴客戶端獲取本地緩存資源;如需要更新,則將最新的資源連同相應的信息一并返回給客戶端。
當強緩存未命中時,瀏覽器就會發(fā)送請求到服務器,服務器會驗證協(xié)商緩存是否命中,如果協(xié)商緩存命中,請求返回的http狀態(tài)為304,并會顯示說明Not Modified,瀏覽器收到該返回后,就會從緩存中加載了。
協(xié)商緩存利用[Last-Modified , If-Modified-Since] 和 [ETag , If-None-Match]這兩對Header來管理。
(1)Last-Modified & If-Modified-Since
Last-Modified為實體首部字段,值為資源最后更新時間,隨服務器response返回。
If-Modified-Since為請求首部字段,通過比較兩個時間來判斷資源在兩次請求期間是否有過修改,如果沒有修改,則命中協(xié)商緩存,瀏覽器從緩存中獲取資源;如果有過修改,則服務器返回資源,同時返回新的Last-Modified時間。其過程如下:
- 瀏覽器第一次請求服務器資源,且服務器返回了該資源時,會在response headers中加上Last-Modified,這個header表示這個資源在服務器上的最后一次修改時間;
- 當瀏覽器再次請求該資源時,會在request headers中加上If-Modified-Since,這個值即為上一次服務器返回的Last-Modified時間;
- 服務器再次收到資源請求時,將If-Modified-Since時間和資源在服務器上的最后修改時間與對比,如果If-Modifid-Since與最后修改時間一致,則命中緩存,服務器返回304,瀏覽器從緩存中獲取資源;若未命中緩存,服務器返回資源同時,瀏覽器緩存資源的Last-Modified會被更新。
(2)ETag & If-None-Match
有些情況下僅判斷最后修改日期來驗證資源是否有改動是不夠的:
- 存在周期性重寫某些資源,但資源實際包含的內容并無變化;
- 被修改的信息并不重要,如注釋等;
- Last-Modified無法精確到毫秒,但有些資源更新頻率有時會小于一秒。
為解決這些問題,http允許用戶對資源打上標簽(ETag)來區(qū)分兩個相同路徑獲取的資源內容是否一致。通常會采用MD5等密碼散列函數對資源編碼得到標簽(強驗證器);或者通過版本號等方式,如W/”v1.0”(W/表示弱驗證器)。
ETag為相應頭部字段,表示資源內容的唯一標識,隨服務器response返回;
If-None-Match為請求頭部字段,服務器通過比較請求頭部的If-None-Match與當前資源的ETag是否一致來判斷資源是否在兩次請求之間有過修改,如果沒有修改,則命中協(xié)商緩存,瀏覽器從緩存中獲取資源;如果有過修改,則服務器返回資源,同時返回新的ETag。其過程如下:
- 服務器第一次收到瀏覽器發(fā)出的資源請求時,會在response headers中加上ETag,這個ETag是根據該資源生成的唯一標識,這個唯一標識是個字符串,只要服務器認為資源有變化且應該提供新的資源,則ETag就必須有變化。瀏覽器將資源連同ETag一并緩存。
- 當瀏覽器再一次向服務器發(fā)送該資源的請求時,會在request headers中加上If-None-Match,該值即為第一次服務器返回的ETag值;
- 服務器收到資源請求后,會根據要請求的資源重新計算生成相應的ETag,然后與If-None-Match比較。對比結果一致即命中緩存,不一致則未命中緩存,返回資源同時將新的ETag發(fā)送至瀏覽器。
(3)協(xié)商緩存管理
[Last-Modified , If-Modified-Since]和[ETag , If-None-Match]一般同時啟用,這是為了處理Last-Modified不可靠的情況。
其他注意事項
- 分布式系統(tǒng)里多臺服務器間的文件的Last-Modified必須保持一致,以免負載均衡到不同服務器導致對比結果不一致;
- 分布式系統(tǒng)盡量關閉掉ETag(每臺機器生成的ETag都會不一樣,淘寶頁面中的靜態(tài)資源response headers中都沒有ETag)。