為什麼要使用 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 相關問題。
使用者行為與快取
使用者的一些行為會影響到瀏覽器的快取,具體如下: