前言
作為前端開發,我們在web開發的過程中需要調用后端接口,用node搭建服務器或者寫一些中間件對數據進行封裝處理等等,必不可少地會和http打上交道,所以我們都要掌握http各方面的知識。
說到性能優化問題,一定會提到http緩存,如何利用好http緩存機制,理解http緩存的整個過程和http如何設置緩存,把這些都弄懂后會對我們以后在項目實踐中都會有很大幫助。本篇文章將會和大家分享一下筆者對http緩存機制的一些知識整理和總結。
與緩存相關的http頭部字段
我們先看看http頭部中與緩存相關的一些字段的基本概念和用法。
Pragma
該字段來自http1.0時代,當其設置為no-cache
的時候和Cache-Control: no-cache
效果一樣,意為無緩存,客戶端不會讀取緩存,直接向服務器發起請求。因為是http1.0留下的遺物,僅為了做向后的兼容。
Expires
該字段也是來自http1.0時代,用于控制緩存時間。例如設置了Thu, 18 Jul 2019 08:43:30 GMT
,即告訴瀏覽器如果沒有超過這個時間,將不會向服務器發起請求。
Expires存在兩個缺點:
- Expires的值是一個時間值。這就要求了客戶端和服務器端需要時間嚴格同步。否就會導致緩存失效。
- 當Expires定義的時間過期了,服務器需要重新配置一個新的時間。
cache-control
http1.1新增了 Cache-Control 字段,用來定義緩存過期時間,若報文中同時出現了 Pragma、Expires 和 Cache-Control,會以 Cache-Control 為準。(網上也有Pragma優先級高于Cache-Control的說法,但是一般設置了Pragma: no-cache
的話也會設置Cache-Control: no-cache
的吧-.-)
先看看cache-control的常用設置值
cache-directive | 描述 |
---|---|
no-cache | 不使用緩存,強制向服務器請求資源 |
no-store | 所有內容都不會被緩存 |
public | 資源將會被客戶端和代理服務器緩存 |
private | 資源將會被客戶端緩存但不會被代理服務器緩存 |
max-age | 緩存資源的最大時間段 |
s-maxage | 同上, 依賴public設置, 覆蓋max-age, 且只在代理服務器上有效. |
max-stale | 指定時間內, 即使緩存過時, 資源依然有效 |
min-fresh | 緩存的資源至少要保持指定時間的新鮮期 |
must-revalidation / proxy-revalidation | 作用與no-cache類似,強制重新向服務器(或代理)發起驗證 |
only-if-cached | 只從緩存中獲取資源,若沒有緩存則返回504 |
cache-control字段可以包含以上多個值,但是最終會選擇最為保守的方案。
Last-Modified & If-Modified-Since & If-Unmodified-Since
當客戶端第一次發起請求時,服務端會返回狀態碼200以及客戶端請求的資源,同時響應頭部會帶有Last-Modified字段,標記此文件在服務器最后修改的時間。
而下次客戶端再次發起請求的時候,請求頭部就會帶有If-Modified-Since或者If-Unmodified-Since的字段,他們的值是上一次響應頭部Last-Modified字段的值,用于校驗資源是否過期。
若使用If-Modified-Since則服務端會判斷此時間后此文件是否修改過,如果沒有修改過則使用緩存資源,返回304,否則將獲取資源返回200.
若使用If-Unmodified-Since,則是相反的情況,若資源未修改過則獲取資源,否則返回412(Precondition Failed)響應。它常用于兩個場景:
- 不安全的請求, 比如說使用post請求更新wiki文檔, 文檔未修改時才執行更新
- 與 If-Range 字段同時使用時, 可以用來保證新的片段請求來自一個未修改的文檔
當遇到下面情況時,If-Unmodified-Since 字段會被忽略:
- Last-Modified值對上了(資源在服務端沒有新的修改)
- 服務端需返回2XX和412之外的狀態碼
- 傳來的指定日期不合法
Last-Modified存在的問題
- 服務器端的靜態資源通常需要編譯打包, 可能出現資源內容沒有改變, 而Last-Modified卻改變的情況.
- 某些文件修改非常頻繁,比如在秒以下的時間內進行修改,If-Modified-Since無法檢查到。
- 某些服務器不能精確的得到文件的最后修改時間。
Etag & If-Match & If-None-Match
為了解決以上Last-Modified存在的問題,http1.1還提出另外一個字段——Etag。Etag,實體首部字段,服務器資源的唯一標識符, 客戶端可以根據ETag值緩存數據, 節省帶寬。因此Etag的優先級高于Last-Modified。
服務器會通過某種算法,給資源計算得出一個唯一標志符(比如md5標志),在把資源響應給客戶端的時候,會在實體首部加上“ETag: 唯一標識符”一起返回給客戶端。
當再次向服務器發起請求時,請求頭部會發送If-Match或者If-None-Match字段,服務器收到請求后則將其與被請求資源的唯一標識進行比對。
If-None-Match,若不同,說明資源又被改動過,則響應整片資源內容,返回狀態碼200;若相同,說明資源無新修改,則響應HTTP 304,告知瀏覽器繼續使用所保存的cache。
If-Match,若不同,則返回412(Precondition Failed) 狀態碼給客戶端。否則服務器直接忽略該字段。其應用場景是客戶端走PUT方法向服務端請求上傳/更替資源,這時候可以通過 If-Match 傳遞資源的ETag。
使用Etag注意兩點:
- 如果資源是走分布式服務器(比如CDN)存儲的情況,需要這些服務器上計算ETag唯一值的算法保持一致,才不會導致明明同一個文件,在服務器A和服務器B上生成的ETag卻不一樣。
- 如果 Last-Modified 和 ETag 同時被使用,則要求它們的驗證都必須通過才會返回304,若其中某個驗證沒通過,則服務器會按常規返回資源實體及200狀態碼。
http緩存策略
做好前期的知識鋪墊,馬上扔個流程圖上來
我們看到圖中不同階段有不同的緩存策略,我們逐一看看。
過期策略(命中強緩存)
過期策略主要是通過Expires和CacheControl來判斷緩存是否已經過期,若兩者都沒有設置的話,則使用瀏覽器默認的一套‘10%’算法來進行判斷,通常會取響應頭的Date_value - Last-Modified_value值的10%作為緩存時間。
假如緩存沒有過期則不向瀏覽器發出請求了,但是抓包的時候還是返回200 ok,但是留意了,會有Provisional headers are shown 的提示,說明就是瀏覽器并未發出請求, 緩存依然有效。
(這坑很大啊,明明沒有向瀏覽器發起請求,還顯示200來嚇唬人-.-)
這時在Chrome里面會出現200(from disk cache)和200(from memory cache)的情況。官方解釋如下:
Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.
意思大概就是Chrome會使用磁盤緩存和內存緩存,內存緩存是直接從內存中讀取的,所以速度更快。內部具體的規則還不大了解,估計是根據渲染刷新方式和文件大小決定的。
假如緩存過期了則走下一步協商策略。
協商策略
協商策略是通過Last-Modified和Etag字段向服務器發起驗證能否使用緩存資源,若驗證通過,則返回304狀態碼,根據響應頭更新緩存,使用緩存資源。
若沒有通過校驗,則向服務器獲取資源,返回200,并將響應內容存進緩存。
存儲策略
這沒啥好說的,就是將響應內容存入緩存或者根據響應內容更新緩存。
綜上,再看回這個流程圖,我們梳理一下整個過程。
- 客戶端首次發起請求,沒有緩存數據,向服務器請求數據,服務器返回數據和緩存規則,并存入緩存。
- 客戶端再次發起請求,檢測是否有緩存,若有緩存,則檢測緩存是否過期,若緩存沒有過期,則使用緩存數據。(過期策略)
- 若緩存數據已過期,則向服務器發起校驗,驗證緩存是否有效,若通過驗證,則使用緩存,返回304;反之向服務器發起請求,返回200。(協商策略)