谷歌 Web 開發最佳實踐手冊(4.2.3):HTTP 快取

陳 鑫偉發表於2014-06-03

【伯樂線上提示】:① 5月6日,谷歌開發者中心推出了一個 Web 開發最佳實踐手冊。伯樂線上資源頻道摘編該資源後,已邀請一些關注 Web 開發的朋友參與翻譯手冊。② 由於譯者朋友幾乎都是已在職,都是在工作之餘參與,每位的翻譯進度會不一樣(請理解),所以手冊中文版不會按照英文版章節順序釋出。③ 手冊中文版尚不完整,請不要轉載,謝謝合作。


【導讀】:在網際網路上獲取內容是相當耗時和昂貴的:大的響應需要在客戶端和伺服器之間有很多次通訊來回,這延緩了他們被瀏覽器使用和處理的時機,同時也增加了訪問者的資料流量開銷。因此,快取和重用已獲取的資源成為了效能優化很關鍵的一方面。

本課內容

  • 使用ETags驗證已快取的響應
  • Cache-Control
  • 定義最優Cache-Control策略
  • 廢棄和更新已快取的響應
  • 快取備忘錄

好訊息是每個瀏覽器都自帶HTTP快取的實現!我們所要做的就是保證每個伺服器響應提供正確的HTTP頭指令資訊來指導瀏覽器何時進行快取以及快取保留多長時間。

注意:
如果你是在應用中使用Webview來獲取和顯示Web內容,你可能需要提供額外的配置項來保證HTTP快取的啟用,並且根據你的應用場景設定合理的快取大小,然後這些快取才能被持久化下來。你可以檢視平臺的文件來確認你的設定。

 

http-request

伺服器返回一個響應的同時也發出了一個HTTP頭的集合,用來描述響應的型別,長度,快取指令,驗證令牌(validation token)等。舉個例子,在上圖的互動中,伺服器返回了一個1024位元組的響應,指示客戶端將它快取120秒,提供了一個驗證令牌(“x234dff”),它可以在響應過期之後用來驗證資源是否更新過。

使用ETags驗證快取的響應

學習重點

  • 驗證令牌通過ETag HTTP頭與伺服器通訊
  • 驗證令牌提供了快速資源更新檢查:如果資源沒有被更新,則不需要傳輸資料

讓我們假設瀏覽器在獲取一個資源120秒之後又對相同的資源發出請求。首先,瀏覽器會檢查本地快取並找到之前的響應,不幸的是這個響應已經“過期”不能用了。這時,瀏覽器原本可以直接發出一個新的請求來獲取完整的響應,但是這並不是最高效的,因為如果這個資源沒有被更改過,我們就沒有理由再去下載一個跟快取中完全一樣的資源。

這就是ETag頭資訊中放入驗證令牌所要解決的問題:伺服器生成並返回一個隨機令牌,通常是檔案內容的雜湊(hash)或者其他指紋碼(fingerprint)。客戶端不需要知道指紋碼是如何生成的,它只需要將它在下一個請求中發回給伺服器:如果指紋碼仍然一致,說明資源沒有被修改,我們就可以跳過下載。

http-cache-control

 

在上面的例子中,客戶端自動在“If-None-Match”請求頭中提供了ETag令牌,伺服器端針對當前的資源檢查令牌,如果沒有被修改過,就返回“304 Not Modified”響應,告訴瀏覽器,快取中的響應沒有被修改過,可以延用下一個120秒。注意到我們不需要再次下載響應——這節約了時間和頻寬。

作為一個Web開發人員,你該如何利用高效的加簽(revalidation)?瀏覽器已經為我們做了很多:它自動檢測驗是否已經擁有驗證令牌,它會將驗證令牌附加到發出的請求上,會根據伺服器的響應在必要的時候更新快取時間戳。事實上,唯一留給我們要做的就是保證伺服器提供必要的ETag令牌:閱讀你的伺服器文件並設定必要的配置項。

小貼士:HTML5 樣板專案包含了所有最流行的伺服器的配置檔案樣例,併為每一個配置項都提供了詳細的註釋:找到你最喜歡的伺服器,查詢適當的設定項,然後拷貝/確認你的伺服器使用了推薦的設定。

Cache-Control

學習重點:

  • 每一個資源都可以通過Cache-Control HTTP頭來定義自己的快取策略
  • Cache-Control指令控制誰,在什麼情況下,可以多久快取響應

最好的請求是不需要和伺服器互動的請求:一個本地的響應拷貝可以幫助我們消除所有的網路等待和資料傳輸費用。為了達到這個目的,HTTP規範允許伺服器返回一系列不同的Cache-Control指令來控制一個響應在瀏覽器或者其他中繼快取中如何被快取以及快取多久。

小貼士:Cache-Control頭定義在HTTP/1.1規範中,取代了之前用來定義響應快取策略的頭資訊(如:Expires)。所有的現代瀏覽器都支援Cache-Control,因此使用它就夠了。

http-cache-control-highlight

 

“no-cache”和“no-store”

“no-cache”指明當前返回的響應在後續相同的URL請求時必須先與伺服器確認響應是否被修改,之後才能被用作後續請求響應的快取。因此,如果我們有一個合適的驗證令牌(ETag),no-cache會增加一次與伺服器的通訊來驗證和確認快取的響應,但是可以避免重複下載不曾更新的響應。

相比之下,“no-store”更加簡單,它直接禁止所有的瀏覽器和中繼快取儲存任何版本的響應——比如:一個包含了個人隱私資訊或者銀行資訊的響應。每次使用者請求這些資源的時候,都會傳送一個請求到伺服器並且下載完整的響應內容。

“public”和“private”

如果響應被標誌為“public”,那麼它是可以被快取的,即使它有HTTP認證資訊,甚至響應狀態碼不是正常的可快取。大多數情況下,“public”不是必須的,因為明確的快取資訊(比如“max-age”)已經說明響應是可以被快取的。

相比之下:“private”響應可以被瀏覽器快取,但是通常只為單個使用者快取,一次不允許任何中繼快取件對其進行快取——比如,一個包含使用者私人資訊的HTML頁面可以被這個使用者的瀏覽器所快取,但是不能被CDN快取。

“max-age”

這個指令指明獲取的響應從當前請求開始允許被重用的最大時間限度(單位為秒)。例如:“max-age=60”說明響應在接下來的60秒內可以被快取和重用。

定義最優Cache-Control策略

http-cache-decision-tree根據上面的決策樹來為你的應用使用的單個資源或資源集設定最優的快取策略。理想情況下,你應該旨在在客戶端儘可能長時間地快取儘可能多的響應內容,並且為每個響應提供驗證令牌來啟用快速驗證。

Cache-Control指令 說明
max-age=86400 響應可以在瀏覽器或任何中繼快取中(如果它是”public”的)被快取最多一天(60秒 x 60分 x 24小時)
private,max-age=600 響應可以在使用者的瀏覽器上快取十分鐘(60秒 x 10分)
no-store 快取不允許被快取,每個請求都要重新獲取

根據HTTP文件,在排名最高的300,000個網站中(Alexa評分),幾乎下載的響應中有一半可以被瀏覽器快取,這在重複的頁面瀏覽和訪問來說是一個巨大的節省!當然,這並不意味著你特定的應用就一定有50%的資源可以被快取:有些網站可以快取90%以上的資源,而有些則有很多私人的或者時間敏感的資料以至於根本不能被快取。

審查你的頁面來識別出哪些資源可以被快取,並確保他們返回正確的Cache-Control和ETag頭資訊。

廢棄和更新已快取的響應

學習重點:

  • 本地快取的響應會被持續使用知道資源“過期”
  • 在URL中嵌入一個檔案內容指紋碼可以幫助我們強制瀏覽器更新新版本的響應
  • 每個應用都要定義它自己的快取結構從而達到最優的效能

一個瀏覽器建立的HTTP請求會首先被路由到瀏覽器的快取來檢視是否有存在有效的響應快取來直接答覆請求。如果有匹配的響應,它就會直接從快取中讀取,這樣我們就省去了傳輸所帶來的網路等待和資料流量。然而,我們該如何更新或者廢棄一個已快取的請求?

舉例來說,假設我們已經告訴我們的訪問者快取我們的CSS樣式檔案24小時(max-age=86400),但是我們的設計師剛剛提交了一個更新,我們希望所有使用者都能使用。那麼我們該如何通知我們所有的訪問者他們的快取拷貝已經過時了,需要更新快取?這是一個欺騙性的題目——實際上我們我們做不到,至少在不改變資源URL的情況下做不到。

一旦響應被瀏覽器快取,被快取的版本一直被使用,直到它不再新鮮,這由max-age或者expires指定,或者它因為某些原因從快取中被刪除——比如使用者清理瀏覽器快取。因此,不同的使用者在頁面被構造的時候可能使用的是不同的版本的CSS檔案;最新獲取資源的使用者會使用更新過的版本,而快取過之前版本(依然有效)拷貝的使用者會繼續使用老版本的響應。

所以,我們如何才能兼得魚和熊掌:客戶端快取和快速更新?

很簡單,我們可以在資源內容更新時改變資源的URL來強制使用者下載最新的響應。典型地:可以通過在檔名上嵌入檔案的指紋碼,或者版本號來實現——比如 styoe.x234dff.css。

http-cache-hierarchy

能夠定義單個資源級別的快取策略讓我們能夠定義“快取層級”,這讓我們不但能夠控制快取有效的時間,而且還能控制新的版本何時能被訪問者看到。例如,我們一起分析一下上面的例子:

  • HTML被標記成“no-cache”,這意味著瀏覽器在每個請求時都會重新驗證文件,當內容更新後會獲取最新版本。同時,在HTML標記中,我們給CSS和JavaScript資源嵌入指紋碼:如果這些檔案的內容發生變化,頁面的HTML也會隨著變化,那麼新的HTML響應就會被下載。
  • CSS允許被瀏覽器和中繼快取(比如CDN)快取,並且過期時間設定成1年。注意我們可以安全地將過期時間設定成1年,因為我們在檔名中使用了指紋碼:如果CSS更新了,那麼URL也會被更新。
  • JavaScript也被設定成一年過期,但是它被標記成私有的,也許是因為它包含了一些私有的使用者資料,這些不應該被CDN快取。
  • 圖片被快取而不包含版本資訊和唯一的指紋碼,過期時間為1天。

結合使用ETag,Cache-Control,和唯一的URL允許我們提供最佳的方案:長久的過期時間,控制哪些響應可以被快取,同時按需更新。

快取備忘錄

沒有一個固定的最佳快取策略。你要根據你的通訊模式,服務的資料型別,和應用特定的資料重新整理需求等來定義和配置最合適的單個資源級別的設定,以及整體的“快取層級”。

在定義快取策略時使用的一些技巧和技術:

  1. 使用一致的URL:如果你對相同的內容使用不同的URL,那麼這些內容將會獲取和儲存多次。提示:注意URL是大小寫敏感的
  2. 確保伺服器提供有效的令牌(ETag):驗證令牌消除了當伺服器端檔案沒有更改時傳輸相同資料的情況。
  3. 定義哪些資源可以被中繼快取:那些對於所有使用者都一樣的響應很適合快取於CDN或其他中繼快取。
  4. 對每個資源定義最優的快取週期:不同的資源可能有不同的重新整理需求。為每一個資源審查並確定合適的max-age。
  5. 為你的網站定義最佳的快取層級:對HTML文件結合使用帶有內容指紋碼的資源URL和短時間或no-cache生命週期,能夠允許你控制客戶端合適獲取最新的更新。
  6. 最小化攪動(chrun):有些資源比其他的更新頻繁。如果有一些特定的資源(比如JavaScript函式,或者一組CSS樣式)會經常更新,考慮將他們提取到單獨檔案。這樣做可以讓其他的內容(比如那些不太更新的庫檔案)可以從快取中讀取,從而當內容更新時最小化需要下載的內容。

相關文章