前端學HTTP之緩存


前面的話

  Web緩存是可以自動保存常見文檔副本的HTTP設備。當Web請求抵達緩存時,如果本地有“已緩存的”副本,就可以從本地存儲設備而不是原始服務器中提取這個文檔。本文將詳細介紹緩存的相關內容

 

功能

  總的來說,緩存有以下四個功能:緩存減少了冗余的數據傳輸,節省了網絡費用;緩解了網絡瓶頸的問題,不需要更多的帶寬就能夠更快地加載頁面;降低了對原始服務器的要求,服務器可以更快地響應,避免過載的出現;降低了距離時延,因為從較遠的地方加載頁面會更慢一些

【冗余的數據傳輸】

  有很多客戶端訪問一個流行的原始服務器頁面時,服務器會多次傳輸同一份文檔,每次傳送給一個客戶端。一些相同的字節會在網絡中一遍遍地傳輸。這些冗余的數據傳輸會耗盡昂貴的網絡帶寬,降低傳輸速度,加重Web服務器的負載。有了緩存,就可以保留第一條服務器響應的副本,后繼請求就可以由緩存的副本來應對了,這樣可以減少那些流入/流出原始服務器的,被浪費掉了的重復流量

【帶寬瓶頸】

  緩存還可以緩解網絡的瓶頸問題。很多網絡為本地網絡客戶端提供的帶寬比為遠程服務器提供的帶寬要寬。客戶端會以路徑上最慢的網速訪問服務器。如果客戶端從一個快速局域網的緩存中得到了一份副本,那么緩存就可以提高性能——尤其是要傳輸比較大的文件時

  下表中說明了在幾種不同的網速下,傳輸幾種不同大小的文檔時,帶寬會對傳輸速度產生什么樣的影響。帶寬會給較大的文檔帶來顯而易見的時延,不同類型網絡的速度差異會非常明顯。一個54kbit/s的Modem傳輸一個5MB的文件需要749秒(超過12分鍾),而在快速以太網LAN中,只要不到一秒的時間

【瞬間擁塞】

  緩存在破壞瞬間擁塞(Flash Crowds)時顯得非常重要。突發事件(比如爆炸性新聞、批量E-mail公告,或者某個名人事件)使很多人幾乎同時去訪問一個Web文檔時,就會出現瞬間擁塞。由此造成的過多流量峰值可能會使網絡和Web服務器產生災難性的崩潰

【距離時延】

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

  假設某個Web頁面中包含了20個小圖片,都在舊金山的一台服務器上。如果波士頓的一個客戶端打開了4條到服務器的並行連接,而且保持着連接的活躍狀態,光速自身就要耗費大約1/4秒(240毫秒)的下載時間。如果服務器位於(距離舊金山6700英里的)東京,時延就會變成600毫秒。中等復雜的Web頁面會帶來幾秒鍾的光速時延

  將緩存放在附近的機房里可以將文件傳輸距離從數千英里縮短為數十米。在實際應用中,信號的傳輸速度會比光速低一些,因此,距離時延會更加嚴重

 

緩存命中

  緩存無法保存世界上每份文檔的副本。可以用已有的副本為某些到達緩存的請求提供服務,這被稱為緩存命中(cache hit)。其他一些到達緩存的請求可能會由於沒有副本可用,而被轉發給原始服務器。這被稱為緩存未命中(cache miss)

  原始服務器的內容可能會發生變化,緩存要不時對其進行檢測,看看它們保存的副本是否仍是服務器上最新的副本。這些“新鮮度檢測”被稱為HTTP再驗證(revalidation)。為了有效地進行再驗證,HTTP定義了一些特殊的請求,不用從服務器上獲取整個對象,就可以快速檢測出內容是否是最新的

  緩存可以在任意時刻,以任意頻率對副本進行再驗證。但由於緩存通常會包含數百萬的文檔,而且網絡帶寬是很珍貴的,所以大部分緩存只有在客戶端發起請求,並且副本舊得足以需要檢測的時候,才會對副本進行再驗證

  緩存對緩存的副本進行再驗證時,會向原始服務器發送一個小的再驗證請求。如果內容沒有變化,服務器會以一個小的304 Not Modified進行響應。只要緩存知道副本仍然有效,就會再次將副本標識為暫時新鮮的,並將副本提供給客戶端,這被稱作再驗證命中(revalidate hit)或緩慢命中(slow hit)。這種方式確實要與原始服務器進行核對,所以會比單純的緩存命中要慢,但它沒有從服務器中獲取對象數據,所以要比緩存未命中快一些

  HTTP提供了幾個用來對已緩存對象進行再驗證的工具,但最常用的是If-Modified-Since首部。將這個首部添加到GET請求中去,就可以告訴服務器,只有在緩存了對象的副本之后,又對其進行了修改的情況下,才發送此對象

  這里列出了在3種情況下(服務器內容未被修改,服務器內容已被修改,或者服務器上的對象被刪除了),服務器收到GET If-Modified-Since請求時會發生的情況:再驗證命中——如果服務器對象未被修改,服務器會向客戶端發送一個小的HTTP 304 Not Modified響應;再驗證未命中——如果服務器對象與已緩存副本不同,服務器向客戶端發送一條普通的、帶有完整內容的HTTP 200 0K響應;對象被刪除——如果服務器對象已經被刪除了,服務器就回送一個404 Not Found響應,緩存也會將其副本刪除

 

