每次訪問網頁,通常瀏覽器會從服務器下載所 需的資源,例如 HTML 文檔、圖片、CSS、JavaScript,甚至包括字體文件等。這里面的許多文件(例如圖片)都是很少變動的,如果每次都要從服務器重新下載,會不必要 地增加網頁載入時間,同時也會對服務器造成一定壓力。通過合理配置緩存策略,可令瀏覽器以某種方式把這些靜態的文件緩存起來,下次請求同一資源時,直接使 用本地存儲的副本,而不是從服務器重新下載。

啟用緩存至少有兩點顯而易見的好處:

  • 減少頁面加載時間
  • 減少服務器負載

瀏覽器是否使用緩存、緩存多久,是由服務器控制的。準確來說,當瀏覽器請求一個網頁(或者其他資源)時,服務器發回的響應的「響應頭」部分的某些字段指明了有關緩存的關鍵信息。

?

Cache-Control

Cache-Control HTTP 響應頭是 HTTP 1.1 協議新增的指令,每個資源都可以通過設定 Cache-Control 來建立緩存策略。通常,可為它指定一個 max-age ,表示緩存的最長時間,單位為秒。例如,若設定 Cache-Control: max-age=604800 ,則表示這個資源的有效時間為 7 天。瀏覽器第一次獲取這個資源后,7 天之內若再次請求,通常都不會與服務器進行任何通信,而是直接使用本地副本。

此外,還可以為 Cache-Control 指定 public private 標 記。如果使用 private,則表示該資源僅僅屬于發出請求的最終用戶,這將禁止中間服務器(如代理服務器)緩存此類資源。對于包含用戶個人信息的文件(如一個包含用 戶名的 HTML 文檔),可以設置 private,一方面由于這些緩存對其他用戶來說沒有任何意義,另一方面用戶可能不希望相關文件儲存在不受信任的服務器上。需要指出的 是,private 并不會使得緩存更加安全,它同樣會傳給中間服務器(如果網站對于傳輸的安全性要求很高,應該使用傳輸層安全措施)。對于 public,則允許所有服務器緩存該資源。通常情況下,對于所有人都可以訪問的資源(例如網站的 logo、圖片、腳本等),Cache-Control 設為 public 是合理的。

?

Expires

同樣是用來控制緩存, Expires 響 應頭從另一個角度——指明緩存的具體過期日期,來控制資源何時過期。在過期時間以內,若再次發起請求,通常瀏覽器都不會與服務器進行任何通信,而是直接使 用本地副本。Apache 服務器允許以多種方式,例如基于該資源的訪問時間或上次修改時間來設定 Expires 的值。注意,這里的時間一律使用格林威治時間(Greenwich Mean Time, GMT),而非本地時間。

當 Expires 和 Cache-Control 同時出現時,通常后者會覆蓋前者的設定。由于 Expires 對用戶的系統時間有所依賴,因此通常認為使用 Cache-Control 是更好的選擇(基本上所有的瀏覽器都支持 Cache-Control 指令)。

?

Last-Modified 和 ETag

服務器可在 HTTP 返回頭中包含 Last-Modified 字段或者 ETag 字段。Last-Modified 表示被請求資源在服務器端的上次修改時間,而 ETag 則是一個唯一文件標識符,每次文件修改后都會生成一個新的 ETag。服務器通過向瀏覽器發送這兩個字段,來告知瀏覽器其獲得的資源的版本。

無論通過 Cache-Control 還是 Expires 設置緩存,在過期時間以內,當用戶點擊瀏覽器刷新按鈕時,為了 確保 用戶所加載的資源是最新的,大部分瀏覽器不會再直接使用緩存中的數據,而是發出一個條件請求(Conditional GET Request)。對于這類請求,瀏覽器會在請求頭中包含 If-Modified-Since If-None-Match 字 段。前者即瀏覽器當初得到的 Last-Modified;后者即瀏覽器當初得到的 ETag。當服務器發現資源的更新時間晚于 If-Modified-Since 所提供的時間,或者資源在服務器端當前的 ETag 和 If-None-Match 提供的不符時,會響應整個資源,否則只會響應一個 304 Not Modified 狀態碼(因此瀏覽器將不需要重新下載整個資源)。這種機制可以最大程度上減少數據下載量。此外,如果緩存的資源已過期,瀏覽器通常有兩種選擇:重新下載這 個資源,或發出一個條件請求。很多瀏覽器都會采取后者,以節約資源。

由于 Last-Modified 和 ETag 的作用是相同的(均為向服務器驗證資源是否最新),因此只使用一個即可。通常認為 Last-Modified 更好(它和 Expires 不同,由服務器生成,不依賴瀏覽器端時間)。

?

我的網站啟用緩存了嗎?

?

用瀏覽器的開發者工具或插件查看

為了確定是否啟用了緩存,只需要檢查服務器發回的「響應頭」就可以。許多瀏覽器以及工具都可以檢查這些信息,我們以 Firefox 的插件 Firebug 為例。如圖所示:

