2012年2月9日 星期四

Cache 教學手冊 [上]

原文網址:http://www.mnot.net/cache_docs/
譯註:原文擷取時間為 2011.12。原文內有許多內部連結,因 blog 特性的關係,故改連結至原文段落。

Cache 教學手冊
給網站作者與網站管理者
這是一個 informational document。雖然本質上是技術性文章,但它試圖讓你理解現實狀況下所涉及的概念。因此,為了保持文章脈絡的清晰,會簡化或省略某些內容的觀點。如果你對這些主題的細節感興趣,可以參考「延伸閱讀的參考資料」。
  1. web cache 是什麼?為甚麼要用它?
  2. web cache 的種類
    1. browser cache
    2. proxy cache
  3. web cache 不是好東西?為甚麼要支援 cache?
  4. web cache 如何運作?
  5. 如何控制 cache? 如何不用 cache?
    1. HTML 的 <meta> 與 HTTP 的 header
    2. 編寫 HTTP header(為甚麼會沒作用?)
    3. 用 Expires HTTP header 控制 freshness
    4. Cache-Control HTTP header
    5. validator 與 validation
  6. 建立一個 Cache-Aware 網站的秘訣
  7. 撰寫 Cache-Aware 程式
  8. 常見問題
  9. 實作筆記—Web Server
  10. 實作筆記—Server 端程式
  11. 延伸閱讀的參考資料
  12. 關於本文件
譯註:此篇 blog 文章只翻譯到「7. 撰寫 Cache-Aware 程式」

web cache 是什麼?為甚麼要用它?
一個 web cache 位於 web server(或稱 origin server)與 client 之間,web server 與 client 的數量可以是一個或多個。web cache 依 request 的內容自行儲存一份 response 的副本——像是 HTML 頁面、圖檔與檔案(統稱 representation)。如果之後有另一個相同 URL 的 request,web cache 就用自己儲存的那份資料作為 response,而不再次向 origin server 發出 request。

使用 web cache 有兩個主要的理由:

  • 減少延遲時間——因為 request 在比較靠近 client 的 cache 就處理掉了,而不是由 origin server 處理,這會減少取得 representation 與顯示的時間。這會讓 web 看起來反應速度更好。
  • 減少網路流量——因為 representation 被重複使用,這會減少 client 端頻寬的使用量。如果 client 的上網流量要付錢,這招可以省錢、並讓頻寬的需求降低、更容易管理。

web cache 的種類
browser cache
如果你仔細看一下現代 web browser(如 IE、Safari、Mozilla)的選項設定(preference),你會發現「cache」這個設定項目。這是讓你設定電腦中硬碟的某個部份,來儲存你已經看過的 representation,並僅供你自己使用。browser cache 的工作原理相當簡單,它會確保 representation(通常在一次的 session 當中,也就是當下 browser 啟動之後)是 fresh 的狀態。

當使用者點「上一頁」、或是按下他們剛剛才看過的連結,這種 cache 就非常有用。此外,如果你在整個網站都使用相同的導覽圖片,它們幾乎可以立即從 browser cache 當中取得。

proxy cache
網路上的 proxy cache 工作原理相同,但是規模大得多了。proxy 以相同方式提供服務給成千上百位使用者,大公司以及 ISP 常常在防火牆上設定 proxy、或是成為獨立運作的設備(又稱為 intermediary)。

因為 proxy cache 是在網路上、而不是 client 或 origin server 的一部分,所以 request 必須要用一些方法來傳送到那裡。其中一個方法就是手動設定 browser 的 proxy 設定,來告訴 browser 要用哪一個 proxy;另一種方法是 interception。interception proxy 是靠底層網路重新導向來取得 request,所以 client 不需要設定、甚至根本不知道有這東西存在。

proxy cache 是 share cache 的一種,通常有很大規模的使用者、而不是只有一個人在使用。這對減少延遲時間與網路流量方面是非常好的作法,因為熱門的 representation 會重複使用好幾次。