【命中率】

  由緩存提供服務的請求所占的比例被稱為緩存命中率(cache hit rate,或稱為緩存命中比例),有時也被稱為文檔命中率(document hit rate)。命中率在0到1之間,但通常是用百分數來描述的,0%表示每次請求都未命中(要通過網絡來獲取文檔),100%表示每次請求都命中了(在緩存中有一份副本)

  緩存的管理者希望緩存命中率接近100%。而實際得到的命中率則與緩存的大小、緩存用戶興趣點的相似性、緩存數據的變化或個性化頻率,以及如何配置緩存有關。命中率很難預測,但對現在中等規模的Web緩存來說,40%的命中率是很合理的。緩存的好處是,即使是中等規模的緩存,其所包含的常見文檔也足以顯著地提高性能、減少流量了。緩存會努力確保將有用的內容保存在緩存中

【字節命中率】

  由於文檔並不全是同一尺寸的,所以文檔命中率並不能說明一切。有些大型對象被訪問的次數可能較少,但由於尺寸的原因,對整個數據流量的貢獻卻更大。因此,有些人更願意使用字節命中率(byte hit rate)作為度量值(尤其那些按流量字節付費的人)

  字節命中率表示的是緩存提供的字節在傳輸的所有字節中所占的比例。通過這種度量方式,可以得知節省流量的程度。100%的字節命中率說明每個字節都來自緩存,沒有流量流到因特網上去

  文檔命中率和字節命中率對緩存性能的評估都是很有用的。文檔命中率說明阻止了多少通往外部網絡的Web事務。事務有一個通常都很大的固定時間成分(比如,建立一條到服務器的TCP連接),提高文檔命中率對降低整體延遲(時延)很有好處。字節命中率說明阻止了多少字節傳向因特網。提高字節命中率對節省帶寬很有利

【區分命中】

  不幸的是,HTTP沒有為用戶提供一種手段來區分響應是緩存命中的,還是訪問原始服務器得到的。在這兩種情況下,響應碼都是200 OK,說明響應有主體部分。有些商業代理緩存會在Via首部附加一些額外信息,以描述緩存中發生的情況

  客戶端有一種方法可以判斷響應是否來自緩存,就是使用Date首部。將響應中Date首部的值與當前時間進行比較,如果響應中的日期值比較早,客戶端通常就可以認為這是一條緩存的響應。客戶端也可以通過Age首部來檢測緩存的響應,通過這個首部可以分辨出這條響應的使用期

 

拓撲結構

  緩存可以是單個用戶專用的,也可以是數千名用戶共享的。專用緩存被稱為私有緩存(private cache)。私有緩存是個人的緩存,包含了單個用戶最常用的頁面。共享的緩存被稱為公有緩存(public cache)。公有緩存中包含了某個用戶團體的常用頁面

【私有緩存】

  私有緩存不需要很大的動力或存儲空間,這樣就可以將其做的很小,很便宜。Web瀏覽器中有內建的私有緩存——大多數瀏覽器都會將常用文檔緩存在個人電腦的磁盤和內存中,並且允許用戶去配置緩存的大小和各種設置。還可以去看看瀏覽器的緩存中有些什么內容。比如,對IE來說,可以從Tools(工具)-> Internet Options(因特網選項)對話框中獲取緩存內容。MSIE將緩存的文檔稱為“臨時文件”,並將其與相關的URL和文檔過期時間一起在文件列表中列出

【公有緩存】

  公有緩存是特殊的共享代理服務器,被稱為緩存代理服務器(caching proxy server),或者更常見地被稱為代理緩存(proxy cache)。代理緩存會從本地緩存中提供文檔,或者代表用戶與服務器進行聯系。公有緩存會接受來自多個用戶的訪問,所以通過它可以更好地減少冗余流量

  在下圖中,每個客戶端都會重復地訪問一個(還不在私有緩存中的)新的“熱門”文檔。每個私有緩存都要獲取同一份文檔,這樣它就會多次穿過網絡。而使用共享的公有緩存時,對於這個流行的對象,緩存只要取一次就行了,它會用共享的副本為所有的請求服務,以降低網絡流量

 

【層次結構】

  在實際中,實現層次化(hierarchy)的緩存是很有意義的,在這種結構中,在較小緩存中未命中的請求會被導向較大的父緩存(parent cache),由它來為剩下的那些“提煉過的”流量提供服務。下圖顯示了一個兩級的緩存層次結構。其基本思想是在靠近客戶端的地方使用小型廉價緩存,而更高層次中,則逐步采用更大、功能更強的緩存來裝載多用戶共享的文檔

  我們希望大部分用戶都能在附近的第一級緩存中命中。如果沒有命中,較大的父緩存可能能夠處理它們的請求。在緩存層次結構很深的情況下,請求可能要穿過很長一溜緩存,但每個攔截代理都會添加一些性能損耗,當代理鏈路變得很長的時候,這種性能損耗會變得非常明顯

