標籤:

一些關於Http Cache的東西

在Web項目中經常會遇到一些緩存相關的問題,所以總結了一些關於緩存的知識點。大部分內容來自於《Http權威指南》, MDN。

緩存通常是一種從原始數據緩存一組冗餘的數據集合的機制,通常用以提高效率和易訪問性

在計算機領域的Cache/Caching其實包含了很多種, 比如CPU Cache,Disk Cache,Web Cache等等。今天只說Web Cache.

Web Cache 的參與角色通常包含數據的發送方(Web Server)和接收方(Client, Browser etc), 當然代理伺服器可能同時會扮演著兩個角色。數據接收方和發送方會根據一些特定的Http Header來約定應該採取什麼樣的緩存策略。

關於緩存的一些概念

私有緩存/公有緩存(代理緩存)

單個用戶專用的緩存被稱為私有緩存,通常Web瀏覽器中有內建的私有緩存。

公有緩存/代理緩存是多個用戶共享的緩存,通常由公共的代理伺服器提供。從客戶端到實際的伺服器之間,通常會存在多級代理緩存。因為代理緩存包含了多個用戶緩存的內容,因此代理緩存會顯著的降低網路流量

緩存命中/未命中/再驗證

用已有的副本某些到達緩存的請求提供服務,這被稱為緩存命中(Cache hit)

如果到達緩存的請求沒有可用的副本,而被轉發給原始伺服器,這被稱為緩存未命中(Cache miss)

再驗證: 為了保證緩存中的副本和原始伺服器的內容保持一致, 需要對緩存進行檢測,這稱之為緩存的新鮮度檢測,也被稱為HTTP再驗證(revalidation)

緩存在對緩存中的副本進行再驗證的時候,會發給原始伺服器一個再驗證請求,如果內容沒有變化,伺服器返回304 Not Modified狀態的小的響應。緩存會將副本標記為暫時新鮮的,並且把副本提供給客戶端。 這被稱為緩慢命中(slow hit)或者 再驗證命中(revalidate hit) 。 如果內容發生了變化,伺服器會向代理伺服器/客戶端發送一條普通的,帶有完整內容的200 OK響應。 如果對象被刪除, 返回404 Not Found,並且緩存會將其副本也刪除

緩存命中率/位元組命中率

由緩存提供服務的請求比例稱為緩存命中率(cache hit rate),或者被稱為文檔命中率(Document hit rate)。

由於各個文檔大小可能差異很大,因此需要從位元組流量的角度來衡量緩存的作用: 位元組命中率(byte hit rate)表示的是緩存提供的位元組在傳輸的所以位元組中所佔據的比例

緩存的處理步驟

對Http請求的緩存處理大概包含以下7個步驟:

存在可用副本的情況 來源於《Http權威指南》

緩存對於一條Http請求的處理通常包含以下7個步驟

  1. 接受: 緩存從網路中讀取到達的請求報文
  2. 解析: 緩存解析報文,提取出Url和Http首部
  3. 查詢: 查詢是否存在本地副本,如果沒有副本,直接從原始伺服器獲取一份並存入緩存
    1. 通常來說主要根據請求的Url和Http Method來確定是否存在緩存副本
    2. 通常只有GET 請求才會被緩存
  4. 新鮮度檢測:如果存在本地副本,對其進行新鮮度監測,如果副本不夠新鮮,則和伺服器進行再驗證。
  5. 創建響應報文: 緩存的創建的響應應該和來自伺服器的一樣,於此同時緩存也會對已經緩存的伺服器首部進行修飾或者擴充。 比如: 插入新鮮度相關的信息(cache-control, age, expires)
    1. 注意緩存不應該修改Date首部,所以可以根據Date和客戶端時間的差值來粗略判斷響應是否來自於緩存
  6. 發送
  7. 日誌: 這一步是可選的

下圖說明了緩存處理的

緩存處理的流程圖 來源於《Http權威指南》

通過修改資源名來控制緩存

從上面的流程圖我們可以看到,是否確定請求的內容已經緩存通常由Url/Http method決定。

因此一種常見的做法就是給文件名中添加版本號/時間戳/hash等內容,並且給這些文件設置很長的過期時間。一旦文件內容發生改變,由自動構建工具修改文件名和引用文件的入口。在客戶端由於文件名(Url)地址發生了改變,老版本的文件緩存就會匹配不上,從而使得客戶端可以去更新文件。

需要注意一點,把版本號/Hash等內容添加到QueryString而不是文件名中可能不是一個好的實踐,原因是有的代理伺服器的緩存匹配策略可能會忽略query string。詳見下文:

Revving Filenames: don』t use querystring?

www.stevesouders.com

當然,這篇文章很老了。據我所知,目前Azure CDN的緩存策略是可以配置是否忽略query string的。

緩存相關的Http Headers

除了通過文件名來控制緩存的失效時間,其實還有一種做法是設置響應的Http Header.

相應的Http Header主要有以下幾種:

Cache-Control

Cache-control是一個通用的首部欄位。在Request和Response中都可以使用。並且其方向是單向的,根據MDN:

Caching directives are unidirectional, meaning that a given directive in a request is not implying that the same directive is to be given in the response.

  • 請求中可以使用的Cache-Control值:

Cache-Control: max-age=<seconds>

Cache-Control: max-stale[=<seconds>]

Cache-Control: min-fresh=<seconds>

Cache-Control: no-cache

Cache-Control: no-store

Cache-Control: no-transform

Cache-Control: only-if-cached

  • 響應中可以使用的Cache-control值:

Cache-Control: must-revalidate

Cache-Control: no-cache

Cache-Control: no-store

Cache-Control: no-transform

Cache-Control: public

Cache-Control: private

Cache-Control: proxy-revalidate

Cache-Control: max-age=<seconds>

Cache-Control: s-maxage=<seconds>

下面說明一下常用的cache-control的值的含義,詳細內容可以參考MDN

緩存行為和新鮮度驗證相關的:

public: 響應可以被私有緩存或者代理緩存/公有緩存所緩存

private: 響應只可以被私有緩存所緩存

only-if-cached: 表示不檢索新的數據。 客戶端只希望獲得緩存的響應,而不應該聯繫原始伺服器來查看是否存在新的副本。

no-cache: 通常很容易被誤解為是不緩存(no-store), 實際上的含義是強制緩存在給客戶端提供副本內容之前,必須先去源伺服器進行一次新鮮度驗證。

no-store: 請求不應該被緩存。

must-revalidate: 在緩存內容過期之後,緩存必須去源伺服器進行新鮮度驗證

Difference between no-cache and must-revalidate?

stackoverflow.com圖標

immutable: 表示在過期之前,資源不會隨著時間改變。因此客戶端不會發送條件請求來進行新鮮度驗證。對於穩定的資源和比較大的max-age配合使用,可以節省一次304的時間。

扼殺 304,Cache-Control: immutable?

www.cnblogs.com圖標

過期時間相關的:

max-age: 值為秒數, 表示從Date到多少秒之前緩存的副本都應該被認為是新鮮的。

s-max-age用法類似,適用於公共緩存。

Pragma HTTP1.0

Pragma: no-cache

Pragma實際是HTTP/1.0的遺留物,其可選的值實際上只有no-cache一種。其行為和給cache-control設置了no-cache一致。 現在使用pragma實際上只是為了保證向下兼容。

Expires HTTP1.0

其語法如下:

Expires: <http-date> Expires: Wed, 21 Oct 2015 07:28:00 GMT

Expires的值是一個是一個日期的絕對值,再此日期之前,資源都被認為是新鮮的, 因為很多伺服器的時間都不準確,所以在Http1.1中推出了Cache-Control: max-age來代替Expires的作用。在cache-control指定了max-age 或者 s-maxage的情況下,Expires會被忽略。

如果Expires的值為0.那麼認為資源已經過期了。

用條件請求做再驗證

使用expires和max-age指定的時間過期了,也不意味著緩存的內容和伺服器上的文檔一定不一致。而只是說明超出這個時間,需要緩存去伺服器驗證副本新鮮度了。

通過在正常的請求中添加一些特殊的條件首部,可以將新鮮度監測和正常的數據獲取結合在一次GET請求中。只有在條件為真的時候,認為緩存副本已經不新鮮,源伺服器才會返回數據對象。

HTTP定義了5個條件請求首部:

  1. If-Match
  2. If-Modified-Since
  3. If-None-Match
  4. If-Range
  5. If-Unmodified-Since

下面簡單說下最常用的兩個:

If-Modified-Since: <date>

如果自指定日期之後,文檔被修改,條件為真,請求會從源伺服器獲取新的文檔和新的首部。

如果自指定日期之後,文檔沒有被修改過,條件為假,會向客戶端返回一個小的304響應報文。

If-Modified-Since通常和Last-Modified配合使用。

If-None-Match: <tags>

有的情況下僅僅用最後修改日期進行新鮮度驗證是不夠的。

  • 文檔的修改日期發生變化,不代表內容一定發生了變化
  • 根據日期判斷的最小粒度是1s. 如果文檔可能會在1s內發生多次變化。用日期判斷是不夠用的。
  • 有的文檔可能被修改了,但是所做的修改並不重要。無需讓緩存失效。(比如修改注釋等等)

為了解決上述問題。HTTP允許通過ETag(實體標籤)進行資源的比較。 ETag是附加到文檔上的字元串形式的標籤。

HTTP Spec文檔中並沒有指定Etag的生成方式, Etag通常會包含文檔內容的校驗和或者其他指紋信息, 但是也可以直接指定版本號等信息。

和基於日期的校驗類似, If-None-Match會和Etag header配合使用。

有的時候,伺服器希望資源發生了一些不重要的修改的時候,不要讓已緩存的副本失效。這個時候可以使用Etag的弱驗證方式。通常用 W/ 前綴標識弱驗證。強驗證方式表示副本和資源的內容是逐位元組相同的。而弱驗證方式通常表示副本和資源的內容從語義上相等。

當伺服器的響應同時提供了Etag和Last-Modified的時候,客戶端需要同時使用If-None-Match和If-Modified-Since首部進行條件驗證。僅當這兩個條件都滿足的時候,客戶端才能接受到304 Not Modified響應。

瀏覽器行為對緩存的影響

用戶在瀏覽器中進行操作的時候,對資源的緩存情況有不同的影響的,常見的行為如下(Chrome下):

  • 從地址欄或者鏈接進入網頁:

對於Expires或者max-age沒有過期的內容,瀏覽器直接從本地私有緩存獲取。

  • 用戶點擊F5或者按鈕刷新:

對於Expires或者max-age沒有過期的內容,瀏覽器會發起一次新鮮度驗證。

  • 用戶Ctrl+F5強制刷新:

這種情況下條件請求頭部會被去掉,所有資源全部從源伺服器獲取最新內容。返回200狀態碼。

使用Meta標籤設置HTML的緩存策略

<meta http-equiv="cache-control" content="no-cache" />

類似上面的這些Meta標籤可以指定Html文檔緩存策略。但是還是推薦修改伺服器配置的方式來控制緩存策略。使用meta來控制的方式,伺服器的兼容性一般,並且

Webkit的Memory Cache和Disk Cache

在Chrome的Dev Tools中,我們會經常看到一些請求的來源標記著 From Memory Cache或者From Disk Cache.

如果想讓這兩者失效,可以改變文件名或者使用no-cache等緩存策略。

關於這兩者可以參考

The cache mechanism of Chromium/WebKit?

www.programering.com

Webkit的MemoryCache和DiskCache - CSDN博客?

blog.csdn.net

The cache mechanism of Chromium/WebKit

Webkit的MemoryCache和DiskCache - CSDN博客?

blog.csdn.net

參考

Cache-Control?

developer.mozilla.org圖標HTTP緩存控制小結 - 騰訊Web前端 IMWeb 團隊社區?

imweb.io圖標
推薦閱讀:

TCP/IP協議的淺析
ApiTestEngine 演化之路(1)搭建基礎框架
HTTP不權威簡介
如何使用Nginx轉發非80埠的非HTTP請求?
http是應用層,ip是網路層,那麼http請求頭部的client ip是怎麼獲取到的呢?

TAG:HTTP | 緩存 |