gateway cache
這也稱為「反向(reverse)proxy cache」或「surrogate cache」,gateway cache 也是 intermediary,但不是網路管理者佈署在網路上以節省頻寬,而是網站管理者自己架設、讓自己的網站能更有擴充性、提昇可靠度、以及更好的效能。

request 有好幾種方法可以轉送到 gateway cache,傳統上是透過負載平衡器(load balancer)讓 client 覺得一個或多個 gateway cache 看起來像 origin server。

content delivery network(CDN)將 gateway cache 分散到整個(或其中一小部份) internet 上,然後銷售 cache 給感興趣的網站。SpeederaAkamai 就是幾個 CDN 的例子。

這個教學手冊的重點在於 browser cache 與 proxy cache,雖然當中的資訊也是和對 gateway cache 有興趣的人。

web cache 不是好東西?為甚麼要支援 cache?
web cache 是 internet 上最被誤解的技術之一。網站管理者特別害怕自己的網站失去控制,因為 proxy cache 可以把使用者「隱藏」起來,很難知道誰在使用這個網站。

不幸的是,即使拿掉 web cache,there are too many variables on the Internet to assure that they'll be able to get an accurate picture of how users see their site(譯註:意思矛盾,故保留原文)。如果這是你擔心的部份,那這個教學手冊會教你如何取得你要的統計資料,而不是讓你的網站變成 cache-unfriendly。

另一個問題是 cache 可能提供過時的內容(名詞為 stale)。不過這個教學手冊可以告訴你如何設定你的 server 以控制你的內容被 cache 的方式。

CDN 是一個有趣的發展,因為有別於許多 proxy cache,它們的 gateway cache 是結合感興趣的網站作 cache,所以不會碰到這些問題。然而,即使你用了 CDN,你仍然要考慮到下游還有 proxy cache 與 browser cache。

反過來說,如果你把網站規劃的很好,cache 可以讓你的網站載入速度更快、並節省 server 與 internet 連接的負荷量。一個很難被 cache 的網站載入時間可能要花幾秒鐘,對比之下有利用到 cache 優點的網站幾乎瞬間就能看到,這之間的差距是很驚人的。使用者會較欣賞快速載入的網站、且更常造訪。

用另一個角度想,許多大的 internet 公司花費數百萬元在世界各地設立 server 農場,用來複製他們的內容好讓他們的使用者能更快存取到。對你而言,cache 做的事情相同、而且還更接近最終的使用者。更棒的是,你不用付錢給它們。

事實上,無論你喜不喜歡,proxy 與 browser cache 都會使用到。如果你沒有把網站設定成可以正確地被 cache,它將會用 cache administrator 預設的方式作 cache。

web cache 如何運作?
所有 cache 機制都有一套規則,來決定 cache 要不要以現有的 representation 作為回傳內容。其中一些規則是在通訊協定當中(HTTP 1.0 與 HTTP 1.1),有些透過 cache administrator(browser 的使用者、或是 proxy 的管理員)來設定。

一般來說,下面這些是最通用的原則(如果不了解細節也不用擔心,後頭會介紹):

  • 如果 response 的 header 要 cache 不要保存它,cache 就不會作用。
  • 如果 request 是認證過或是加密過(如 HTTPS),也不會被 cache
  • 滿足下列條件,一個已經被 cache 的 representation 被標為 fresh,會不向 origin server 核對、直接將 fresh representation 傳送給 client:
    • 它會有一個 expiry time 或是其他 age-controlling header 設定,並仍在 fresh 期間,或是......
    • 如果 cache 最近看過這個 representation,且在較之前的時間被修改過。
  • 如果 representation 是 stale,會要 origin server 對其作 validate、或是告訴 cache 這份副本還是可以用的。
  • 在某些情況下(例如網路斷線),cache 會提供 stale 的 response 而不向 origin server 核對。

如果沒有 validator(ETagLast-Modified 的 header)在 response 中出現,並且沒有任何清楚標示的 freshness 資訊,那通常(但不是永遠)會認定為不可 cache 的。

freshnessvalidation 是 cache 處理內容最重要的兩個方法。representation 如果是 fresh 就會馬上從 cache 中獲得,而 validate 的 representation 會在沒有改變的前提下避免重送。

如何控制 cache?如何不用 cache?
有許多方法可以讓網頁設計者與網站管理者微調網站被 cache 的方式。這可能要讓你動手設定 server,不過結果是值得的。關於設定 server 上的細節,請參照後頭的「實作筆記」。

HTML 的 <meta> 與 HTTP 的 header
編寫 HTML 的人可以在文件中 <head> 段落加上一些 tag 來描述這份文件的屬性。這些 meta tag 在使用上通常奠基在「可以把這份文件標記為不可 cache」或是「可以把這份文件標記為在一段時間後過期」的信念上。

meta tag 很容易使用,但不怎麼有效。因為這只對幾個(會實際去讀 HTML)browser cache 有效,對(幾乎不會去讀 HTML)proxy cache 無效。在網頁裡頭試圖放 Pragma:no-cache 的 meta tag 來讓這一頁保持 fresh 狀態,這招未必有用。

如果你的網站是由 ISP 或是虛擬主機商託管,他們不讓你自由設定 HTTP header(例如 ExpiresCache-Control),請用力抱怨這件事情,這對你的工作來說是必要的。

另一方面,HTTP header 讓你可以很容易去控制 browser cache 與 proxy cache 要如何處理你的 representation。HTTP header 在 HTML 當中看不到,通常是由 web server 自動產生。能控制到什麼程度取決於你使用的 server。在後頭的章節當中,你會看到 HTTP header 有趣的一面,以及如何在你的網站上使用。

server 會在 HTML 之前送出 HTTP header,只有 browser 以及所有 intermediate cache 會看到。典型 HTTP 1.1 版的 response 長得像這樣:
HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: "3e86-410-3596fbbc"
Content-Length: 1040
Content-Type: text/html
HTML 的內容會在一個空白行之後接續下去。設定 HTTP header 的方法,請參閱「實作筆記」。

Pragma HTTP header(為甚麼會沒作用?)
許多人認為在 representation 的 HTTP header 指定 Pragma: no-cache 會讓它無法被 cache。這不完全正確,HTTP 規格當中並沒有針對 Pragma response header 作任何規範,而是討論 Pragma request header(由 browser 發給 server)。雖然有少數的 cache 可能會實踐這個 header,但大多數不會、且不會產生任何影響。請使用下述的替代方式。

用 Expires HTTP header 控制 freshness
Expires 這個 HTTP header 是控制 cache 的基本手段,它告訴所有 cache 該份 representation 在多久時間內是 fresh 的。在那之後,cache 就一定會向 origin server 檢查文件是否有改變。幾乎每一種 cache 都支持 Expires 這個 header。

大多數 web server 允許你用一些方法設定 Expires response header。正常來說,它們會允許依照 client 最後一次取得 representation 的時間(最後 access time)、或是 server 上文件最後一次修改的時間(最後 modification time)為基準,設定一個過期的絕對時間。

Expires header 用在靜態圖檔(如導覽列與按鈕)使其可以被 cache 著,特別有用。因為它們通常沒啥改變,你可以設定很長的過期時間,讓你的使用者瀏覽起來的回應速度更快。這也適用於讓 cache 處理一個定期改變的頁面。舉例來說,如果你每天早上六點更新一個網頁,你可以設定這個 representation 在那時間過期,這樣 cache 就會知道什麼時候要來抓 fresh 的副本,而不用使用者去重新載入。

Expires header 唯一允許的值是 HTTP 的日期,其他的值都很可能被解釋為「過時了」,而該分 representation 也不會被 cache 住。此外,請記住 HTTP 日期裡的時間是格林威治時間(GMT),不是本地時間。

範例:
Expires: Fri, 30 Oct 1998 14:19:41 GMT

如果你使用 Expires header,請確認你的 web server 上時鐘是準確的,這很重要。其中一個方法是使用 Network Time Protocol(NTP),跟你的系統管理員談談可以了解更多。

雖然 Expires header 很有用,但也有一些局限性。首先,因為牽涉到日期,web server 與 cache 的時間必須同步,如果它們對時間有不同的想法,就無法達到預期的效果、cache 也可能將 fresh 的資料誤判成 stale。

另一個 Expires 的問題是,你很容易忘記哪些內容會在哪些時間到期。如果你沒有在過期之前更新 Expires 時間,每一個 request 還是會回到你的 web server,增加負荷與延遲時間。

Cache-Control HTTP header
HTTP 1.1 導入了 Cache-Control 這一個新的 header 類別,讓網頁出版者可以更能掌控他們的內容,並解決 Expires 的限制。

有效的 Cache-Control response header 包括下列這些東西:
  • max-age=(單位:秒):指定在最長為多少的時間內,該份 representation 會被認為是 fresh。和 Expires 相似,不過這個指令是相對於 request 的時間,而不是絕對時間。等號後方的值是你希望該份 representation 在 request 之後幾秒內會是 fresh 的狀態。
  • s-maxage=(單位:秒):與 max-age 相似,但它僅適用於 share cache(如 proxy)。
  • public:將認證過的 response 標明為可 cache。一般狀況下如果需要 HTTP 認證,response 會自動變成 private。
  • private:允許針對單一使用者的 cache(例如 browser cache)儲存 response,share cache(例如 proxy)則不存。
  • no-cache:要求 cache 在每一次發佈被 cache 住的副本之前,先向 origin server 發出 validation 的 request。這有助於確保「有遵循認證過程(結合 public)」與「維持嚴格的 fresh」,卻不損及 cache 的優點。
  • no-store:要 cache 不管在任何條件下都不保存 representation 的副本。
  • must-revalidate:告訴 cache 要遵從你給予這個 representation 任何 freshness 的資訊。HTTP 允許 cache 在某些特定條件下提供 stale 的 representation,透過指定這個 header 告訴 cache 要嚴格遵守你指定的規則。
  • proxy-revalidate:類似 must-revalidate,但僅適用於 proxy cache。
範例:
Cache-Control: max-age=3600, must-revalidate

如果你打算使用 Cache-Control header,你應該讀一下 HTTP 1.1 的文件,詳情參閱「延伸的閱讀資料」。

validator 與 validation
在「web cache 如何運作」的段落中,我們提到在 representation 改變的時候,server 與 client 溝通時會用到 validation。透過使用 validation,cache 可以在 local 端已經有一份副本、但不確定是不是仍然是 fresh 的情況下,避免下載整份 representation。

validator 是非常重要的,如果沒有 validator、也沒有任何 fresh 資訊(ExpiresCache-Control),cache 就不會儲存任何 representation。

最常見的 validator 是文件最後修改時間,用 Last-Modified header 傳遞。當 cache 儲存一個有  Last-Modified header 的 representation,它可以把這個時間值包在  If-Modified-Since header 中,向 server 確認該 representation 在最後一次造訪之後是否有改變。

HTTP 1.1 導入了一個新的、叫做 ETag 的 validator。ETag 是在每次 representation 改變時都會由 server 產生的唯一識別碼。因為 server 控制 ETag 的產生方式,cache 可以更確定在發出  If-None-Match request 而 ETag 又符合時,該份 representation 是相同的。

幾乎所有 cache 用 Last-Modified 時間來判斷 representation 是否 fresh;用 ETag validation 的方式也越來越普遍。

大多數現代的 web server 會自動針對靜態內容(例如檔案)同時產生 ETag 與  Last-Modified header 來作 validator,你啥都不用作。然而對於動態內容(像 CGI、ASP 或是 database 應用網站)卻不知道要如何產生的話,請參閱「撰寫 Cache-Aware 程式」。