cache-optimization-1

下面再來看一個沒有啟用緩存的資源的例子:

cache-optimization-2

沒有包含 Cache-Control 以及 Expires 信息。

?

在線檢測

也有一些方便的在線檢測服務,用于對網站速度給出建議,其中就會檢測緩存設置情況。比如 Yahoo! 公司的 YSlow,以及 百度站長工具 等,都有相應的功能。大家可以去百度那里檢測一下,目前是不需要登錄即可檢測的。

?

使用緩存的策略

?

為靜態資源設置長緩存時間

有些資源是很長時間不會改變的,比如網站的 logo 圖片、jQuery 庫、字體等,因此可以為它們設定「永不過期」的緩存時間,例如設定為 10 年。

?

確保文件修改生效

有 些時候我們會修改一些資源,比如更新了 jQuery 版本,或網站的 CSS 樣式。如果這些資源已經被緩存,那么除非用戶手工刷新頁面,否則要等緩存自然過期之后用戶才會獲得新版本。如何在這種情況下強制瀏覽器重新下載呢?最有效 的一個辦法就是在這類資源的文件名中包含版本信息,并在更改之后對應地修改文件名。瀏覽器發現文件更換后,自然無法使用緩存,而會重新下載。

?

對于 HTML 文檔謹慎設定過期時間

大 部分情況下,對于其他圖片、CSS、JavaScript 等資源的請求都來自一個單一的 HTML 文檔。對于這類頁面通常應該設定比較短的過期時間,或者干脆不設定。因為如果這類頁面被緩存,那么頁面中包含的資源的文件名等等信息都會一并被緩存,導致 對它的更新難以確保立即對用戶生效。

引用靜態資源時,不要使用 Query String

Query String 就是例如 ?key=val 的字符串,如

        <script src="/static/js/func.js?v=a87ff8"></script>
      

?

這會阻止一部分較老的瀏覽器(包括 IE6 )對該資源進行緩存。

?

設定緩存的方法

對于 Apache 服務器,可以通過 mod_expires 模塊來設定 Expires HTTP 頭或 Cache-Control HTTP 頭的 max-age 指令。編輯相應目錄下的 .htaccess 文件,或直接對 Apache 的配置文件(根據服務器系統版本不同,可能為 httpd.conf apache2.conf 等)作出修改。

?

分文件類別設定

使用 ExpiresByType 可以按照文件的 MIME Type 設定某一類文件的過期日期。例如:

        <IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType	text/css                "access plus 1 week"
    ExpiresByType	application/javascript  "access plus 2 weeks"
    ExpiresByType	image/x-icon            "access plus 6 months"
    ExpiresByType	image/gif               "access plus 6 months"
    ExpiresByType	image/png               "access plus 6 months"
    ExpiresByType	image/jpeg              "access plus 6 months"
    ExpiresByType	video/x-flv             "access plus 6 months"
    ExpiresByType	application/pdf         "access plus 6 months"
</IfModule>
      

?

其中 access plus 1 week 表示將緩存過期設置為訪問時間(即當前時間)之后的一周。如果將 access 替換為 modification ,則緩存過期會被設定為文件修改時間之后的一周。可以使用的時間單位包括:

  • years
  • months
  • weeks
  • days
  • hours
  • minutes
  • seconds

不同的時間也可以進行組合,例如:

        ExpiresByType text/html "access plus 1 month 15 days 2 hours"
ExpiresByType image/gif "modification plus 5 hours 3 minutes"
      

?

根據文件擴展名進行設置

如果希望根據擴展名來指定緩存規則,可以使用 FilesMatch 配合正則表達式。為了簡潔,我這里只規定了 ExpiresDefault 。它的優先級很低,只會在對應文件沒有任何其他規則能夠匹配(包括上層目錄下的緩存規則)時生效。

        <IfModule mod_expires.c>
    <FilesMatch "\.(css|js)$">
        ExpiresActive on 
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
</IfModule>
      

?

對某些文件設定

同理,也可以對某些文件啟用特定的緩存策略。注意,文件名中的點( . )是需要轉義的。

        <IfModule mod_expires.c>
    <FilesMatch "^(example\.css|example\.js)$">
        ExpiresActive on 
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
</IfModule>
      

?

對某一文件夾下的所有文件設定

對于靜態文件,一個比較方便的做法是將它們全部放到一個目錄下,并對該目錄下的所有文件設定。但是,此處需要注意防止其他規則將 ExpiresDefault 覆蓋掉。

        <IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 10 years"
</IfModule>
      

?

有用的工具及參考資料

  1. Cache-Control header checker (可檢測給定的地址是否啟用了 Cache-Control,還會教你如何設定)
  2. Caching Tutorial for Web Authors and Webmasters
  3. HTTP 緩存 - Web Fundamentals (來自 Google Developers)
  4. 百度站長工具-頁面優化建議
  5. h5bp/server-configs (HTML5 Boilerplate 提供了所有最流行的服務器的配置文件樣例)