HTTP緩存機制[譯文]

本文翻譯自: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching ,主要用於我的記錄和共享,如有疏漏錯誤,請不吝指正,謝謝!html

 

經過重用已獲取的資源,可大幅提升web站點和應用的性能。因爲web緩存減小了延遲和網絡流量,所以縮短了展現一個資源所需的時間。經過使用HTTP緩存機制,web站點可實現更快更靈活的響應。webpack

不一樣類型的緩存

緩存是一種保存資源副本並在下次請求時直接使用該副本的技術。當發起一個請求時,web緩存會判斷是否已有此請求的一個副本(以前已經請求過一次而且被緩存了),如有,則緩存會攔截此請求,並直接返回緩存中的請求結果副本,從而防止從新到源服務器下載資源。緩存的目的:減輕服務器壓力(服務器不用每次爲全部客戶端提供服務了),提升訪問效率(由於緩存離客戶端最近,可直接提供資源副本,也可節省不少傳輸時間)。對於網站來說,緩存是建設高性能網站的最重要的組件,但在另外方面,緩存必須進行合理的配置才能達到最佳效果,由於並不是全部資源都是永久不變更的,因此咱們須要保證某個資源的緩存僅在它未變更時有效。web

全部不一樣類型的緩存,大體能夠歸爲兩類:私有緩存 和 共享緩存。共享緩存中存儲的資源副本是供全部用戶使用的(好比不一樣瀏覽器,不一樣機器),而私有緩存是僅提供給單個用戶的專有緩存(不一樣用戶保留不一樣私有緩存副本)。本文僅討論瀏覽器緩存和代理緩存,但就目前來說,還有不少其餘類型的緩存,好比:網關緩存、CDN、反向代理緩存、負載均衡(負載均衡是部署在服務器端的,爲多個web服務器提供更可靠、更高性能以及更易進行規模化擴展的方案)。算法

What a cache provide, advantages/disadvantages of shared/private caches.

瀏覽器(私有)緩存

私有緩存是單個用戶的專有緩存,通常來說,在你的瀏覽器設置中就能夠看到「緩存」的選項。瀏覽器緩存保留了用戶經過HTTP下載的全部文檔資源,前進/後退、保存、查看源代碼等操做均可以使用到此緩存,而不用再從新訪問服務器。一樣的,有了緩存,咱們還能夠實現脫機瀏覽文檔和資源。瀏覽器

代理(共享)緩存

共享緩存中存放的訪問結果是提供給多個用戶使用的。好比:ISP或你的公司可能會組建一個本地網絡的代理,該代理(服務器)會緩存不一樣用戶訪問外網時請求的公共資源,這些公共資源被緩存後,下次其餘用戶也訪問同一資源時,就會重用此已被緩存的資源(就不用再向源站獲取了),從而減小了網絡瀏覽和延遲。緩存

緩存操做的目標

HTTP緩存雖然是可選的,但通常是全部人都須要的。HTTP緩存一般只緩存GET請求(其餘請求通常不緩存),緩存的主鍵由請求方法和目標URI(一般只用到URI,由於通常僅緩存GET請求)組成。一般的緩存條目有:服務器

  • 成功的查詢請求的結果數據:狀態碼爲200的GET響應(結果中可能包含資源數據如:HTML文檔、圖片或文件等)
  • 永久性跳轉:狀態碼爲301(Moved Permanently)的響應
  • 返回出錯,文檔不存在:狀態碼爲404(Not Found)的響應
  • 不完整的結果數據:狀態碼爲206(Partial Content)的響應(經過Range頭髮起的請求所返回的結果,Range用於只獲取文檔某一部分)
  • 其餘非GET請求的結果(若是這些結果比較適合做爲緩存的話)

緩存的條目也可能緩存多個,其中根據內容協商方式,每個對應的二級鍵(header頭中的字段)不同。詳見 Vary 頭。
網絡

緩存控制

Cache-control頭部

HTTP/1.1中,Cache-Control頭用於指定緩存機制中的不一樣指令,它是可用在請求報文及響應報文中的通用頭部。經過該頭部提供的不一樣指令,你能夠定義一個本身的緩存策略。app

禁止緩存 方式

以下頭部定義,在該方式下,緩存不會保存任何的客戶端請求和服務器響應。每次客戶端的請求都會發送到源服務器,而且每次源服務器返回的數據都會所有下載到客戶端。負載均衡

Cache-Control: no-store Cache-Control: no-cache, no-store, must-revalidate

強制確認緩存 方式

以下頭部定義,此方式下,每次有請求發出時,緩存會將此請求發到服務器(譯者注:該請求應該會帶有與本地緩存相關的驗證字段),服務器端會驗證請求中所描述的緩存是否過時,若未過時(譯者注:實際就是返回304),則緩存才使用本地緩存副本。

