輕鬆理解HTTP緩存策略

上一篇文章我寫了koa-static的源碼解析,其中用到了HTTP的緩存策略,給返回的靜態文件設置了一些緩存的頭,好比Cache-Control之類的。因而我就跟朋友討論了一下HTTP的緩存策略:javascript

朋友說:「HTTP裏面控制緩存的頭(header)太多了,啥Cache-ControlETagLast-Modified,一大堆,亂七八糟的,並且之間邏輯關係不強,要掌握基本靠背!」html

我有點驚訝:「爲何要去背這個呢?全部的技術都是爲了解決問題而存在的,不瞭解問題而去單純的學習技術,去,背,去,死記,確實很枯燥,並且效果很差。HTTP緩存策略只是爲了解決客戶端和服務端信息不對稱的問題而存在的,客戶端爲了加快速度會緩存部分資源,可是下次請求時,客戶端不知道這個資源有沒有更新,服務端也不知道客戶端緩存的是哪一個版本,不知道該不應再返回資源,其實就是一個信息同步問題,HTTP緩存策略就是來解決這個問題的。若是咱們跳出這種純粹的技術思惟,咱們會發現生活中這種信息同步問題也很常見。而咱們解決這些問題的思路不少時候都是司空見慣了,若是從這個角度來講,這個問題就很好理解!」前端

因而我給他講了一個我小時候租光碟看奧特曼的故事。java

租光碟看奧特曼

事情是這樣的,我小時候特別喜歡看動畫片,尤爲是奧特曼,可是那時候沒有電腦啊,也沒有網絡。我只有一臺DVD播放機,因而我會常常跑去租光碟的店租奧特曼。git

ETag

某天,我看完了《艾斯奧特曼》第10集,我還想繼續看。因而我找到了光碟店的老闆:「老闆,第10集我看完了哦,你還有沒有新的啊?」老闆說:「有有有,剛出了第11集,你拿去吧!」github

上面這一個簡單的交流過程其實就包含了一個HTTP的緩存技術,那就是ETag!類比於網絡請求,我其實就是客戶端,光碟店就是服務端,我去租光碟就至關於發起一個請求。可是我去租光碟時,老闆並不知道我看到哪集了,咱們的信息是不一樣步的。因此我告訴了他一個標記(Tag),在這裏這個標記就是第10集,老闆拿到這個標記,跟他本身庫存的標記比較一下,發現他最新標記是第11集,因而知道有更新了,將第11集給了我。瀏覽器

Last-Modified & If-Modified-Since

再來,我《艾斯奧特曼》看完了,我開始看《泰羅奧特曼》了。但是老闆此次比較雞賊,《泰羅奧特曼》沒買正版的,是他本身翻錄的,他翻錄的時候本身也不知道是第幾集,可是他聰明的在光盤上寫上了翻錄日期。因而我正在看的這盤也沒啥封面,只光禿禿的寫了一個2000年12月1日。當我這盤看完了,我又去找老闆了:「老闆,你這個2000年12月1日的我已經看完了,你還有沒有新的啊?」這裏的2000年12月1日其實就是標記了我手上副本的更新日期,這也對應了HTTP的一個緩存技術,那就是Last-ModifiedIf-Modified-Since。你能夠理解爲,老闆給日期還取了一個名字,叫Last-Modified,因此光碟上完整文字是Last-Modified:2000年12月1日,而我去問的時候就這麼問:「Do you have any updates IF-Modified-Since 2000年12月1日?」。緩存

Expires和Max-Age

繼續,我《泰羅奧特曼》也看完了,開始看《雷歐奧特曼》了。這《雷歐奧特曼》跟前面兩個都不同,我去租的時候老闆就說了:「你小子別每天跑來問了!《雷歐奧特曼》我每週去進一次貨,你每週一來拿就行!」這句話也對應了一個HTTP緩存技術,那就是ExpiresMax-Age。我知道了下週一以前,我手上都是最新的,到了下週一就過時(Expire)了。因此「我手上的是最新的」這個說法有個生命週期,他的年齡是有限的,他的年齡等於下週一更新時間減去當前時間,這就是他的最大年齡(Max-Age)。服務器

Immutable

再來一個,我《雷歐奧特曼》也看完了,開始看《奈克斯特奧特曼》了。這《奈克斯特奧特曼》跟前面幾個都不同,我去租的時候老闆說了:「小子,你此次運氣好,這《奈克斯特奧特曼》已經出完了,你所有拿去吧,也不用每天跑來問了!」這句話對應的HTTP緩存技術是啥?固然是ImmutableImmutable就跟字面意思同樣,不可變的!就像《奈克斯特奧特曼》同樣,已經出完了,不用再去問更新了。網絡

