HTTP深入之快取

Bjkb發表於2018-09-29

HTTP快取在WEB系統效能優化的過程中,起到了不可忽視的作用。而我們又常常將它忽視?,這次給它一個面子,好好了解一下快取。

快取的目標以及順序

目標

常見的 HTTP 快取只能儲存 GET 響應,對於其他型別的響應則無能為力。快取的關鍵主要包括request method和目標URI。

順序

  1. 接收、解析—從網路中讀取抵達的請求報文,對報文進行解析,提取出 URL 和各種首部。
  2. 查詢—快取檢視是否有本地副本可用,如果沒有,就獲取一份副本(並將其儲存在本地)。
  3. 新鮮度檢測—快取檢視已快取副本是否足夠新鮮,如果不是,就詢問伺服器是否有任何更新。
  4. 建立響應—快取會用新的首部和已快取的主體來構建一條響應報文。
  5. 傳送—快取通過網路將響應發回給客戶端。
  6. 日誌—快取可選地建立一個日誌檔案條目來描述這個事務。

為什麼使用快取?

1. 冗餘的資料傳輸

 多個客戶端同時訪問一個WEB服務頁面,伺服器會多次傳輸同一份文件,每次傳送給一個客戶端。一些相同的位元組會在網路中一遍遍地傳輸。這些冗餘的資料傳輸會耗盡昂貴的網路頻寬,降低傳輸速度,加重 Web 伺服器的負載。

2. 頻寬瓶頸

 很多網路為本地網路客戶端提供的頻寬比為遠端伺服器提供的頻寬要寬,客戶端會以路徑上最慢的網速訪問伺服器。如果客戶端從一個快速區域網的快取中得到了一份副本,那麼快取就可以提高效能——尤其是要傳輸比較大的檔案時。

3. 瞬間擁塞

 快取在破壞瞬間擁塞(Flash Crowds)時顯得非常重要。'突發事件',使很多人幾乎同時去訪問一個 Web 文件時,就會出現瞬間擁塞。由此造成的過多流量峰值,可能會使網路和 Web 伺服器產生災難性的崩潰。

4. 距離時延

 即使頻寬不是問題,距離也可能成為問題。每臺網路路由器都會增加因特網流量的時延。即使客戶端和伺服器之間沒有太多的路由器,光速自身也會造成顯著的時延。


常見的快取頭

快取控制欄位 說明
Cache-Control 頭 HTTP/1.1定義的 Cache-Control 頭用來區分對快取機制的支援情況, 請求頭和響應頭都支援這個屬性。通過它提供的不同的值來定義快取策略。
Pragma 頭 Pragma 是HTTP/1.0標準中定義的一個header屬性,請求中包含Pragma的效果跟在頭資訊中定義Cache-Control: no-cache相同,但是HTTP的響應頭不支援這個屬性,所以它不能拿來完全替代HTTP/1.1中定義的Cache-Control頭。通常定義Pragma以向後相容基於HTTP/1.0的客戶端。
Etag http1.1時期新加屬性 ,使用inode+mtime(以下有解釋)來計算。根據實體內容生成的一段hash字串(類似於MD5或者SHA1之後的結果),可以標識資源的狀態。 當資源傳送改變時,ETag也隨之發生變化。
Last-Modified http1.1時期屬性,比較資源最後一次修改時間
快取對比欄位 說明
Expires http1.0時期屬性,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器快取取資料,而無需再次請求。一般用來做相容。
Vary HTTP 響應頭決定了對於後續的請求頭,如何判斷是請求一個新的資源還是使用快取的檔案。
If-Match 比較ETag是否一致, 在請求方法為 GET 和 HEAD 的情況下,伺服器僅在請求的資源滿足此首部列出的 ETag 之一時才會返回資源。而對於 PUT 或其他非安全方法來說,只有在滿足條件的情況下才可以將資源上傳。
If-None-Match 比較ETag是否不一致,對於 GETGET 和 HEAD 請求方法來說,當且僅當伺服器上沒有任何資源的 ETag 屬性值與這個首部中列出的相匹配的時候,伺服器端會才返回所請求的資源,響應碼為 200 。對於其他方法來說,當且僅當最終確認沒有已存在的資源的 ETag 屬性值與這個首部中所列出的相匹配的時候,才會對請求進行相應的處理。
If-Modified-Since 比較資源最後更新的時間是否一致,如果請求的資源從那時起未經修改,那麼返回一個不帶有訊息主體的 304 響應,而在 Last-Modified 首部中會帶有上次修改時間。 不同於 If-Unmodified-Since, If-Modified-Since 只可以用在 GET 或 HEAD 請求中。
If-Unmodified-Since 比較資源最後更新的時間是否不一致,只有當資源在指定的時間之後沒有進行過修改的情況下,伺服器才會返回請求的資源,或是接受 POST 或其他 non-safe 方法的請求。如果所請求的資源在指定的時間之後發生了修改,那麼會返回 412 (Precondition Failed) 錯誤。
If-Range 頭欄位通常用於斷點續傳的下載過程中,用來自從上次中斷後,確保下載的資源沒有發生改變。

快取的發展

HTTP1.0近時代

給客戶端設定快取可通過兩個欄位,PragmaExpires來實現。

  1. Pragma欄位值為no-cache的時候,會告訴客戶端不要對該資源讀快取,即每次都得向伺服器發一次請求才行。
  2. Expires的值對應一個GMT(格林尼治時間),比如Tue, 09 Oct 2018 10:22:09 GMT GMT來告訴瀏覽器資源快取過期時間,如果還沒過該時間點則不發請求。
    • 響應報文中Expires所定義的快取時間是相對伺服器上的時間而言的,其定義的是資源過期時刻。

    • 如果客戶端上的時間跟伺服器上的時間不一致(特別是使用者修改了自己電腦的系統時間),那快取時間可能就沒啥意義了。

如果Pragma頭部和Expires頭部同時存在,則起作用的會是Pragma

HTTP1.1後時代

因為1.0時代的快取問題,“Expires時間是相對伺服器而言的,無法保證和客戶端時間統一”,http1.1新增了 Cache-Control 來定義快取過期時間。

  1. 若報文中同時出現了 Expires 和 Cache-Control,則以 Cache-Control 為準。
  2. 優先順序從低到高 Expires <- Cache-Control <- Pragma

Cache-Control 欄位簡介

在RFC中規範了 Cache-Control 的格式為:"Cache-Control" ":" cache-directive

作用 欄位 說明
禁止進行快取 no-store 快取中不得儲存任何關於客戶端請求和服務端響應的內容。每次由客戶端發起的請求都會下載完整的響應內容。
強制確認快取 no-cache 每次有請求發出時,快取會將此請求發到伺服器(該請求應該會帶有與本地快取相關的驗證欄位),伺服器端會驗證請求中所描述的快取是否過期,若未過期(實際就是返回304),則快取才使用本地快取副本。
私有快取 private 則表示該響應是專用於某單個使用者的,中間人不能快取此響應,該響應只能應用於瀏覽器私有快取中。
公共快取 public 指令表示該響應可以被任何中間人(比如中間代理、CDN等)快取。若指定了"public",則一些通常不被中間人快取的頁面(因為預設是private)(比如 帶有HTTP驗證資訊(帳號密碼)的頁面 或 某些特定影響狀態碼的頁面),將會被其快取。
快取過期機制 max-age=<seconds> 表示資源能夠被快取(保持新鮮)的最大時間。相對Expires而言,max-age是距離請求發起的時間的秒數。針對應用中那些不會改變的檔案,通常可以手動設定一定的時長以保證快取有效,例如圖片、css、js等靜態資源。
快取驗證確認 must-revalidate 快取在考慮使用一個陳舊的資源時,必須先驗證它的狀態,已過期的快取將不被使用

客戶端決定是否向伺服器傳送請求,比如設定的快取時間未過期,那麼自然直接從本地快取取資料即可,若快取時間過期了或資源不該直接走快取,則會發請求到伺服器去。

但是隻有這樣還不夠,我們來假設一種情況。快取過期了,伺服器上的這個資源資料量夠多,但又沒更改過。那這個時候重新請求的話,相當於白白載入了一遍,浪費頻寬跟時間。

Last-Modified 響應頭

伺服器將資源傳遞給客戶端時,會將資源最後更改的時間以“Last-Modified: GMT”的形式加在實體首部上一起返回給客戶端。

這個響應頭也被叫做弱型別校驗器,說它弱是因為它只能精確到一秒。

Last-Modified: Thu, 30 Aug 2018 08:03:28 GMT
複製程式碼
  1. 當向服務端發起快取校驗的請求時,會比較兩個時間是否一致,服務端會返回 200 表示返回正常的結果或者 304 Not Modified(不返回body)表示瀏覽器可以使用本地快取檔案。
  2. 需要注意的是,304的響應頭也可以同時更新快取文件的過期時間。

跟其配合的常常有 If-Modified-Since、If-Unmodified-Since等(看上面快取頭解釋)

ETag 響應頭

  1. 作為快取的一種強校驗器,ETag 響應頭是一個對使用者代理(User Agent)不透明的值。
  2. 為了解決上述Last-Modified可能存在的不準確的問題,Http1.1還推出了 ETag 實體首部欄位。 伺服器會通過某種演算法,給資源計算得出一個唯一標誌符(比如md5標誌),在把資源響應給客戶端的時候,會在實體首部加上“ETag: 唯一識別符號”一起返回給客戶端。
Etag: "4280832337"
Etag: W/"57a1bb7b-10c8" // 表示使用弱驗證器
複製程式碼
  1. 客戶端會保留該 ETag 欄位,並在下一次請求時將其一併帶過去給伺服器。

  2. 伺服器只需要比較客戶端傳來的ETag跟自己伺服器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。

  3. 如果伺服器發現ETag匹配不上,那麼直接以常規GET 200回包形式將新的資源(當然也包括了新的ETag)發給客戶端。

  4. 如果ETag是一致的,則直接返回304知會客戶端直接使用本地快取即可。

跟其配合的常常有 If-Match、If-None-Match等(看上面快取頭解釋)


快取頭部效果對比

頭部 優勢和特點 劣勢和問題
Expires 1. HTTP 1.0 產物,可以在HTTP 1.0和1.1中使用,簡單易用。

2. 以時刻標識失效時間。
1. 時間是由伺服器傳送的(UTC),如果伺服器時間和客戶端時間存在不一致,可能會出現問題。

2. 存在版本問題,到期之前的修改客戶端是不可知的。
Cache-Control 1. HTTP 1.1 產物,以時間間隔標識失效時間,解決了Expires伺服器和客戶端相對時間的問題。

2. 比Expires多了很多選項設定。
1. HTTP 1.1 才有的內容,不適用於HTTP 1.0 。

2. 存在版本問題,到期之前的修改客戶端是不可知的。
Last-Modified 不存在版本問題,每次請求都會去伺服器進行校驗。伺服器對比最後修改時間如果相同則返回304,不同返回200以及資源內容。 1. 只要資源修改,無論內容是否發生實質性的變化,都會將該資源返回客戶端。例如週期性重寫,這種情況下該資源包含的資料實際上一樣的。

2. 以時刻作為標識,無法識別一秒內進行多次修改的情況。

3. 某些伺服器不能精確的得到檔案的最後修改時間。
ETag 1. 可以更加精確的判斷資源是否被修改,可以識別一秒內多次修改的情況。

2. 不存在版本問題,每次請求都回去伺服器進行校驗。
1. 計算ETag值需要效能損耗。

2. 分散式伺服器儲存的情況下,計算ETag的演算法如果不一樣,會導致瀏覽器從一臺伺服器上獲得頁面內容後到另外一臺伺服器上進行驗證時發現ETag不匹配的情況。

快取實踐

強快取(200 from cache)

  1. 為靜態資源配置一個超過當前時長的Expires或Cache-Control。
  2. 這樣使用者在訪問網頁時,只會在第一次載入時從伺服器請求靜態資源。
  3. 只要快取沒有失效並且使用者沒有強制重新整理的條件下都會從自己的快取中載入。

HTTP深入之快取

協商快取(304 Not Modified)

當瀏覽器對某個資源的請求沒有命中強快取

  1. 伺服器還可以配置 http response header 的 ETag (http1.1) 欄位,表示當前資源的唯一標識。
  2. 如果資源在客戶端過期,傳送資源請求到伺服器有 http request header 有 If-None-Match 欄位,則表示當前資源配置過 ETag 欄位。
  3. 此時伺服器需要判斷當前資源的唯一標識 ETag 值是否與請求的 If-None-Match 欄位值一致,如果一致則表示當前資源未修改,可以繼續使用快取資源,這就是 304 not-modified (協商快取)

HTTP深入之快取

快取命中速度

快取命中 > 快取再驗證成功 > 快取未命中 = 快取再驗證失敗;

200(from memory cache) 和 200(from disk cache)

在用chrome檢視快取的過程中,發現了一個問題,那就是有的快取資源是200(from memory cache),而有的資源是200(from disk cache),這一奇特的現象讓我百思不得解呀!

先看看他們的解釋。

  • MemoryCache顧名思義,就是將資源快取到記憶體中,等待下次訪問時不需要重新下載資源,而直接從記憶體中獲取。

  • diskCache顧名思義,就是將資源快取到磁碟中,等待下次訪問時不需要重新下載資源,而直接從磁碟中獲取,它的直接操作物件為CurlCacheManager。它與memoryCache最大的區別在於,當退出程式時,記憶體中的資料會被清空,而磁碟的資料不會,所以,當下次再進入該程式時,該程式仍可以從diskCache中獲得資料,而memoryCache則不行。

  • chrome的解釋 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會使用兩個快取,一個是磁碟快取一個是非常快的記憶體快取,記憶體快取是和渲染程式繫結的,大部分情況下於瀏覽器Tab對應。不要輕易強制重新整理快取,代價非常昂貴...

指令碼檔案的差異

HTTP深入之快取
隨便開啟兩個
HTTP深入之快取
HTTP深入之快取

  • 紅線圈出來的是不同的地方,難道是這樣的嗎?
  • 發現一個不一樣的,沒有content-length欄位
    HTTP深入之快取
  • 然後我又發現了一個不同的。而且這玩意在我強制重新整理瀏覽器的情況下,還是200 OK (from disk cache)
    HTTP深入之快取
    所以結論就是,留給大家思考吧。

參考文章:

相關文章