【緩存類型】

  有些網絡結構會構建復雜的網狀緩存(cache mesh),而不是簡單的緩存層次結構。網狀緩存中的代理緩存之間會以更加復雜的方式進行對話,做出動態的緩存通信決策,決定與哪個父緩存進行對話,或者決定徹底繞開緩存,直接連接原始服務器。這種代理緩存會決定選擇何種路由對內容進行訪問、管理和傳送,因此可將其稱為內容路由器(content router)

  網狀緩存中為內容路由設計的緩存(除了其他任務之外)要完成下列所有功能:根據URL在父緩存或原始服務器之間進行動態選擇;根據URL動態地選擇一個特定的父緩存;前往父緩存之前,在本地緩存中搜索已緩存的副本;允許其他緩存對其緩存的部分內容進行訪問,但不允許因特網流量通過它們的緩存

  緩存之間這些更為復雜的關系允許不同的組織互為對等(peer)實體,將它們的緩存連接起來以實現共贏。提供可選的對等支持的緩存被稱為兄弟緩存(sibling cache)。HTTP並不支持兄弟緩存,所以人們通過一些協議對HTTP進行了擴展,比如因特網緩存協議(Internet Cache Protocol, ICP)和超文本緩存協議(HyperText Caching Protocol, HTCP)

 

處理步驟

  現代的商業化代理緩存相當地復雜。這些緩存構建得非常高效,可以支持HTTP和其他一些技術的各種高級特性。但除了一些微妙的細節之外,Web緩存的基本工作原理大多很簡單。對一條HTTP GET報文的基本緩存處理過程包括以下7個步驟

  1、接收——緩存從網絡中讀取抵達的請求報文

  2、解析——緩存對報文進行解析,提取出URL和各種首部

  3、査詢——緩存査看是否有本地副本可用,如果沒有,就獲取一份副本,並將其保存在本地

  4、新鮮度檢測——緩存査看已緩存副本是否足夠新鮮,如果不是,就詢問服務器是否有任何更新

  5、創建響應——緩存會用新的首部和已緩存的主體來構建一條響應報文

  6、發送——緩存通過網絡將響應發回給客戶端

  7、日志——緩存可選地創建一個日志文件條目來描述這個事務

  在第一步中,緩存檢測到一條網絡連接上的活動,讀取輸入數據。高性能的緩存會同時從多條輸入連接上讀取數據,在整條報文抵達之前開始對事務進行處理

  接下來,緩存將請求報文解析為片斷,將首部的各個部分放入易於操作的數據結構中。這樣,緩存軟件就更容易處理首部字段並修改它們

  在第三步中,緩存獲取了URL,査找本地副本。本地副本可能存儲在內存、本地磁盤,甚至附近的另一台計算機中。專業級的緩存會使用快速算法來確定本地緩存中是否有某個對象。如果本地沒有這個文檔,它可以根據情形和配置,到原始服務器或父代理中去取,或者返回一條錯誤信息

  已緩存對象中包含了服務器響應主體和原始服務器響應首部,這樣就會在緩存命中時返回正確的服務器首部。已緩存對象中還包含了一些元數據(metadata),用來記錄對象在緩存中停留了多長時間,以及它被用過多少次等

  HTTP通過緩存將服務器文檔的副本保留一段時間。在這段時間里,都認為文檔是“新鮮的”,緩存可以在不聯系服務器的情況下,直接提供該文檔。但一旦已緩存副本停留的時間太長,超過了文檔的新鮮度限值(freshness limit),就認為對象“過時”了,在提供該文檔之前,緩存要再次與服務器進行確認,以査看文檔是否發生了變化。客戶端發送給緩存的所有請求首部自身都可以強制緩存進行再驗證,或者完全避免驗證,這使得事情變得更加復雜了

  HTTP有一組非常復雜的新鮮度檢測規則,緩存產品支持的大量配置選項,以及與非HTTP新鮮度標准進行互通的需要則使問題變得更加嚴重了

  我們希望緩存的響應看起來就像來自原始服務器的一樣,緩存將已緩存的服務器響應首部作為響應首部的起點。然后緩存對這些基礎首部進行了修改和擴充

  緩存負責對這些首部進行改造,以便與客戶端的要求相匹配。比如,服務器返回的可能是一條HTTP/1.0響應(甚至是HTTP/0.9響應)而客戶端期待的是一條HTTP1.1響應,在這種情況下,緩存必須對首部進行相應的轉換。緩存還會向其中插入新鮮度信息(Cache-Control、Age以及Expires首部),而且通常會包含一個Via首部來說明請求是由一個代理緩存提供的

  [注意]緩存不應該調整Date首部。Date首部表示的是原始服務器最初產生這個對象的日期

  一旦響應首部准備好了,緩存就將響應回送給客戶端。和所有代理服務器一樣,代理緩存要管理與客戶端之間的連接。高性能的緩存會盡力高效地發送數據,通常可以避免在本地緩存和網絡I/O緩沖區之間進行文檔內容的復制

  大多數緩存都會保存日志文件以及與緩存的使用有關的一些統計數據。每個緩存事務結束之后,緩存都會更新緩存命中和未命中數目的統計數據(以及其他相關的度量值),並將條目插入一個用來顯示請求類型、URL和所發生事件的日志文件

  最常見的緩存日志格式為Squid日志格式和網景的可擴展通用日志格式,但很多緩存產品都允許用戶創建自定義的日志文件

保持新鮮

  可能不是所有的已緩存副本都與服務器上的文檔一致。畢竟,這些文檔會隨着時間發生變化。如果緩存提供的總是老的數據,就會變得毫無用處。已 緩存數據要與服務器數據保持一致

  HTTP有一些簡單的機制可以在不要求服務器記住有哪些緩存擁有其文檔副本的情況下,保持已緩存數據與服務器數據之間充分一致。HTTP將這些簡單的機制稱為文檔過期(document expiration)和服務器再驗證(server revalidation)

【文檔過期】

  通過特殊的HTTP Cache-Control首部和Expires首部,HTTP讓原始服務器向每個文檔附加了一個“過期日期”,這些首部說明了在多長時間內可以將這些內容視為新鮮的

  在緩存文檔過期之前,緩存可以以任意頻率使用這些副本,而無需與服務器聯系——當然,除非客戶端請求中包含有阻止提供已緩存或未驗證資源的首部。但一旦已緩存文檔過期,緩存就必須與服務器進行核對,詢問文檔是否被修改過,如果被修改過,就要獲取一份新鮮(帶有新的過期日期)的副本

【過期日期和使用期】

  服務器用HTTP/1.0+的Expires首部或HTTP/1.1的Cache-Control: max-age響應首部來指定過期日期,同時還會帶有響應主體。Expires首部和Cache-Control:max-age首部所做的事情本質上是一樣的,但由於Cache-Control首部使用的是相對時間而不是絕對日期,所以我們更傾向於使用比較新的Cache-Control首部

Expires: Fri, 05 Jul 2016, 05:00:00 GMT
Cache-Control: max-age=484200

【服務器再驗證】

  僅僅是已緩存文檔過期了並不意味着它和原始服務器上目前處於活躍狀態的文檔有實際的區別,這只是意味着到了要進行核對的時間了。這種情況被稱為“服務器再驗證”,說明緩存需要詢問原始服務器文檔是否發生了變化

  如果再驗證顯示內容發生了變化,緩存會獲取一份新的文檔副本,並將其存儲在舊文檔的位置上,然后將文檔發送給客戶端。如果再驗證顯示內容沒有發生變化,緩存只需要獲取新的首部,包括一個新的過期日期,並對緩存中的首部進行更新就行了

  這是個很棒的系統。緩存並不一定要為每條請求驗證文檔的有效性——只有在文檔過期時它才需要與服務器進行再驗證。這樣不會提供陳舊的內容,還可以節省服務器的流量,並擁有更好的用戶響應時間

  HTTP協議要求行為正確的緩存返回下列內容之一:“足夠新鮮”的已緩存副本;與服務器進行過再驗證,確認其仍然新鮮的已緩存副本;如果需要與之進行再驗證的原始服務器出故障了,就返回一條錯誤報文;附有警告信息說明內容可能不正確的已緩存副本

【條件方法】

  HTTP的條件方法可以高效地實現再驗證。HTTP允許緩存向原始服務器發送一個“條件GET”,請求服務器只有在文檔與緩存中現有的副本不同時,才回送對象主體。通過這種方式,將新鮮度檢測和對象獲取結合成了單個條件GET。向GET請求報文中添加一些特殊的條件首部,就可以發起條件GET。只有條件為真時,Web服務器才會返回對象

  HTTP定義了5個條件請求首部。對緩存再驗證來說最有用的2個首部是If-Modified-Since和If-None-Match。所有的條件首部都以前綴“If-”開頭。下表中列出了在緩存再驗證中使用的條件請求首部

  [注意]其他條件首部包括If-Unmodified-Since(在進行部分文件的傳輸時,獲取文件的其余部分之前,要確保文件未發生變化,此時這個首部是非常有用的)、If-Range(支持對不完整文檔的緩存)和If-Match(用於與Web服務器打交道時的並發控制)

  最常見的緩存再驗證首部是If-Modified-Since。If-Modified-Since再驗證請求通常被稱為IMS請求。只有自某個日期之后資源發生了變化的時候,IMS請求才會指示服務器執行請求

  如果自指定日期后,文檔被修改了,If-Modified-Since條件就為真,通常GET就會成功執行。攜帶新首部的新文檔會被返回給緩存,新首部除了其他信息之外,還包含了一個新的過期日期

  如果自指定日期后,文檔沒被修改過,條件就為假,會向客戶端返回一個小的304 Not Modified響應報文,為了提高有效性,不會返回文檔的主體。這些首部是放在響應中返回的,但只會返回那些需要在源端更新的首部。比如,Content-Type首部通常不會被修改,所以通常不需要發送。一般會發送一個新的過期日期

  If-Modified-Since首部可以與Last-Modified服務器響應首部配合工作。原始服務器會將最后的修改日期附加到所提供的文檔上去。當緩存要對已緩存文檔進行再驗證時,就會包含一個If-Modified-Since首部,其中攜帶有最后修改已緩存副本的日期

