為什麼要使用 Web 緩存 Web緩存一般分為瀏覽器緩存、代理伺服器緩存以及網關緩存,本文主要講的是 瀏覽器緩存 ,其它兩種緩存大家自行去瞭解下。 Web 緩存游走於伺服器和客戶端之間。這個伺服器可能是源伺服器(資源所駐留的伺服器),數量可能是1個或多個;這個客戶端也可能是1個或多個。Web 緩存就 ...
為什麼要使用 Web 緩存
Web緩存一般分為瀏覽器緩存、代理伺服器緩存以及網關緩存,本文主要講的是 瀏覽器緩存,其它兩種緩存大家自行去瞭解下。
Web 緩存游走於伺服器和客戶端之間。這個伺服器可能是源伺服器(資源所駐留的伺服器),數量可能是1個或多個;這個客戶端也可能是1個或多個。Web 緩存就在伺服器-客戶端之間搞監控,監控請求,並且把請求輸出的內容(例如html頁面、 圖片和文件)(統稱為副本)另存一份;然後,如果下一個請求是相同的 URL,則直接請求保存的副本,而不是再次麻煩源伺服器。
使用緩存的2個主要原因:
- 降低延遲:緩存離客戶端更近,因此,從緩存請求內容比從源伺服器所用時間更少,呈現速度更快,網站就顯得更靈敏。
- 降低網路傳輸:副本被重覆使用,大大降低了用戶的帶寬使用,其實也是一種變相的省錢(如果流量要付費的話),同時保證了帶寬請求在一個低水平上,更容易維護了。
試想現在的大型網站,隨便一個頁面都是一兩百個請求,每天 pv 都是億級別,如果沒有緩存,用戶體驗會急劇下降(表現在等待請求的時間上)、同時伺服器壓力和網路帶寬都面臨嚴重的考驗。
瀏覽器緩存控制機制
瀏覽器緩存控制機制有三種:HTML5離線存儲和本地緩存、HTML Meta 標簽、HTTP 協議緩存。
HTML5離線存儲和本地緩存
該種緩存機制是運用 HTMl5 新推出一些支持離線應用的 API 來進行數據的緩存,比如 appcache、sessionStorage、localStorage等等。
appcache 通過定義一個描述文件(manifest file)來列出要下載和緩存的資源,manifest file 示例如下:
CACHE MANIFEST
# Comment
file.js
file.css
然後在 html 中引用:
<html manifest="./xxx.manifest">
sessionStorage、localStorage 的基本用法如下:
// localStorage 用法相似
sessionStorage.set('name', 'laixiangran') // 存儲數據
sessionStorage.get('name') // 獲取數據 'laixiangran'
本文暫時就不詳細介紹,後面我會單獨介紹這塊的內容。
HTML Meta 標簽
使用 HTML Meta 標簽,Web 開發者可以在 HTML 頁面的 <head>
節點中加入 <meta>
標簽,代碼如下:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
上述代碼的作用是告訴瀏覽器當前頁面不被緩存,每次訪問都需要去伺服器拉取。
使用上很簡單,但只有部分瀏覽器可以支持,而且所有緩存代理伺服器都不支持,因為代理不解析 HTML 內容本身。
HTTP 協議緩存
HTTP 協議緩存是我們本文講解的重點,它是通過 HTTP 頭信息來控制緩存的,HTTP 頭信息可以讓你對瀏覽器和代理伺服器如何處理你的副本進行更多的控制。他們在 HTML 代碼中是看不見的,一般由 Web 伺服器自動生成。但是,根據你使用的伺服器,你可以在某種程度上進行控制。
瀏覽器請求流程
瀏覽器第一次請求流程圖:
該流程比較簡單了,瀏覽器在第一次請求的時候不存在緩存,直接從瀏覽器請求,等請求返回結果之後再根據 HTTP 頭信息將數據緩存在記憶體或者硬碟中。
瀏覽器再次請求時:
該流程就複雜多了,瀏覽器需要根據 HTTP 頭信息來判斷是否直接從緩存讀取數據還是交由伺服器來判斷是否從緩存讀取數據。
幾種狀態碼的區別:
下麵我們就從該流程中出現的 HTTP 狀態碼 200(from cache)和 304 來講解 HTTP 協議緩存中的 HTTP 頭信息。
200(from cache)
這種 HTTP 狀態碼表示不訪問伺服器,直接從緩存(記憶體或者硬碟)讀取數據。
看兩張圖:
從上面兩張圖,我們會看到狀態碼有點不一樣,分別是 200(from memory cache)
以及 200(from diks cache)
,這兩個的區別一個是從記憶體讀取數據,一個是從硬碟讀取數據,然後它們的先後順序是先從記憶體讀取,再從硬碟讀取。這裡我們就統稱為 200(from cache)
。
出現 200(from cache)
這種情況,我們需要關註 Expires
和 Cache-control
這兩種HTTP 頭信息欄位。
Expires
Expires 的中文意思是“有效期”。顯然,就是告訴瀏覽器緩存的有效期。如果過期,緩存會檢查源伺服器以確定文件是否改變了。
Expires 頭唯一的有效值是 HTTP 時間,其他值無效,不會去緩存的。註意:時間是格林威治時間(GMT),而不是本地時間。如下所示:
Expires: Mon, 29 Oct 2018 03:53:10 GMT
那麼看我們上面的兩張圖中的 Expires,它都是到 2018-10-29 03:53:10 過期,而我們本次請求的時間 Date 是 2018-04-29 03:53:10,因此本次請求直接從緩存讀取數據,返回 200(from cache)。
儘管 Expires 頭很有用,但它有一定的局限性:
- 因為牽扯到時間,Web 伺服器端的時間必須和緩存的同步,否則很可能實現不了預期的結果 —— 緩存把過期的數據當成最新的數據,把最新的數據當作過期的數據。
- 你很容易忘記給某內容設置了一個特定時間,如果返回內容的時候沒有更新這個過期時間,則每個請求都是上訪到伺服器,反而增加了負載和響應時間。
- 最後呢,Expires 是 HTTP 1.0 的東西,現在預設瀏覽器均預設使用 HTTP 1.1,所以它的作用基本忽略。
Cache-Control
Cache-Control 與 Expires 的作用一致,都是指明當前資源的有效期,控制瀏覽器是否直接從瀏覽器緩存讀取數據還是重新發請求到伺服器讀取數據。只不過 Cache-Control 的選擇更多,設置更細緻,如果同時設置的話,其優先順序高於 Expires。
Cache-Control 有用的響應頭包括:
- max-age=[秒]: 表示在這個時間範圍內緩存是新鮮的無需更新。類似 Expires 時間,不過這個時間是相對的,而不是絕對的。也就是某次請求成功後多少秒內緩存是新鮮的。
- s-maxage=[秒]: 類似 max-age, 除了僅應用於共用緩存(如代理)。
- public: 標記認證的響應才能夠被緩存。一般而言,需要認證 HTTP 請求內容會自動私有化(不會被緩存)。
- privateN: 允許緩存專門為某一個用戶存儲響應,比方說在瀏覽器中;共用緩存一般不會,例如在代理中。
- no-cache: 每次在釋放緩存副本之前都強制發送請求給源伺服器進行驗證,這在確保認證有效性上很管用(和 public 結合使用)或者保證內容必須是即時的,不得無視緩存的所有優點,如國內的微博、twitter等的刷新顯示。
- no-store: 強制緩存在任何情況下都不要保留任何副本。
- must-revalidate: 告訴緩存,我給你準備了一些關於新鮮度的信息,在表現的時候要嚴格遵循。HTTP 允許緩存在某些特定情況下返回過期數據,指定了這個屬性,相對於告訴緩存,你必須嚴格遵循我的規則。
- proxy-revalidate: 類似 must-revalidate,除了只能應用於代理緩存。
使用如下所示:
Cache-Control: max-age=15811200
那麼看我們上面的兩張圖中的 Cache-Control,它在當前請求成功後15811200秒內都是有效的,因此本次請求直接從緩存讀取數據,返回 200(from cache)。如果從當前請求成功開始,過了15811200秒之後就會重新從伺服器請求新數據。
304
當瀏覽器通過 Expires
或者 Cache-control
判斷出緩存已經過期,那麼就需要重新發送請求到伺服器,讓伺服器判斷當前緩存是否可以繼續使用。
當伺服器判斷該緩存已經失效,那麼就會返回新數據,HTTP 狀態碼為 200;
當瀏覽器判斷該緩存還未失效,那麼就會返回 HTTP 狀態碼為 304 (無需包體,節省流量),告知瀏覽器繼續使用緩存。
那麼通過哪些 HTTP 頭信息欄位來判斷是否返回 200 還是 304 呢?那麼我們就請出接下來的主角: Last-Modified/If-Modified-Since
及 Etag/If-None-Match
。這兩個欄位都需要配合 Cache-Control
使用。
Last-Modified/If-Modified-Since
Last-Modified: 標示這個響應資源的最後修改時間。web 伺服器在響應請求時,告訴瀏覽器資源的最後修改時間。
If-Modified-Since: 當資源過期時(使用
Cache-Control
標識的max-age
),發現資源具有Last-Modified
聲明,則再次向 web 伺服器請求時帶上 If-Modified-Since,表示請求時間。web伺服器收到請求後發現有 If-Modified-Since 則與被請求資源的最後修改時間進行比對。若最後修改時間較新,說明資源有被改動過,則響應資源內容(寫在響應消息包體內),HTTP 200;若最後修改時間較舊,說明資源無新修改,則響應 HTTP 304 (無需包體,節省流量),告知瀏覽器繼續使用緩存。
Etag/If-None-Match
這是在 HTTP 1.1 中引入了一個新的驗證器。
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。
Etag 優先於 Last-Modified
你可能會覺得使用 Last-Modified 已經足以讓瀏覽器知道本地的緩存副本是否足夠新,為什麼還需要 Etag(實體標識)呢?HTTP1.1 中 Etag 的出現主要是為瞭解決幾個 Last-Modified 比較難解決的問題:
Last-Modified 標註的最後修改只能精確到秒級,如果某些文件在1秒鐘以內,被修改多次的話,它將不能準確標註文件的修改時間。
如果某些文件會被定期生成,當有時內容並沒有任何變化,但Last-Modified卻改變了,導致文件沒法使用緩存。
有可能存在伺服器沒有準確獲取文件修改時間,或者與代理伺服器時間不一致等情形。
Etag 是伺服器自動生成或者由開發者生成的對應資源在伺服器端的唯一標識符,能夠更加準確的控制緩存。Last-Modified 與 ETag 是可以一起使用的,伺服器會優先驗證 ETag,一致的情況下,才會繼續比對 Last-Modified,最後才決定是否返回 304。
創建支持緩存網站的小技巧
通過上面的介紹,我們知道 HTTP 協議緩存的機制,目的就是讓你可以更靈活更細緻的控制瀏覽器緩存,從而讓你的網站的緩存更加友好,用戶體驗更完美。
下麵這些技巧也可以讓你網站的緩存更加友好:
- 保持URL穩定: 這是緩存的金科玉律,如果你為不同頁面,不同用戶或不同網站提供相同的內容,他們應該使用相同的URL。 這是簡單卻非常行之有效的方法。例如,你的 HTML 中的某個引用地址是"/index.html", 則要一直使用這個地址。
- 不同地方的圖片和其他元素 使用同一庫。
- 對於不經常改變的圖片/頁面啟用緩存,通過將
Cache-Control: max-age
頭信息的值設大一點。 - 對於定期更新的內容通過指定
max-age
或過期時間實現緩存。 - 如果資源改變了(尤其下載文件),改變其名字。由於一般這種資源會有很長的過期時間,而伺服器上一直是正確的版本;因此,鏈接這個下載資源的頁面需要要比較短的過期時間。否則,會出現伺服器的資源是新的,但頁面被緩存了,其中的鏈接地址還是舊的,就會出現新舊版本衝突的可能。
- 萬不得已不要變動文件: 否則你要設置一個新的
Last-Modified
值。另外,當你更新站點的時候,只要上傳改動的那些文件,而不要把整個站點都覆蓋過去。 - Cookie能不用就不用: Cookie 難以被緩存,且大多情境下是沒有必要的。如果你非得使用 Cookie,建議用在動態頁面上。
- 減少SSL的使用: 因為共用緩存不能存儲認證頁面,只在必要的時候使用,並且在 SSL 頁面上減少圖片的使用。
SSL:全稱 Secure Socket Layer – 安全套接層,為 Netscape 所研發,用以保障在 Internet 上數據傳輸之安全,利用數據加密 (Encryption) 技術,可確保數據在網路上的傳輸過程中不會被截取及竊聽。目前一般通用的規格為 40 bit 的安全標準,美國則已推出 128 bit 的更高安全標準,但限制出境。只要 3.0 版本以上的 I.E. 或 Netscape 瀏覽器即可支持 SSL。
- 使用 REDbot 檢查你的網站: 可以幫助你應用本文所介紹的一些概念。
REDbot:REDbot = RED + robot,是個機器人,檢查 HTTP 資源,看他們如何會表現,指出常見的問題,並提出改進建議。雖然它屬於 HTTP 一致性測試儀,但卻可以找到不少 HTTP 相關問題。
用戶行為與緩存
用戶的一些行為會影響到瀏覽器的緩存,具體如下: