對于您的站點的通路者來說,智能化的内容緩存是提高使用者體驗最有效的方式之一。緩存,或者對之前的請求的臨時存儲,是http協定實作中最核心的内容分發政策之一。分發路徑中的元件均可以緩存内容來加速後續的請求,這受控于對該内容所聲明的緩存政策。
在這份指南中,我們将讨論一些web内容緩存的基本概念。這主要包括如何選擇緩存政策以保證網際網路範圍内的緩存能夠正确的處理您的内容。我們将談一談緩存帶來的好處、副作用以及不同的政策能帶來的性能和靈活性的最大結合。

緩存(caching)是一個描述存儲可重用資源以便加快後續請求的行為的術語。有許多不同類型的緩存,每種都有其自身的特點,應用程式緩存和記憶體緩存由于其對特定回複的加速,都很常用。
這份指南的主要講述的web緩存是一種不同類型的緩存。web緩存是http協定的一個核心特性,它能最小化網絡流量,并且提升使用者所感覺的整個系統響應速度。内容從伺服器到浏覽器的傳輸過程中,每個層面都可以找到緩存的身影。
web緩存根據特定的規則緩存相應http請求的響應。對于緩存内容的後續請求便可以直接由緩存滿足而不是重新發送請求到web伺服器。
有效的緩存技術不僅可以幫助使用者,還可以幫助内容的提供者。緩存對内容分發帶來的好處有:
減少網絡開銷:内容可以在從内容提供者到内容消費者網絡路徑之間的許多不同的地方被緩存。當内容在距離内容消費者更近的地方被緩存時,由于緩存的存在,請求将不會消耗額外的網絡資源。
加快響應速度:由于并不是必須通過整個網絡往返,緩存可以使内容的獲得變得更快。緩存放在距使用者更近的地方,例如浏覽器緩存,使得内容的擷取幾乎是瞬時的。
在同樣的硬體上提高速度:對于儲存原始内容的伺服器來說,更多的性能可以通過允許激進的緩存政策從硬體上壓榨出來。内容擁有者們可以利用分發路徑上某個強大的伺服器來應對特定内容負載的沖擊。
網絡中斷時内容依舊可用:使用某種政策,緩存可以保證在原始伺服器變得不可用時,相應的内容對使用者依舊可用。
在面對緩存時,您可能對一些經常遇到的術語可能不太熟悉。一些常見的術語如下:
原始伺服器:原始伺服器是内容的原始存放地點。如果您是web伺服器管理者,它就是您所管理的機器。它負責為任何不能從緩存中得到的内容進行回複,并且負責設定所有内容的緩存政策。
緩存命中率:一個緩存的有效性依照緩存的命中率進行度量。它是可以從緩存中得到資料的請求數與所有請求數的比率。緩存命中率高意味着有很高比例的資料可以從緩存中獲得。這通常是大多數管理者想要的結果。
新鮮度:新鮮度用來描述一個緩存中的項目是否依舊适合傳回給用戶端。緩存中的内容隻有在由緩存政策指定的新鮮期内才會被傳回。
過期内容:緩存中根據緩存政策的新鮮期設定已過期的内容。過期的内容被标記為“陳舊”。通常,過期内容不能用于回複用戶端的請求。必須重新從原始伺服器請求新的内容或者至少驗證緩存的内容是否仍然準确。
校驗:緩存中的過期内容可以驗證是否有效以便重新整理過期時間。驗證過程包括聯系原始伺服器以檢查緩存的資料是否依舊代表了最近的版本。
失效:失效是依據過期日期從緩存中移除内容的過程。當内容在原始伺服器上已被改變時就必須這樣做,緩存中過期的内容會導緻用戶端發生問題。
還有許多其他的緩存術語,不過上面的這些應該能幫助您開始。
某些特定的内容比其他内容更容易被緩存。對大多數站點來說,一些适合緩存的内容如下:
logo和商标圖像
普通的不變化的圖像(例如,導航圖示)
css樣式表
普通的javascript檔案
可下載下傳的内容
媒體檔案
這些檔案更傾向于不經常改變,是以長時間的對它們進行緩存能獲得好處。
一些項目在緩存中必須加以注意:
html頁面
會替換改變的圖像
經常修改的javascript和css檔案
需要有認證後的cookies才能通路的内容
一些内容從來不應該被緩存:
與敏感資訊相關的資源(銀行資料,等)
使用者相關且經常更改的資料
除上面的通用規則外,通常您需要指定一些規則以便于更好地緩存不同種類的内容。例如,如果登入的使用者都看到的是同樣的網站視圖,就應該在任何地方緩存這個頁面。如果登入的使用者會在一段時間内看到站點中使用者特定的視圖,您應該讓使用者的浏覽器緩存該資料而不應讓任何中介節點緩存該視圖。
web内容會在整個分發路徑中的許多不同的位置被緩存:
浏覽器緩存:web浏覽器自身會維護一個小型緩存。典型地,浏覽器使用一種政策訓示緩存最重要的内容。這可能是使用者相關的内容或可能會再次請求且下載下傳代價較高。
中間緩存代理:任何在用戶端和您的基礎架構之間的伺服器都可以按期望緩存一些内容。這些緩存可能由isp(網絡服務提供者)或者其他獨立組織提供。
反向緩存:您的伺服器基礎架構可以為後端的服務實作自己的緩存。如果實作了緩存,那麼便可以在處理請求的位置傳回相應的内容而不用每次請求都使用後端服務。
上面的這些位置通常都可以根據它們自身的緩存政策和内容源的緩存政策緩存一些相應的内容。
緩存政策依賴于兩個不同的因素。所緩存的實體本身需要決定是否應該緩存可接受的内容。它可以隻緩存部分可以緩存的内容,但不能緩存超過限制的内容。
緩存行為主要由緩存政策決定,而緩存政策由内容擁有者設定。這些政策主要通過特定的http頭部來清晰地表達。
經過幾個不同http協定的變化,出現了一些不同的針對緩存方面的頭部,它們的複雜度各不相同。下面列出了那些你也許應該注意的:
**<code>expires</code>**:盡管使用範圍相當有限,但<code>expires</code>頭部是非常簡潔明了的。通常它設定一個未來的時間,内容會在此時間過期。這時,任何對同樣内容的請求都應該回到原始伺服器處。這個頭部或許僅僅最适合回退模式(fall back)。
**<code>cache-control</code>**:這是<code>expires</code>的一個更加現代化的替換物。它已被很好的支援,且擁有更加靈活的實作。在大多數案例中,它比<code>expires</code>更好,但同時設定兩者的值也無妨。稍後我們将讨論您可以設定的<code>cache-control</code>的詳細選項。
**<code>etag</code>**:<code>etag</code>用于緩存驗證。源伺服器可以在首次服務一個内容時為該内容提供一個獨特的<code>etag</code>。當一個緩存需要驗證這個内容是否即将過期,他會将相應的<code>etag</code>發送回伺服器。源伺服器或者告訴緩存内容是一緻的,或者發送更新後的内容(帶着新的<code>etag</code>)。
<code>last-modified</code>:這個頭部指明了相應的内容最後一次被修改的時間。它可能會作為保證内容新鮮度的驗證政策的一部分被使用。
**<code>content-length</code>**:盡管并沒有在緩存中明确涉及,<code>content-length</code>頭部在設定緩存政策時很重要。某些軟體如果不提前獲知内容的大小以留出足夠空間,則會拒絕緩存該内容。
**<code>vary</code>**:緩存系統通常使用請求的主機和路徑作為存儲該資源的鍵。當判斷一個請求是否是請求同樣内容時,<code>vary</code>頭部可以被用來提醒緩存系統需要注意另一個附加頭部。它通常被用來告訴緩存系統同樣注意<code>accept-encoding</code>頭部,以便緩存系統能夠區分壓縮和未壓縮的内容。
<code>vary</code>頭部提供給您存儲同一個内容的不同版本的能力,代價是降低了緩存的容量。
在使用<code>accept-encoding</code>時,設定<code>vary</code>頭部允許明确區分壓縮和未壓縮的内容。這在服務某些不能處理壓縮資料的浏覽器時很重要,它可以保證基本的可用性。<code>vary</code>的一個典型的值是<code>accept-encoding</code>,它隻有兩到三個可選的值。
一開始看上去<code>user-agent</code>這樣的頭部可以用于區分移動浏覽器和桌面浏覽器,以便您的站點提供差異化的服務。但<code>user-agent</code>字元串是非标準的,結果将會造成在中間緩存中儲存同一内容的許多不同版本的緩存,這會導緻緩存命中率的降低。<code>vary</code>頭部應該謹慎使用,尤其是您不具備在您控制的中間緩存中使請求标準化的能力(也許可以,比如您可以控制cdn的話)。
上面我們提到了<code>cache-control</code>頭部如何被用與現代緩存政策标準。能夠通過這個頭部設定許多不同的緩存指令,多個不同的指令通過逗号分隔。
一些您可以使用的訓示内容緩存政策的<code>cache-control</code>的選項如下:
<code>no-cache</code>:這個指令訓示所有緩存的内容在新的請求到達時必須先重新驗證,再發送給用戶端。這條指令實際将内容立刻标記為過期的,但允許通過驗證手段重新驗證以避免重新下載下傳整個内容。
<code>no-store</code>:這條指令訓示緩存的内容不能以任何方式被緩存。它适合在回複敏感資訊時設定。
<code>public</code>:它将内容标記為公有的,這意味着它能被浏覽器和其他任何中間節點緩存。通常,對于使用了http驗證的請求,其回複被預設标記為<code>private</code>。<code>public</code>标記将會覆寫這個設定。
<code>private</code>:它将内容标記為私有的。私有資料可以被使用者的浏覽器緩存,但不能被任何中間節點緩存。它通常用于使用者相關的資料。
<code>max-age</code>:這個設定訓示了緩存内容的最大生存期,它在最大生存期後必須在源伺服器處被驗證或被重新下載下傳。在現代浏覽器中這個選項大體上取代了<code>expires</code>頭部,浏覽器也将其作為決定内容的新鮮度的基礎。這個選項的值以秒為機關表示,最大可以表示一年的新鮮期(31536000秒)。
<code>s-maxage</code>:這個選項非常類似于<code>max-age</code>,它指明了内容能夠被緩存的時間。差別是這個選項隻在中間節點的緩存中有效。結合這兩個選項可以建構更加靈活的緩存政策。
<code>must-revalidate</code>:它指明了由<code>max-age</code>、<code>s-maxage</code>或<code>expires</code>頭部指明的新鮮度資訊必須被嚴格的遵守。它避免了緩存的資料在網絡中斷等類似的場景中被使用。
<code>proxy-revalidate</code>:它和上面的選項有着一樣的作用,但隻應用于中間的代理節點。在這種情況下,使用者的浏覽器可以在網絡中斷時使用過期内容,但中間緩存内容不能用于此目的。
<code>no-transform</code>:這個選項告訴緩存在任何情況下都不能因為性能的原因修改接收到的内容。這意味着,緩存不允許壓縮接收到的内容(沒有從原始伺服器處接收過壓縮版本的該内容)并發送。
這些選項能夠以不同的方式結合以獲得不同的緩存行為。一些互斥的值如下:
<code>no-cache</code>,<code>no-store</code>以及由其他前面未提到的選項指明的常用的緩存行為
<code>public</code>和<code>private</code>
如果<code>no-store</code>和<code>no-cache</code>都被設定,那麼<code>no-store</code>會取代<code>no-cache</code>。對于非授權的請求的回複,<code>public</code>是隐含的設定。對于授權的請求的回複,<code>private</code>選項是隐含的。他們可以通過在<code>cache-control</code>頭部中指明相應的相反的選項以覆寫。
在理想情況下,任何内容都可以被盡可能緩存,而您的伺服器隻需要偶爾的提供一些驗證内容即可。但這在現實中很少發生,是以您應該嘗試設定一些明智的緩存政策,以在長期緩存和站點改變的需求間達到平衡。
在許多情況中,由于内容被産生的方式(如根據每個使用者動态的産生)或者内容的特性(例如銀行的敏感資料),這些内容不應該被緩存。另一些許多管理者在設定緩存時可能面對的問題是外部緩存的資料未過期,但新版本的資料已經産生。
這些都是經常遇到的問題,它們會影響緩存的性能和您提供的資料的準确性。然而,我們可以通過開發提前預見這些問題的緩存政策來緩解這些問題。
盡管您的實際情況會指導您選擇的緩存政策,但是下面的建議能幫助您獲得一些合理的決定。
在您擔心使用哪一個特定的頭部之前,有一些特定的步驟可以幫助您提高您的緩存命中率。一些建議如下:
為圖像、css和共享的内容建立特定的檔案夾:将内容放到特定的檔案夾内使得您可以友善的從您的站點中的任何頁面引用這些内容。
使用同樣的url來表示同樣的内容:由于緩存使用内容請求中的主機名和路徑作為鍵,是以應保證您的所有頁面中的該内容的引用方式相同,前一個建議能讓這點更加容易做到。
盡可能使用css圖像拼接:對于像圖示和導航等内容,使用css圖像拼接能夠減少渲染您頁面所需要的請求往返,并且允許對拼接緩存很長一段時間。
盡可能将主機腳本和外部資源本地化:如果您使用javascript腳本和其他外部資源,如果上遊沒有提供合适的緩存頭部,那麼您應考慮将這些内容放在您自己的伺服器上。您應該注意上遊的任何更新,以便更新本地的拷貝。
對緩存内容收集檔案摘要:靜态的内容比如css和javascript檔案等通常比較适合收集檔案摘要。這意味着為檔案名增加一個獨特的标志符(通常是這個檔案的哈希值)可以在檔案修改後繞開緩存保證新的内容被重新擷取。有很多工具可以幫助您建立檔案摘要并且修改html文檔中的引用。
對于不同的檔案正确地選擇不同的頭部這件事,下面的内容可以作為一般性的參考:
允許所有的緩存存儲一般内容:靜态内容以及非使用者相關的内容應該在分發鍊的所有節點被緩存。這使得中間節點可以将該内容回複給多個使用者。
允許浏覽器緩存使用者相關的内容:對于每個使用者的資料,通常在使用者自己的浏覽器中緩存是可以被接受且有益的。緩存在使用者自身的浏覽器能夠使得使用者在接下來的浏覽中能夠瞬時讀取,但這些内容不适合在任何中間代理節點緩存。
将時間敏感的内容作為特例:如果您的資料是時間敏感的,那麼相對上面兩條參考,應該将這些資料作為特例,以保證過期的資料不會在關鍵的情況下被使用。例如,您的站點有一個購物車,它應該立刻反應購物車裡面的物品。依據内容的特點,可以在<code>cache-control</code>頭部中使用<code>no-cache</code>或<code>no-store</code>選項。
總是提供驗證器:驗證器使得過期的内容可以無需重新下載下傳而得到重新整理。設定<code>etag</code>和<code>last-modified</code>頭部将允許緩存向原始伺服器驗證内容,并在内容未修改時重新整理該内容新鮮度以減少負載。
對于支援的内容設定長的新鮮期:為了更加有效的利用緩存,一些作為支援性的内容應該被設定較長的新鮮期。這通常比較适合圖像和css等由使用者請求用來渲染html頁面的内容。和檔案摘要一起,設定延長的新鮮期将允許緩存長時間的存儲這些資源。如果資源發生改變,修改的檔案摘要将會使緩存的資料無效并觸發對新的内容的下載下傳。那時,新的支援的内容會繼續被緩存。
對父内容設定短的新鮮期:為了使得前面的模式正常工作,容器類的内容應該相應的設定短的新鮮期,或者設定不全部緩存。這通常是在其他協助内容中使用的html頁面。這個html頁面将會被頻繁的下載下傳,使得它能快速的響應改變。支援性的内容是以可以被盡量緩存。
關鍵之處便在于達到平衡,一方面可以盡量的進行緩存,另一方面為未來保留當改變發生時進而改變整個内容的機會。您的站點應該同時具有:
盡量緩存的内容
擁有短的新鮮期的緩存内容,可以被重新驗證
完全不被緩存的内容
這樣做的目的便是将内容盡可能的移動到第一個分類(盡量緩存)中的同時,維持可以接受的緩存命中率。
<a target="_blank"></a>
花時間確定您的站點使用了合适的緩存政策将對您的站點産生重要的影響。緩存使得您可以在保證服務同樣内容的同時減少帶寬的使用。您的伺服器是以可以靠同樣的硬體處理更多的流量。或許更重要的是,客戶們能在您的網站中獲得更快的體驗,這會使得他們更願意頻繁的通路您的站點。盡管有效的web緩存并不是銀彈,但設定合适的緩存政策會使您以最小的代價獲得可觀的收獲。
原文釋出時間:2015-05-17
本文來自雲栖合作夥伴“linux中國”