If-Modified-Since: <cached last-modified date>

  如果在此期間內容被修改了,最后的修改日期就會有所不同,原始服務器就會回送新的文檔。否則,服務器會注意到緩存的最后修改日期與服務器文檔當前的最后修改日期相符,會返回一個304 Not Modified響應

  [注意]如果有一個不認識If-Modified-Since首部的老服務器收到了條件請求,它會將其作為一個普通的GET解釋。在這種情況下,系統仍然能夠工作,但由於要對未修改的文檔數據進行不必要的傳輸,所以效率會比較低

  有些Web服務器並沒有將lf-Modified-Since作為真正的日期來進行比對。相反,它們在IMS日期和最后修改日期之間進行了字符串匹配。這樣得到的語 義就是“如果最后的修改不是在這個確定的日期進行的”,而不是“如果在這個日期之后沒有被修改過”。將最后修改日期作為某種序列號使用時,這種替代語義能夠很好地識別出緩存是否過期,但這會妨礙客戶端將If-Modified-Since首部用於真正基於時間的一些目的

  有些情況下僅使用最后修改日期進行再驗證是不夠的,包括以下情況:有些文檔可能會被周期性地重寫(比如,從一個后台進程中寫入),但實際包含的數據常常是一樣的。盡管內容沒有變化,但修改日期會發生變化;有些文檔可能被修改了,但所做修改並不重要,不需要讓世界范圍內的緩存都重裝數據(比如對拼寫或注釋的修改);有些服務器無法准確地判定其頁面的最后修改日期;有些服務器提供的文檔會在亞稱間隙發生變化(比如,實時監視器),對這些服務器來說,以一秒為粒度的修改日期可能就不夠用了

  為了解決這些問題,HTTP允許用戶對被稱為實體標簽(ETag)的“版本標識符”進行比較。實體標簽是附加到文檔上的任意標簽(引用字符串)。它們可能包含了文檔的序列號或版本名,或者是文檔內容的校驗和及其他指紋信息

  當發布者對文檔進行修改時,可以修改文檔的實體標簽來說明這個新的版本。這樣,如果實體標簽被修改了,緩存就可以用If-None-Match條件首部來GET文檔的新副本了

  下圖中,緩存中有一個實體標簽為V2.6的文檔。它會與原始服務器進行再驗證,如果標簽v2.6不再匹配,就會請求一個新對象。因為,標簽仍然與之匹配,因此會返回一條304 Not Modified響應

  如果服務器上的實體標簽已經發生了變化(可能變成了v3.0),服務器會在一個200 OK響應中返回新的內容以及相應的新Etag

  可以在If-None-Match首部包含幾個實體標簽,告訴服務器緩存中已經存在帶有這些實體標簽的對象副本

If-None-Match: "v2.6"
If-None-Match: "v2.4","v2.5","v2.6"
If-None-Match: "football","a123244","Profiles in Courage"

【強弱驗證器】

  緩存可以用實體標簽來判斷,與服務器相比,已緩存版本是不是最新的(與使用最近修改日期的方式很像)。從這個角度來看,實體標簽和最近修改日期都是緩存驗證器(cachevalidator)

  有時,服務器希望在對文檔進行一些非實質性或不重要的修改時,不要使所有的已緩存副本都失效。HTTP/1.1支持“弱驗證器”,如果只對內容進行了少量修改,就允許服務器聲明那是“足夠好”的等價體

  只要內容發生了變化,強驗證器就會變化。弱驗證器允許對一些內容進行修改,但內容的主要含義發生變化時,通常它還是會變化的。有些操作不能用弱驗證器來現(比如有條件地獲取部分內容),所以,服務器會用前綴“W/”來標識弱驗證器