Cache-Control: no-cache

私有和公共緩存

"public" 指令表示該響應能夠被任何中間人(譯者注:好比中間代理、CDN等)緩存。若指定了"public",則一些一般不被中間人緩存的頁面(譯者注:由於默認是private)(好比 帶有HTTP驗證信息(賬號密碼)的頁面 或 某些特定影響狀態碼的頁面),將會被其緩存。

而 "private" 則表示該響應是專用於某單個用戶的,中間人不能緩存此響應,該響應只能應用於瀏覽器私有緩存中。

Cache-Control: private Cache-Control: public

過時機制

過時機制中,最重要的指令是 "max-age=<seconds>",表示資源可以被緩存(保持新鮮)的最大時間。與 Expires指令不一樣,該指令的值是相對於請求的那個時間以後的秒數。對於那些不會變更的文檔資源,你能夠直接將其設置爲永久緩存,好比像圖片、CSS文件、JS文件這些靜態資源。

更多信息,參見下方的 Freshness 。

Cache-Control: max-age=31536000

驗證確認

當使用了 "must-revalidate" 指令,那就意味着緩存在考慮使用一個陳舊的資源時,必須先驗證它的狀態,而且,已過時的緩存將不被使用。更多信息,參見下方的 Validation

Cache-Control: must-revalidate

Pragma 頭部

Pragma 是HTTP/1.0規範中的頭部,它已經不是可靠的用於過時控制的頭部了,儘管它的行爲和 Cache-Control: no-cache 一致(未設置Cache-Control頭部的狀況下)。Pragma 現僅用於兼容 HTTP/1.0 客戶端。

新鮮度

理論上來說,當一個資源被緩存存儲後,該資源應該能夠被永久存儲在緩存中。因爲緩存只有有限的空間用於存儲資源副本,因此緩存會按期地將一些副本刪除,這個過程叫作 緩存驅逐。另外一方面,當服務器上面的資源進行了更新,那麼緩存中的對應資源也應該被更新,因爲HTTP是C/S模式的協議,服務器更新一個資源時,不可能直接通知客戶端及其緩存,因此雙方必須爲該資源約定一個過時時間,在該過時時間以前,該資源(緩存副本)就是 新鮮的,當過了過時時間後,該資源(緩存副本)則變爲 陳舊的。驅逐算法用於將陳舊的資源(緩存副本)替換爲新鮮的,注意,一個陳舊的資源(緩存副本)是不會直接被清除或忽略的,當客戶端發起一個請求時,緩存檢索到已有一個對應的陳舊資源(緩存副本),則緩存會先將此請求附加一個If-None-Match頭,而後發給目標服務器,以此來檢查該資源副本是不是依然仍是算新鮮的,若服務器返回了 304 (Not Modified)(該響應不會有帶有實體信息),則表示此資源副本是新鮮的,這樣一來,能夠節省一些帶寬。(譯者注:若服務器經過 If-None-Match 或 If-Modified-Since判斷後發現已過時,那麼會帶有該資源的實體內容返回)

下面是一個代理共享緩存的過程示例:

Show how a proxy cache acts when a doc is not cache, in the cache and fresh, in the cache and stale.

新鮮度的生命週期是經過若干頭部值來計算的,若是設置了 "Cache-control: max-age=N" 頭部,那麼新鮮度的生命期則等於 N。常常狀況下,可能未設置此頭部,則會檢查 Expires 頭部是否存在,若 Expires 頭部存在,則新鮮度生命期 等於 該頭部的值 減去 Date 頭部的值。若兩種頭部都未設置,則會查找 Last-Modified 頭部,若存在,則新鮮度生命期 等於 Date 頭部值 減去 Last-modified 頭部值 再除以 10。

expirationTime = responseTime + freshnessLifetime - currentAge

上式中,responseTime 表示瀏覽器接收到此響應的那個時間點。

資源版本化

緩存使用越頻繁(譯者注:跟命中率有關了),那麼網站的響應速度和效率就越高,爲此,在最佳實踐中,咱們推薦儘量地將過時時間設置得長一些,但這會致使咱們很難去更新那些不常變更的資源。好比咱們常常會遇到這樣的需求:不少頁面都引用了一些JS和CSS文件,當這些文件的內容變更時,咱們但願能儘快地讓其在緩存中更新。

web開發者們研究出一個方案,Steve Sounders稱它爲 revving[1]。其原理是,將那些常常更新的文件的文件名經過一種特別的方式來命名,即文件名中加入版本號,這樣一來,每一次文件內容改變,文件名也被一塊兒改變,就至關於新建了另外一個不一樣的資源,那咱們就能夠將該資源設置爲永久不過時了(一般設置爲1年以上)。但爲了引用這個改動後的資源,因此連接到此資源的連接地址都須要改變(譯者注:其文件名改變後,相對應的URI也改變,因此連接到此資源的地址也應該改變),這也是該方案的缺點:帶來了額外的複雜性,一般web開發者會使用一些工具來自動應對此缺點(譯者注:如webpack),當不常變更的資源改變時,這些資源的文件名URI也隨之改變,而引用這些資源的另外的常常變更的資源,也將隨之改變(引用地址改變),當客戶端請求常變更的資源時,它裏面引用的不常變更的資源,因爲加入了版本號,因此其新的版本也被下載。

這種方案有一個好處:能夠解決2個資源過時時間不一致致使不一同被更新的問題。這在當網站的CSS或JS資源擁有共同的依賴時顯得尤其重要,好比他們引用了同一個HTML元素,致使他們互相依賴。(譯者注:舉例,好比當前緩存了兩個JS文件A,B,A裏面是比較早期的功能,A先過時,B是晚於A開發的功能,且依賴於A,B後過時,假如這期間服務器端A和B都作了改變,加了新的功能,那麼當A過時時拿到了最新版的A,而B還未過時,則使用的是舊版的B,這樣頁面運行時,可能致使嚴重錯誤)。

添加在資源名稱上面的版本號並不是必須是像1.1.3這樣的經常使用版本定義方式,甚至能夠僅僅是一個連續遞增的數字,只要版本號不衝突,它能夠是任意的,好比hash值或日期時間。

緩存的驗證確認

當用戶點擊刷新(從新加載)按鈕時,就會觸發一個再次驗證確認,當用戶正常訪問某網站/資源時,瀏覽器會檢查該請求對應的緩存內容,若以前緩存的響應內容中,包含了 "Cache-control: must-revalidate" 頭部,那麼也會觸發一個再次驗證確認。另一個引起再次驗證確認的因素是:用戶能夠在瀏覽器的 Advanced->Cache 設置選項面板中設置 強制每次加載頁面時都進行驗證確認。

當一個緩存副本已經到了過時時間,那麼就會先到服務器驗證確認此緩存的新鮮度,或者直接從服務器獲取該資源最新的內容。驗證確認操做僅僅在服務器的響應信息中提供了 強驗證器弱驗證器 纔會發生。

ETag頭部

響應報文中的 ETag 頭是一個對 用戶代理透明 的值,被用來做爲強驗證器,意思就是說,像瀏覽器這樣的用戶代理程序並不知道其值表明的含義。若在服務器的響應報文中含有 ETag 頭部,則後續客戶端發起同一請求時,會附加一個 If-None-Match 頭部(其值爲以前Etag的值)用於讓服務器驗證該請求對應的緩存是否新鮮。

響應報文中的 Last-Modified 頭用來做爲弱驗證器,之因此做爲「弱」,是由於它最多隻能精確到秒,若服務器的響應報文中存在 Last-Modified 頭部,那麼客戶端發起一個 If-Modified-Since 請求來驗證緩存是否新鮮。

當發起了一個驗證請求,服務器能夠忽略此驗證請求並返回一個正常的 200 OK 響應報文,或者也能夠返回 304 Not Modified(帶有一個空的實體)來告知瀏覽器:你可使用當前緩存副本,後者(304)的響應報文中也能夠附加一些頭部,用來更新當前緩存副本中緩存的頭部信息。

可變響應 - Vary頭部

響應報文中的 Vary 頭部用於 決定如何匹配後續請求的頭部,從而決定是否採用某緩存副本,而不是從服務器得到一個新的副本。

當發起一個請求時,緩存命中了一個副本(以前緩存的響應報文信息),而這個副本中含有 Vary 頭部,那麼緩存須要檢查 Vary 頭部中所列出的頭部字段,若在當前請求中的這些頭部字段值 與 緩存副本中響應的頭部字段值匹配,那麼可使用此緩存副本,不然須要不能使用此副本(從新請求服務器)。

The Vary header leads cache to use more HTTP headers as key for the cache.

有了這個頭部,就能夠動態地提供內容了,舉個例子,當咱們使用 Vary: User-Agent 頭部時,緩存服務器就須要去查看新發起的請求的 User-Agent 頭部是否匹配,而後再決定是否採用緩存。若是你須要爲手機端用戶展現不一樣的內容,此舉能夠防止將電腦端的緩存錯誤地提供給了手機端的用戶,而且還能夠幫助Google這些搜索引擎發現並抓取手機端版本的頁面,以及告訴搜索引擎這不是 Cloaking 做弊。

Vary: User-Agent

因爲 User-Agent 頭部在移動端和電腦端中的值都是不一樣的,因此緩存就不會錯誤地將移動端內容提供給電腦端用戶了,反之亦然。

參考資料

相關文章
相關標籤/搜索