言歸正傳

扯蛋到這裏結束,我們言歸正傳!之因此舉這麼個例子,是爲了說明HTTP緩存技術要解決的問題在生活中很常見,從這些常見的場景入手,理解起來更簡單。下面咱們正兒八經的來講說HTTP緩存技術:

兩種機制

從上面的幾個小例子能夠看出,有時候爲了知道是否是有更新,我必須去問老闆,好比第一個例子裏面:「老闆,第10集我看完了哦,你還有沒有新的啊?」。這種爲了知道有沒有更新,必須跟服務端溝經過才知道的,咱們稱之爲協商緩存。還有些場景,我不去問就知道有沒有更新,好比第三個例子,由於知道是周更的,當週一來以前,我都不會去問了,到了週一再去問,這種不用跟服務器協商直接用本地副本的叫作強制緩存。換成技術的話說就是,強制緩存不用發請求直接用本地緩存,協商緩存要發請求去問服務器有沒有更新。下面咱們詳細來說下這兩種緩存:

協商緩存

前面第一個例子和第二個例子每次都須要向服務器端詢問,因此是協商緩存

ETag和If-None-Match

ETag是URL的Entity Tag,就是一個URL資源的標識符,相似於文件的md5,計算方式也相似,當服務器返回時,能夠根據返回內容計算一個hash值或者就是一個數字版本號,相似於咱們的第10集,具體返回什麼值要看服務器的計算策略。而後將它加到responseheader裏面,可能長這樣:

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

客戶端拿到後會將這個ETag和返回值一塊兒存下來,等下次請求時,使用配套的If-None-Match,將這個放到requestheader裏面,可能長這樣:

If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

而後服務端拿到請求裏面的If-None-Match跟當前版本的ETag比較下:

  1. 若是是同樣的話,直接返回304,語義爲Not Modified,不返回內容(body),只返回header,告訴瀏覽器直接用緩存。
  2. 若是不同的話,返回200和最新的內容

ETag配套的還有一個不太經常使用的request header ----If-Match,這個和前面If-None-Match的語義是相反的。前面If-None-Match的語義是若是不匹配就下載。而If-Match一般用於post或者put請求中,語義爲若是匹配才提交,好比你在編輯一個商品,其餘人也可能同時在編輯。當你提交編輯時,其餘人可能已經先於你提交了,這時候服務端的ETag就已經變了,If-Match就不成立了,這時候服務端會給你返回412錯誤,也就是Precondition Failed,前提條件失敗。若是If-Match成立,就正常返回200

Last-Modified & If-Modified-Since

Last-ModifiedIf-Modified-Since也是配套使用的,相似於ETagIf-None-Match的關係。只不過ETag放的是一個版本號或者hash值,Last-Modified放的是資源的最後修改時間。Last-Modified是放到responseheader裏面的,可能長這樣:

Last-Modified: Wed, 21 Oct 2000 07:28:00 GMT

而客戶端瀏覽器在使用時,應該將配套的If-Modified-Since放到requestheader裏面,長這樣:

If-Modified-Since: Wed, 21 Oct 2000 07:28:00 GMT

服務端拿到這個頭後,會跟當前版本的修改時間進行比較:

  1. 當前版本的修改時間比這個晚,也就是這個時間後又改過了,返回200和新的內容
  2. 當前版本的修改時間和這個同樣,也就是沒有更新,返回304,不返回內容,只返回頭,客戶端直接使用緩存

If-Modified-Since對應的還有If-Unmodified-SinceIf-Modified-Since能夠理解爲有更新才下載,那If-Unmodified-Since就是沒有更新才下載。若是客戶端傳了If-Unmodified-Since,像這樣:

If-Unmodified-Since: Wed, 21 Oct 2000 07:28:00 GMT

服務端拿到這個頭後,也會跟當前版本的修改時間進行比較:

  1. 若是這個時間後沒有更新,服務器返回200,並返回內容。
  2. 若是這個時間後有更新,其實就是這個if不成立,會返回錯誤代碼412,語義爲Precondition Failed

ETag和Last-Modified優先級

ETagLast-Modified都是協商緩存,都須要服務器進行計算和比較,那若是這兩個都存在,用哪一個呢?答案是ETagETag的優先級比Last-Modified。由於Last-Modified在設計上有個問題,那就是Last-Modified的精度只能到秒,若是一個資源頻繁修改,在同一秒進行屢次修改,你從Last-Modified上是看不出來區別的。可是ETag每次修改都會生成新的,因此他比Last-Modified精度高,更準確。可是ETag也不是徹底沒問題的,你的ETag若是設計爲一個hash值,每次請求都要計算這個值,須要額外耗費服務器資源。具體使用哪個,須要根據本身的項目狀況來進行取捨。

強制緩存

上面扯蛋那裏的第三個例子和第四個例子就是強制緩存,就是我知道在某個時間段徹底不用去問服務端,直接去用緩存就行。這兩個例子裏面提到的Expires是一個單獨的headermax-ageimmutable同屬於Cache-Control這個header

Expires

Expires比較簡單,就是服務器responseheader帶上這個字段:

Expires: Wed, 21 Oct 2000 07:28:00 GMT

而後在這個時間前,客戶端瀏覽器都不會再發起請求,而是直接用緩存資源。

Cache-Control

Cache-Control相對比較複雜,可設置屬性也比較多,max-age只是其中一個屬性,長這樣:

Cache-Control: max-age=20000

這表示當前資源在20000秒內都不用再請求了,直接使用緩存。

上面提到的immutable也是Cache-Control的一個屬性,可是是個實驗性質的,各個瀏覽器兼容並很差。設置了Cache-control: immutable表示這輩子都用緩存了,再請求是不可能的了。

其餘經常使用屬性還有:

no-cache:使用緩存前,強制要求把請求提交給服務器進行驗證(協商緩存驗證)。

no-store:不存儲有關客戶端請求或服務器響應的任何內容,即不使用任何緩存。

另外Cache-Control還有不少屬性,你們能夠參考MDN的文檔

Expires和Cache-Control的優先級

就一句話:若是在Cache-Control響應頭設置了 max-age 或者 s-maxage 指令,那麼 Expires 頭會被忽略。

協商緩存和強制緩存優先級

這個其實很好理解,協商緩存須要發請求跟服務器協商,強制緩存若是生效,根本就不會發請求。因此這個優先級就是:先判斷強制緩存,若是強制緩存生效,直接使用緩存;若是強制緩存失效,再發請求跟服務器協商,看要不要使用緩存

總結

本文從生活中常見的場景入手,闡述了HTTP緩存機制實際上是提升訪問速度和解決信息不一樣步的一種機制。這種信息不一樣步在生活中很常見,不少解決思路咱們已經司空見慣,帶着這種思惟,咱們能夠很好的理解HTTP緩存機制。HTTP緩存機制要點以下:

  1. HTTP緩存機制分爲強制緩存協商緩存兩類。
  2. 強制緩存的意思就是不要問了(不發起請求),直接用緩存吧。
  3. 強制緩存常見技術有ExpiresCache-Control
  4. Expires的值是一個時間,表示這個時間前緩存都有效,都不須要發起請求。
  5. Cache-Control有不少屬性值,經常使用屬性max-age設置了緩存有效的時間長度,單位爲,這個時間沒到,都不用發起請求。
  6. immutable也是Cache-Control的一個屬性,表示這個資源這輩子都不用再請求了,可是他兼容性很差,Cache-Control其餘屬性能夠參考MDN的文檔
  7. Cache-Controlmax-age優先級比Expires高。
  8. 協商緩存常見技術有ETagLast-Modified
  9. ETag其實就是給資源算一個hash值或者版本號,對應的經常使用request headerIf-None-Match
  10. Last-Modified其實就是加上資源修改的時間,對應的經常使用request headerIf-Modified-Since,精度爲
  11. ETag每次修改都會改變,而Last-Modified的精度只到,因此ETag更準確,優先級更高,可是須要計算,因此服務端開銷更大。
  12. 強制緩存協商緩存都存在的狀況下,先判斷強制緩存是否生效,若是生效,不用發起請求,直接用緩存。若是強制緩存不生效再發起請求判斷協商緩存

參考資料:

ETag MDN文檔:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/ETag

Last-Modified MDN文檔:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Last-Modified

Expires MDN文檔:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Expires

Cache-Control MDN文檔:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control

文章的最後,感謝你花費寶貴的時間閱讀本文,若是本文給了你一點點幫助或者啓發,請不要吝嗇你的贊和GitHub小星星,你的支持是做者持續創做的動力。

做者博文GitHub項目地址: https://github.com/dennis-jiang/Front-End-Knowledges

做者文章彙總:https://juejin.im/post/5e3ffc85518825494e2772fd

我也搞了個公衆號[進擊的大前端],能夠第一時間獲取高質量原創,歡迎關注~

相關文章
相關標籤/搜索