建立一個 Cache-Aware 網站的秘訣
除了使用 freshness 資訊與 validation,還有一些方法可以讓你的網站 cache-friendly。

  • 用同樣的 URL:這是 cache 中的至理名言。如果你在不同的網頁、給不同的使用者、或是來自不同的網站,提供相同的內容,你應該要使用相同的 URL。這是最簡單、也最有效的方法讓你的網站 cache-friendly。舉例來說,如果一旦在 HTML 中使用「/index.html」這個 referenct,就一直用這個方法使用它。
  • Use a common library of images and other elements and refer back to them from different places.(譯註:翻譯不能... Orz)
  • 對不常改變的圖檔與網頁,把 Cache-Control: max-age header 設一個很大的值,讓 cache 儲存它們
  • 透過指定適當的 max-age 或是 expiration 時間,讓 cache 認得定期更新的頁面
  • 如果一個 resource(尤其是可下載的檔案)有改變,連它的名字也一起改。如此一來,你可以讓它在很久之後才過期、但依然確保可以提供正確的版本;只有連結到這個 resource 的頁面才需要設定很短的 expiry 時間。
  • 沒有需要就不要改檔案。如果你這樣作,所有東西都會有不正確、很新的 Last-Modified 日期。舉例來說,當你更新網站的時候,不要複製整個網站,只要把修改過的檔案放上去就好。
  • 必要時才使用 cookie——cookie 很難、大多數情況下也不需要 cache。如果你需要使用 cookie,將它限制用在 dynamic 頁面上。
  • 盡可能的減少使用 SSL——因為加密的頁面無法被 shared cache 儲存,只有在需要的時候用、並且謹慎地在 SSL 頁面上使用圖檔。
  • REDbot 檢查你的網頁——它可以幫助你應用這份文件中的許多概念。

撰寫 Cache-Aware 程式
預設情況下,大多數程式不會回傳 validator(Last-ModifiedETag 的 response header)或 freshness 資訊(ExpiresCache-Control)。雖然某些程式是 dynamic(每個 request 都會回傳不同的 response)的,但大多(像搜尋引擎或 database 應用網站)還是可以從「變成 cache-friendly」中獲益。

一般來說,如果程式產出的輸出與之後(無論幾分鐘還是幾天後)同樣 request 的輸出內容一樣,那就應該被 cache 住。如果程式所改變的內容取決於 URL,那可以 cache;如果輸出內容跟 cookie、認證資訊或其他外部標準有關,那大概不能 cache。

  • 讓程式變得 cache-firendly(同時也會改善效能)最好的方法,是在它改變時把內容 dump 成 plain 檔案。web server 可以像其他網頁一樣產生並使用 validator,讓你的生活更輕鬆。請記得:只有在變更的情況下寫入檔案,使 Last-Modified 時間可以保持更久。
  • 另一個有局限性、讓程式輸出可以 cache 的方法,是將 age 相關的 header 盡可能地設定遠一點的時間。雖然這可以用 Expires 做到,但用 Cache-Control: max-age 可能是最容易的方法、讓這個 request 之後還能保留一端時間的 refresh 狀態。
  • 如果你無法做到上述事情,你會需要讓程式產生 validator,然後回應 If-Modified-Since 以及/或者 If-Modified-Match request。這可以透過解析 HTTP header,然後在適當時機回應 304 Not Modified 來作到。不幸的是,這不是一件 trival 的工作。(譯註:原文就是 trival,意義不明)

一些其他技巧:

  • 除非時機適當,不然不要用 POST。大多數 cache 並不會儲存回應 POST 的 response,如果你用路徑或 query(GET)的方式送出資訊,cache 可以儲存這些資訊。
  • 不要在 URL 中嵌入使用者特定的資訊,除非產生的內容完全只針對該使用者。
  • 不要期望來自同一個使用者的所有 request 會來自同一個 host,因為 cache 通常會一起運作。
  • 產生 Content-Length 的 response header。這很容易做到,會讓程式的 response 變成一個 persistent connection。這可以讓 client 在一個 TCP/IP 的 connection 中 request 多個 representation,而不是每一個 request 都開一條 connection。這使你的網站看起來快得多。

更具體的資訊,請參閱「實作筆記


譯註:這篇文章翻譯過程十分痛苦,因此會不會有下集...... 再說吧 [炸]

3 則留言:

  1. 基本上不會有下集了,因為...

    1. 這篇文章有點舊了
    2. 下集是 FAQ 與 Implement Notes,FAQ 很零碎、Implement Notes 更顯得老舊(而且沒有 JSP container [怒])

    最後,只要有心,人人都可以翻譯......

    回覆刪除