ETag: W/"v2.6"
If-None-Match: W/"v2.6"

  不管相關的實體值以何種方式發生了變化,強實體標簽都要發生變化。而相關實體 在語義上發生了比較重要的變化時,弱實體標簽也應該發生變化

  原始服務器一定不能為兩個不同的實體重用一個特定的強實體標簽值,或者為兩個語義不同的實體重用一個特定的弱實體標簽值。不管過期時間是多少,緩存條目都可能會留存任意長的時間。因此,假設緩存不會再次通過它在過去某個時刻獲得的驗證器,對一個條目進行驗證是不合適的

  如果服務器回送了一個實體標簽,HTTP/1.1客戶端就必須使用實體標簽驗證器。如果服務器只回送了一個Last-Modified值,客戶端就可以使用If-Modified-Since驗證。如果實體標簽和最后修改日期都提供了,客戶端就應該使用這兩種再驗證方案,這樣HTTP/1.0和HTTP/1.1緩存就都可以正確響應了

  除非HTTP/1.1原始服務器無法生成實體標簽驗證器,否則就應該發送一個出去,如果使用弱實體標簽有優勢的話,發送的可能就是個弱實體標簽,而不是強實體標簽。而且,最好同時發送一個最近修改值

  如果HTTP/1.1緩存或服務器收到的請求既帶有If-Modified-Since,又帶有實體標簽條件首部,那么只有這兩個條件都滿足時,才能返回304 Not Modified 響應

 

控制緩存

  服務器可以通過HTTP定義的幾種方式來指定在文檔過期之前可以將其緩存多長時間。按照優先級遞減的順序,服務器可以:附加一個Cache-Control: no-store首部到響應中去;附加一個Cache-Control: no-cache首部到響應中去;附加一個Cache-Control: must-revalidate 首部到響應中去;附加一個Cache-Control: max-age首部到響應中去;附加一個Expires日期首部到響應中去;不附加過期信息,讓緩存確定自己的過期日期

【no-store和no-cache】

  HTTP/1.1提供了幾種限制對象緩存,或限制提供已緩存對象的方式,以維持對象的新鮮度。no-store首部和no-cache首部可以防止緩存提供未經證實的已緩存對象

  標識為no-store的響應會禁止緩存對響應進行復制。緩存通常會像非緩存代理服務器一樣,向客戶端轉發一條no-store響應,然后刪除對象

  標識為no-cache的響應實際上是可以存儲在本地緩存區中的。只是在與原始服務器進行新鮮度再驗證之前,緩存不能將其提供給客戶端使用。這個首部使用do-not-serve-from-cache-without-revalidation這個名字會更恰當一些

  HTTP/1.1中提供Pragma: no-cache首部是為了兼容HTTP/1.0+。除了與只理解Pragma: no-cache的HTTP/1.0應用程序進行交互時,HTTP1.1應用程序都應該使用Cache-Control: no-cache

  [注意]從技術上來講,pragma:no-Cache首部只能用於HTTP請求,但在實際中它作為擴展首部已被廣泛地用於HTTP請求和響應之中

【max-age】

  Cache-Control: max-age首部表示的是從服務器將文檔傳來之時起,可以認為此文檔處於新鮮狀態的秒數。還有一個s-maxage首部(注意maxage的中間沒有連字符),其行為與tnax-age類似,但僅適用於共享(公有)緩存:

Cache-Control: max-age=3600
Cache-Control: s-maxage = 3600

  服務器可以請求緩存不要緩存文檔,或者將最大使用期設置為零,從而在每次訪問的時候都進行刷新

Cache-Control: max-age=0
Cache-Control: s-maxage = 0

【Expires】

  不推薦使用Expires首部,它指定的是實際的過期日期而不是秒數。HTTP設計者后來認為,由於很多服務器的時鍾都不同步,或者不正確,所以最好還是用剩余秒數,而不是絕對時間來表示過期時間。可以通過計算過期值和日期值之間的秒數差來計算類似的新鮮生存期:

Expires: Fri, 05 Jul 2016, 05:00:00 GMT

  有些服務器還會回送一個ExPires:0響應首部,試圖將文檔置於永遠過期的狀態,但這種語法是非法的,可能給某些軟件帶來問題。應該試着支持這種結構的輸入,但不應該產生這種結構的輸出

【must-revalidate】

  可以配置緩存,使其提供一些陳舊(過期)的對象,以提高性能。如果原始服務器希望緩存嚴格遵守過期信息,可以在原始響應中附加一個Cache-Control: must-revalidate首部

Cache-Control: must-revalidate

  Cache-Control: must-revalidate響應首部告訴緩存,在事先沒有跟原始服務器進行再驗證的情況下,不能提供這個對象的陳舊副本。緩存仍然可以隨意提供新鮮的副本。如果在緩存進行must-revalidate新鮮度檢査時,原始服務器不可用,緩存就必須返回一條504 Gateway Timeout錯誤

【試探性過期】

  如果響應中沒有Cache-Control: max-age首部,也沒有Expires首部,緩存可以計算出一個試探性最大使用期。可以使用任意算法,但如果得到的最大使用期大於24小時,就應該向響應首部添加一個Heuristic Expiration Warning(試探性過期警告,警告13)首部。但很少有瀏覽器會為用戶提供這種警告信息

  LM-Factor算法是一種很常用的試探性過期算法,如果文檔中包含了最后修改日期,就可以使用這種算法。LM-Factor算法將最后修改日期作為依據,來估計文檔有多么易變。算法的邏輯如下所示:如果已緩存文檔最后一次修改發生在很久以前,它可能會是一份穩定的文檔,不太會突然發生變化,因此將其繼續保存在緩存中會比較安全。如果已緩存文檔最近被修改過,就說明它很可能會頻繁地發生變化,因此在與服務器進行再驗證之前,只應該將其緩存很短一段時間。實際的LM-Factor算法會計算緩存與服務器對話的時間跟服務器聲明文檔最后被修改的時間之間的差值

  通常人們會為試探性新鮮周期設置上限,這樣它們就不會變得太大了。盡管比較保守的站點會將這個值設置為一天,但通常站點會將其設置為一周。如果最后修改日期也沒有的話,緩存就沒什么信息可利用了。緩存通常會為沒有任何新鮮周期線索的文檔分配一個默認的新鮮周期(通常是一個小時或一天)。有時,比較保守的緩存會將這種試探性新鮮生存期設置為0,強制緩存在每次將其提供給客戶端之前,都去驗證一下這些數據仍然是新鮮的

  與試探性新鮮計算有關的最后一點是——它們可能比你想象的要常見得多。很多原始服務器仍然不會產生Expires和max-age首部。選擇緩存過期的默認時間時要特別小心

【客戶端的新鮮度限制】

  Web瀏覽器都有Refresh(刷新)或Reload(重載)按鈕,可以強制對瀏覽器或代理緩存中可能過期的內容進行刷新。Refresh按鈕會發布一個附加了cache- control請求首部的GET請求,這個請求會強制進行再驗證,或者無條件地從服務器獲取文檔。Refresh的確切行為取決於特定的瀏覽器、文檔以及攔截緩存的配置

  客戶端可以用Cache-Control請求首部來強化或放松對過期時間的限制。有些應用程序對文檔的新鮮度要求很高(比如人工刷新按鈕),對這些應用程序來說,客戶端可以用Cache-Control首部使過期時間更嚴格。另一方面,作為提高性能、可靠性或開支的一種折衷方式,客戶端可能會放松新鮮度要求。下表對Cache-control請求指令進行了總結

  文檔過期系統並不是一個完美的系統。如果發布者不小心分配了一個很久之后的過期日期,在文檔過期之前,對文檔做的所有修改都不會出現在任何緩存中

  因此,很多發布者都不會使用很長的過期日期。而且,很多發布者甚至都不使用過期日期,這樣緩存就很難確定文檔會在多長時間內保持新鮮了

 

設置緩存

  不同的Web服務器為HTTP Cache-Control和Expiration首部的設置提供了一 些不同的機制。Apache Web服務器提供了幾種設置HTTP緩存控制首部的機制。其中很多機制在默認情況下都沒有啟動

  通過mod_headers模塊可以對單獨的首部進行設置。裝載了這個模塊,就可以用設置單個HTTP首部的指令來擴充Apache的配置文件了。還可以將這些設置與Apache的常用表達式以及過濾器結合在一起使用,將這些首部與個別內容關聯起來。這里有一個配置實例,這個例子將某目錄下所有的HTML文件都標識為非緩存的

<Files * .html>
    Header set Cache-control no-cache
</Files>

  mod_expires模塊提供的程序邏輯可以自動生成帶有正確過期日期的Expires首部。通過這個模塊,就可以在文檔最后一次被訪問之后,或者在它的最近修改日期之后將過期日期設置為某個時間區間。通過這個模塊可以為不同的文件類型設置不同的過期日期,還可以使用便捷的冗長描述信息,比如“訪問時間增加一個月”,來描述其緩存能力。這里有幾個例子:

ExpiresDefault "access plus 1 week"
ExpiresByType text/html "modification plus 2 days 6 hours 12 minutes"

  通過mod_cern_meta模塊可以將一個包含HTTP首部的文件與特定的對象聯系起來。啟動這個模塊時,就創建了一組“元文件”,每個需要控制的文檔一個,而且還會為每個元文件添加所期望的首部

【通過HTTP-EQUIV控制HTML緩存】

  HTTP服務器響應首部用於回送文檔的到期信息以及緩存控制信息。Web服務器與配置文件進行交互,為所提供的文檔分配正確的Cache-Control首部

  為了讓作者在無需與Web服務器的配置文件進行交互的情況下,能夠更容易地為所提供的HTML文檔分配HTTP首部信息,HTML2.0定義了<META HTTP-EQUIV>標簽。這個可選的標簽位於HTML文檔的頂部,定義了應該與文檔有所關聯的HTTP首部。這里有一個<META HTTP-EQUIV>標簽設置的例子,它將HTML文檔標記為非緩沖的

