一些關於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請求的處理通常包含以下7個步驟
- 接受: 緩存從網路中讀取到達的請求報文
- 解析: 緩存解析報文,提取出Url和Http首部
- 查詢: 查詢是否存在本地副本,如果沒有副本,直接從原始伺服器獲取一份並存入緩存
- 通常來說主要根據請求的Url和Http Method來確定是否存在緩存副本
- 通常只有
GET
請求才會被緩存 - 新鮮度檢測:如果存在本地副本,對其進行新鮮度監測,如果副本不夠新鮮,則和伺服器進行再驗證。
- 創建響應報文: 緩存的創建的響應應該和來自伺服器的一樣,於此同時緩存也會對已經緩存的伺服器首部進行修飾或者擴充。 比如: 插入新鮮度相關的信息(cache-control, age, expires)
- 注意: 緩存不應該修改Date首部,所以可以根據Date和客戶端時間的差值來粗略判斷響應是否來自於緩存
- 發送
- 日誌: 這一步是可選的
下圖說明了緩存處理的
通過修改資源名來控制緩存
從上面的流程圖我們可以看到,是否確定請求的內容已經緩存通常由Url/Http method決定。
因此一種常見的做法就是給文件名中添加版本號/時間戳/hash等內容,並且給這些文件設置很長的過期時間。一旦文件內容發生改變,由自動構建工具修改文件名和引用文件的入口。在客戶端由於文件名(Url)地址發生了改變,老版本的文件緩存就會匹配不上,從而使得客戶端可以去更新文件。
需要注意一點,把版本號/Hash等內容添加到QueryString而不是文件名中可能不是一個好的實踐,原因是有的代理伺服器的緩存匹配策略可能會忽略query string。詳見下文:
Revving Filenames: don』t use querystring當然,這篇文章很老了。據我所知,目前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-storeCache-Control: no-transformCache-Control: only-if-cached
- 響應中可以使用的Cache-control值:
Cache-Control: must-revalidate
Cache-Control: no-cacheCache-Control: no-storeCache-Control: no-transform
Cache-Control: publicCache-Control: privateCache-Control: proxy-revalidateCache-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-revalidateimmutable: 表示在過期之前,資源不會隨著時間改變。因此客戶端不會發送條件請求來進行新鮮度驗證。對於穩定的資源和比較大的max-age配合使用,可以節省一次304的時間。
扼殺 304,Cache-Control: immutable過期時間相關的:
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個條件請求首部:
If-Match
If-Modified-Since
If-None-Match
If-Range
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/WebKitWebkit的MemoryCache和DiskCache - CSDN博客The cache mechanism of Chromium/WebKit
Webkit的MemoryCache和DiskCache - CSDN博客
參考
Cache-ControlHTTP緩存控制小結 - 騰訊Web前端 IMWeb 團隊社區
推薦閱讀:
※TCP/IP協議的淺析
※ApiTestEngine 演化之路(1)搭建基礎框架
※HTTP不權威簡介
※如何使用Nginx轉發非80埠的非HTTP請求?
※http是應用層,ip是網路層,那麼http請求頭部的client ip是怎麼獲取到的呢?