HTTP 緩存機制一二三
Web 緩存大致可以分為:資料庫緩存、伺服器端緩存(代理伺服器緩存、CDN 緩存)、瀏覽器緩存。
瀏覽器緩存也包含很多內容: HTTP 緩存、indexDB、cookie、localstorage 等等。這裡我們只討論 HTTP 緩存相關內容。
在具體了解 HTTP 緩存之前先來明確幾個術語:
- 緩存命中率:從緩存中得到數據的請求數與所有請求數的比率。理想狀態是越高越好。
- 過期內容:超過設置的有效時間,被標記為「陳舊」的內容。通常過期內容不能用於回復客戶端的請求,必須重新向源伺服器請求新的內容或者驗證緩存的內容是否仍然準備。
- 驗證:驗證緩存中的過期內容是否仍然有效,驗證通過的話刷新過期時間。
- 失效:失效就是把內容從緩存中移除。當內容發生改變時就必須移除失效的內容。
瀏覽器緩存主要是 HTTP 協議定義的緩存機制。HTML meta 標籤,例如
<META HTTP-EQUIV="Pragma" CONTENT="no-store">n
含義是讓瀏覽器不緩存當前頁面。但是代理伺服器不解析 HTML 內容,一般應用廣泛的是用 HTTP 頭信息控制緩存。
HTTP 頭信息控制緩存
大致分為兩種:強緩存和協商緩存。強緩存如果命中緩存不需要和伺服器端發生交互,而協商緩存不管是否命中都要和伺服器端發生交互,強制緩存的優先順序高於協商緩存。具體內容下文介紹。
匹配流程(已有緩存的情況下):
強緩存
可以理解為無須驗證的緩存策略。對強緩存來說,響應頭中有兩個欄位 Expires/Cache-Control 來表明規則。
Expires
Expires 指緩存過期的時間,超過了這個時間點就代表資源過期。有一個問題是由於使用具體時間,如果時間表示出錯或者沒有轉換到正確的時區都可能造成緩存生命周期出錯。並且 Expires 是 HTTP/1.0 的標準,現在更傾向於用 HTTP/1.1 中定義的 Cache-Control。兩個同時存在時也是 Cache-Control 的優先順序更高。
Cache-Control
Cache-Control 可以由多個欄位組合而成,主要有以下幾個取值:
1. max-age 指定一個時間長度,在這個時間段內緩存是有效的,單位是s。例如設置 Cache-Control:max-age=31536000,也就是說緩存有效期為(31536000 / 24 / 60 * 60)天,第一次訪問這個資源的時候,伺服器端也返回了 Expires 欄位,並且過期時間是一年後。
在沒有禁用緩存並且沒有超過有效時間的情況下,再次訪問這個資源就命中了緩存,不會向伺服器請求資源而是直接從瀏覽器緩存中取。
2. s-maxage 同 max-age,覆蓋 max-age、Expires,但僅適用於共享緩存,在私有緩存中被忽略。
3. public 表明響應可以被任何對象(發送請求的客戶端、代理伺服器等等)緩存。
4. private 表明響應只能被單個用戶(可能是操作系統用戶、瀏覽器用戶)緩存,是非共享的,不能被代理伺服器緩存。
5. no-cache 強制所有緩存了該響應的用戶,在使用已緩存的數據前,發送帶驗證器的請求到伺服器。不是字面意思上的不緩存。
6. no-store 禁止緩存,每次請求都要向伺服器重新獲取數據。
協商緩存
緩存的資源到期了,並不意味著資源內容發生了改變,如果和伺服器上的資源沒有差異,實際上沒有必要再次請求。客戶端和伺服器端通過某種驗證機制驗證當前請求資源是否可以使用緩存。
瀏覽器第一次請求數據之後會將數據和響應頭部的緩存標識存儲起來。再次請求時會帶上存儲的頭部欄位,伺服器端驗證是否可用。如果返回 304 Not Modified,代表資源沒有發生改變可以使用緩存的數據,獲取新的過期時間。反之返回 200 就相當於重新請求了一遍資源並替換舊資源。
Last-modified/If-Modified-Since
Last-modified: 伺服器端資源的最後修改時間,響應頭部會帶上這個標識。第一次請求之後,瀏覽器記錄這個時間,再次請求時,請求頭部帶上 If-Modified-Since 即為之前記錄下的時間。伺服器端收到帶 If-Modified-Since 的請求後會去和資源的最後修改時間對比。若修改過就返回最新資源,狀態碼 200,若沒有修改過則返回 304。
注意:如果響應頭中有 Last-modified 而沒有 Expire 或 Cache-Control 時,瀏覽器會有自己的演算法來推算出一個時間緩存該文件多久,不同瀏覽器得出的時間不一樣,所以 Last-modified 要記得配合 Expires/Cache-Control 使用。
Etag/If-None-Match
由伺服器端上生成的一段 hash 字元串,第一次請求時響應頭帶上 ETag: abcd,之後的請求中帶上 If-None-Match: abcd,伺服器檢查 ETag,返回 304 或 200。
關於 last-modified 和 Etag 區別,已經有很多人總結過了:
- 某些伺服器不能精確得到資源的最後修改時間,這樣就無法通過最後修改時間判斷資源是否更新。
- Last-modified 只能精確到秒。
- 一些資源的最後修改時間改變了,但是內容沒改變,使用 Last-modified 看不出內容沒有改變。
- Etag 的精度比 Last-modified 高,屬於強驗證,要求資源位元組級別的一致,優先順序高。如果伺服器端有提供 ETag 的話,必須先對 ETag 進行 Conditional Request。
注意:實際使用 ETag/Last-modified 要注意保持一致性,做負載均衡和反向代理的話可能會出現不一致的情況。計算 ETag 也是需要佔用資源的,如果修改不是過於頻繁,看自己的需求用 Cache-Control 是否可以滿足。
選擇 Cache-Control 的策略(摘自 Google Developers)
實際應用
回到實際應用上來,首先要明確哪些內容適合被緩存哪些不適合。
考慮緩存的內容:
- css樣式文件
- js文件
- logo、圖標
- html文件
- 可以下載的內容
一些不應該被緩存的內容:
- 業務敏感的 GET 請求
可緩存的內容又分為幾種不同的情況:
不經常改變的文件:
給 max-age 設置一個較大的值,一般設置 max-age=31536000
比如引入的一些第三方文件、打包出來的帶有 hash 後綴 css、js 文件。一般來說文件內容改變了,會更新版本號、hash 值,相當於請求另一個文件。
標準中規定 max-age 的值最大不超過一年,所以設成 max-age=31536000。至於過期內容,緩存區會將一段時間沒有使用的文件刪除掉。
有看到用對話的形式來描述這個過程,便仿照著試圖更清晰地解釋:
可能經常需要變動的文件:
Cache-Control: no-cache / max-age=0
比如入口 index.html 文件、文件內容改變但名稱不變的資源。選擇 ETag 或 Last-Modified 來做驗證,在使用緩存資源之前一定會去伺服器端做驗證,命中緩存時會比第一種情況慢一點點,畢竟還要發請求進行通信。
注意: 這裡只描述了最基本的思路,實際使用 HTTP 緩存需要後端配合配置,具體情況具體對待,而且各方的實現並不一定完全按照標準來的,踩踩坑更健康??。
參考文章
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn#publicprivate
https://jakearchibald.com/2016/caching-best-practices
https://zhuanlan.zhihu.com/p/28113197
https://www.digitalocean.com/community/tutorials/web-caching-basics-terminology-http-headers-and-caching-strategies
https://stackoverflow.com/questions/12908766/what-is-cache-control-private
http://www.alloyteam.com/2016/03/discussion-on-web-caching/#prettyPhoto
推薦閱讀:
※緩存級數是否有上限?
※在校學生一枚,面對高性能伺服器開發、分散式系統、緩存系統等等。該如何最快最好的提升自己的技術水平呢?
※1G緩存的機械硬碟,拷貝文件是否只是前幾秒快一些?
※如何設計網站的緩存?
※求教大神瀏覽器是根據什麼決定from disk cache 與 from memory cache?