rn丶 2021-08-15 17:02:26 阅读数:152
在任何一個前端項目中,訪問服務器獲取數據都是很常見的事情,但是如果相同的數據被重複請求,那麼多餘的請求次數必然會浪費網絡帶寬,以及延遲瀏覽器渲染所要處理的內容,從而影響用戶的使用體驗。如果用戶使用的是按量計費的方式訪問網絡,那麼多餘的請求還會隱性地增加用戶的網絡流量資費。因此考慮使用緩存技術對已獲取的資源進行重用,是一種提昇網站性能與用戶體驗的有效策略。
在首次請求後保存一份請求資源的響應副本,當用戶再次發起相同請求後,如果判斷緩存命中則攔截請求,將之前存儲的響應副本返回給用戶,從而避免重新向服務器發起資源請求。
緩存的技術種類有很多,比如代理緩存
、瀏覽器緩存
、網關緩存
、負載均衡器
及內容分發網絡
等,它們大致可以分為兩類:
共享緩存
- 緩存內容可被多個用戶使用,如公司內部的Web代理。私有緩存
- 只能單獨被用戶使用的緩存,如瀏覽器緩存。HTTP 緩存 算是前端開發中最常接觸的緩存機制之一,它又可細分為 强制緩存 與 協商緩存,二者最大的區別在於判斷緩存命中時,瀏覽器是否需要向服務器端進行詢問以協商緩存的相關信息,進而判斷是否需要就響應內容進行重新請求。 下面就來具體看HTTP緩存的具體機制及緩存的决策策略。
對於强制緩存而言,如果瀏覽器判斷所請求的目標資源有效命中(還沒有過期),則可直接從强制緩存中返回請求響應,無須與服務器進行任何通信。
簡單地說就是,不會向服務器發送請求,直接從緩存中讀取資源。
强制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來决定是否使用該緩存結果的過程,强制緩存的情况主要有三種,如下:
第一種情况: 第一次請求,不存在緩存結果和緩存標識,直接向服務器發送請求。
第二種情况: 存在緩存標識和緩存結果,但是已經失效過期,强制緩存失效,則使用協商緩存(後面會介紹)
第三種情况: 存在該緩存結果和緩存標識,且該結果尚未失效,强制緩存生效,直接返回該結果。
當瀏覽器向服務器發起請求時,服務器會將緩存規則放入HTTP響應報文的HTTP頭中和請求結果一起返回給瀏覽器,控制强制緩存的字段分別是Expires
和Cache-Control
,其中Cache-Control
優先級比Expires
高。
在介紹 强制緩存 判斷是否命中之前,我們首先來看一段響應頭的部分信息:
access-control-allow-origin: *
age: 734978
content-length: 40830
content-type: image/jpeg
cache-control: max-age=31536000
expires: Web, 14 Fed 2021 12:23:42 GMT
複制代碼
其中和强制緩存相關的兩個字段是 expires
和 cache-control
。
expires
是在 HTTP1.0協議
中聲明的用來控制緩存失效日期時間戳的字段。它在服務器端設置後通過響應頭告知瀏覽器,瀏覽器在接收到帶有該字段的響應體後進行緩存。Expires = max-age + 請求時間
若之後瀏覽器再次發起相同的資源請求,便會對比 Expires
與本地當前的時間戳,如果當前請求的本地時間戳小於 Expires
的值,則說明瀏覽器緩存的響應還未過期,可以直接使用而無須向服務器端再次發起請求。只有當本地時間戳大於 Expires
值時,才允許重新向服務器發起請求。
resp.setHeader("Expires", new Date("2021-08-13 22:50:24").toUTCString());
複制代碼
Expires 的缺陷:
上述的 强制緩存是否過期的判斷機制 存在一個很大的漏洞,即對本地時間戳過分依賴,如果客戶端本地的時間與服務器端的時間不同步,或者對客戶端時間進行主動修改,那麼對於緩存過期的判斷可能就無法和預期相符。
為了解决 Expires
判斷的局限性,從 HTTP1.1協議
開始新增了 Cache-control
字段來對 Expires
的功能進行擴展和完善。
從上述代碼中可見 Cache-control
設置了 max-age=31536000
的屬性值來控制響應資源的有效期,它是一個以 秒
為單比特的時間長度,錶示該資源在被請求後的 31536000秒
內有效。如此便可避免服務器端和客戶端時間戳不同步而造成的問題。除此之外,Cache-control
還可配置一些其他屬性值來更准確地控制緩存,下面來具體介紹:
設置 no-cache
並非像字面上的意思不使用緩存,其錶示為强制進行 協商緩存(後面會說),即對於發起的請求不再去判斷强制緩存是否過期,而是直接與服務器協商來驗證緩存的有效性,若緩存未過期,則會使用本地緩存。
設置 no-store
則錶示禁止使用任何緩存策略,客戶端的每次請求都需要服務端給予全新的響應結果。
no-cache
和 no-store
是兩個互斥的屬性值,不能同時設置。
private
和 public
也是 Cache-control
的一組互斥屬性值,它們用以明確響應資源是否可被代理服務器進行緩存。
private
- 錶示響應資源既可以被瀏覽器緩存,又可以被代理服務器緩存。public
- Cache-control的默認取值。錶示響應資源只能被瀏覽器緩存。對於應用程序中不會改變的文件,通常可以在發送響應頭前添加積極緩存(public
)。例如應用程序中的靜態文件,例如圖像、CSS文件和JS文件。
Cache-control: public, max-age=600
複制代碼
max-age
屬性值比 s-maxage
更常用,它(max-age)錶示服務器端告知客戶端瀏覽器響應資源的過期時長,在一般項目的使用場景中基本够用。 但對於大型架構的項目通常會涉及使用各種代理服務器的情况,這就需要考慮緩存在代理服務器上的有效性問題。這便是 s-maxage
存在的意義,它錶示緩存在代理服務器中的過期時長,且僅當設置了 public
屬性值才有效。
由此可見 Cache-control
能作為 expires
的完全替代方案,並且擁有其所不具備的一些緩存控制特性,在項目實踐中使用它就足够了。而目前 expires
還存在的唯一理由是考慮到可用性方面的向下兼容。
協商緩存就是在使用本地緩存之前,需要向服務器端發起一次 GET 請求,與之協商當前瀏覽器保存的本地緩存是否已經過期。
通常是采用所請求資源最近一次的修改時間戳(獲取文件的 mtime
時間)來判斷的,為了便於理解,下面來看一個例子:假設客戶端瀏覽器需要向服務器請求一個 main.js
的 JS 文件資源,為了讓該資源被再次請求時能通過協商緩存的機制使用本地緩存,那麼首次返回該圖片資源的響應頭中應包含一個 Last-Modified
字段,該字段屬性值為該 JS 文件資源最近一次修改的時間戳。簡略截取 請求頭 與 響應頭 的關鍵信息:
// 第一次請求的請求頭
Request URL: http://localhost:3000/image.jpg
Request Method: GET
// 第一次請求的響應頭
last-modified: Thu, 29 Apr 2021 03:09:28 GMT
cache-control: no-cache
複制代碼
當我們刷新網頁時,由於該 JS 文件使用的是協商緩存(no-cache),客戶端瀏覽器無法確定本地緩存是否過期,所以需要向服務器發送一次 GET
請求,進行緩存有效性的協商,此次 GET
請求的請求頭中需要包含一個 If-Modified-Since
字段,其值就是上次響應頭中 Last-Modified
的字段值。
當服務器收到該請求後便會對比請求資源當前的修改時間戳與 If-Modified-Since
字段的值,如果二者相同則說明緩存未過期,告知瀏覽器可繼續使用本地緩存,否則服務器重新返回全新的文件資源,簡略截取 請求頭 與 響應頭 的關鍵信息:
// 再次請求的請求頭
Request URL: http://localhost:3000/image.jpg
Request Method: GET
If-Modified-Since: Thu, 29 Apr 2021 03:09:28 GMT
// 協商緩存有效的響應頭
Status Code: 304 Not Modified
複制代碼
*!注意:*協商緩存 判斷緩存是否有效的響應狀態碼是 304
,即緩存還有效就重定向到本地緩存上。這和强制緩存有所不同,强制緩存是若有效,則再次請求的響應狀態碼是 200
。
Last-Modified 的缺陷
通過 Last-Modified
所實現的協商緩存能够滿足大部分的使用場景,但也存在兩個比較明顯的缺陷:
其實造成上述兩種缺陷的原因相同,就是服務器無法僅依據資源修改的時間戳來識別出真正的更新,進而導致重新發起了請求,該重新請求卻使用了緩存的 Bug 場景。
為了彌補通過時間戳判斷的不足,從 HTTP 1.1協議
開始新增了一個 ETag
的頭信息,即實體標簽(Entity Tag)。
其內容主要是服務器根據對不同資源進行哈希運算所生成的字符串,該字符串類似於文件指紋,只要文件內容編碼存在差异,對應的 ETag
值就會不同。因此可以使用 ETag
對文件資源進行更精確的變化感知。
下面使用 ETag
進行協商緩存圖片資源為例,首次請求後的部分響應頭關鍵信息:
Content-Type: image/jpeg
ETag: "xxx"
Last-Modified: Fri, 12 Jul 2021 18:30:00 GMT
Content-Length: 9887
複制代碼
上面響應頭中同時包含了 last-modified
文件修改時間戳和 ETag
實體標簽兩種協商緩存的有效性校驗字段,因為 ETag
比 Last-Modified
具有更准確的文件資源變化感知,所以它的優先級也更高,二者同時存在時會以 ETag
為准。
再次對該圖片資源發起請求時,就會將之前首次請求的響應頭的 ETag 的字段值作為此次請求頭中 If-None-Match 字段,提供給服務器進行緩存有效性驗證。
請求頭的關鍵字段信息:
// 再次請求的請求頭:
If-Modified-Since: Fri, 12 Jul 2021 18:30:00 GMT
If-None-Match: "xxx"
複制代碼
若驗證緩存有效,則返回 304
狀態碼響應重定向到本地緩存。
ETag 的缺陷:
不像强制緩存中的 cache-control
可以完全替代 expires
的功能,在協商緩存中,ETag
並非 Last-Modified
的替代方案而是一種補充方案,因為它依舊存在一些弊端。
一方面服務器對於生成文件資源的 ETag
需要付出額外的計算開銷,如果資源的尺寸較大,數量較多且修改比較頻繁,那麼生成 ETag
的過程就會很影響服務器的性能。
另一方面 ETag
字段值的生成還分為 强驗證 和 弱驗證:
版权声明:本文为[rn丶]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/08/20210815170211220K.html