<HEAD>
<TITLE>My Document</TITLE>
<META HTTP-EQUIV="Cache-control" CONTENT="no-cache"> 
</HEAD>

  最初,HTTP-EQUIV標簽是給Web服務器使用的。Web服務器應該為HTML解析<META HTTP-EQUIV>標簽,並將規定的首部插入HTTP 響應中:HTTP服務器可以用此信息來處理文檔。特別是,它可以在為請求此文檔的報文所發送的響應中包含一個首部字段:首部名稱是從HTTP-EQUIV屬性值中獲取的,首部值是從CONTENT屬性值中獲取的

  不幸的是,支持這個可選特性會增加服務器的額外負載,這些值也只是靜態的,而且它只支持HTML,不支持很多其他的文件類型,所以很少有Web服務器和代理支持此特性。 但是,有些瀏覽器確實會解析並在HTML內容中使用HTTP-EQUIV標簽,像對待真的HTTP首部那樣來處理嵌入式首部。這樣的效果並不好,因為支持HTTP-EQUIV標簽的HTML瀏覽器使用的Cache-control規則可能會與攔截代理緩存所用的規則有所不同。這樣會使緩存的過期處理行為發生混亂

  總之,<META HTTP-EQUIV>標簽並不是控制文檔緩存特性的好方法。通過配置正確的服務器發出HTTP首部,是傳送文檔緩存控制請求的唯一可靠的方法

 

緩存流程

【緩存類型】

  瀏覽器緩存分為強緩存和協商緩存,它們的區別在於強緩存不發請求到服務器,協商緩存會發請求到服務器

  1、強緩存

  瀏覽器在加載資源時,先根據這個資源的一些http header判斷它是否命中強緩存,強緩存如果命中,瀏覽器直接從自己的緩存中讀取資源,不會發請求到服務器。比如某個css文件,如果瀏覽器在加載它所在的網頁時,這個css文件的緩存配置命中了強緩存,瀏覽器就直接從緩存中加載這個css,連請求都不會發送到網頁所在服務器;

  強緩存通過頭信息expires和cache-control控制

  Expires是http1.0提出的一個表示資源過期時間的header,它描述的是一個絕對時間,由服務器返回,用GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2016 23:55:55 GMT

  由於它是服務器返回的一個絕對時間,這樣存在一個問題,如果客戶端的時間與服務器的時間相差很大(比如時鍾不同步,或者跨時區),那么誤差就很大,所以在HTTP 1.1版開始,使用Cache-Control: max-age=秒替代

  Cache-Control描述的是一個相對時間,在進行緩存命中的時候,都是利用客戶端時間進行判斷,所以相比較Expires,Cache-Control的緩存管理更有效,安全一些

  注意:瀏覽器f5刷新時,會自動將cache-control置為max-age=0,如果是ctrl+f5強制刷新,則cache-control會置為no-cache

  2、協商緩存

  當強緩存沒有命中的時候,瀏覽器一定會發送一個請求到服務器,通過服務器端依據資源的另外一些http header驗證這個資源是否命中協商緩存,如果協商緩存命中,服務器會將這個請求返回(304),但是不會返回這個資源的數據,而是告訴客戶端可以直接從緩存中加載這個資源,於是瀏覽器就又會從自己的緩存中去加載這個資源;若未命中請求,則將資源返回客戶端,並更新本地緩存數據(200)

  協商緩存通過last-modified和etag控制

  Last-Modified:標示這個響應資源的最后修改時間。web服務器在響應請求時,告訴瀏覽器資源的最后修改時間

  If-Modified-Since:當資源過期時(強緩存失效),發現資源具有Last-Modified聲明,則再次向web服務器請求時帶上頭 If-Modified-Since,表示請求時間。web服務器收到請求后發現有頭If-Modified-Since 則與被請求資源的最后修改時間進行比對。若最后修改時間較新,說明資源又被改動過,則響應整片資源內容(寫在響應消息包體內),HTTP 200;若最后修改時間較舊,說明資源無新修改,則響應HTTP 304 (無需包體,節省瀏覽),告知瀏覽器繼續使用所保存的cache。

  Last-Modified標注的最后修改只能精確到秒級,如果某些文件在1秒鍾以內,被修改多次的話,它將不能准確標注文件的修改時間(無法及時更新文件)

  如果某些文件會被定期生成,有時內容並沒有任何變化,但Last-Modified卻改變了,導致文件沒法使用緩存,有可能存在服務器沒有准確獲取文件修改時間,或者與代理服務器時間不一致等情形(無法使用緩存)

  Etag解決了上述問題

  Etag:web服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(生成規則由服務器決定)。Apache中,ETag的值,默認是對文件的索引節(INode),大小(Size)和最后修改時間(MTime)進行Hash后得到的

  If-None-Match:當資源過期時(使用Cache-Control標識的max-age),發現資源具有Etage聲明,則再次向web服務器請求時帶上頭If-None-Match (Etag的值)。web服務器收到請求后發現有頭If-None-Match 則與被請求資源的相應校驗串進行比對,決定返回200或304

【緩存過程】

  緩存過程如下

cache

【靜態資源優化過程】 

  一般地,靜態資源優化過程如下所示

  1、配置超長時間的本地緩存 —— 節省帶寬,提高性能

  2、采用內容摘要作為緩存更新依據 —— 精確的緩存控制

  3、靜態資源CDN部署 —— 優化網絡請求

  4、更新資源發布路徑實現非覆蓋式發布 —— 平滑升級

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM