HTTP快取


Diff本文源自BOOK,不同於官方現有文件。本文在某些地方解釋得更深入、更細緻。因此我們沒有強行與官方同步。

富網絡應用程式的天然屬性是,它們是動態的。不管你的程式多有效率,每次請求都一直承受著遠遠大於靜態檔案的開銷。

而更多的web程序,並沒有受大的影響。 Symfony閃電般快,除非你在做一些超級重載,每一次請求都會很快恢復,而沒有把過多壓力留給伺服器。

但你的網站正在成長,過載有可能成為問題。針對通常請求的處理,只應完成一次。而這正是快取鎖定的目標。

緩存於巨人的肩膀 

改善一套程式的效能,最有效的方式是快取頁面的全部輸出,然後無視整個後續請求。當然,對於高動態網站而言,不可能總是這樣。本章,你將了解Symfony的快取系統是如何運作的,以及為何這是最佳方案。

Symfony快取系統與眾不同,因為它依賴的是HTTP specification所定義的HTTP cache之簡單與強大。有別於重新發明一套快取方法,Symfony強調的是定義了web基本通訊的標準。一旦你掌握了“HTTP驗證”,以及“快取models的過期”等基本知識,你已經可以去掌握Symfony的快取系統。

學習Symfony快取的過程,可分為四個步驟:

  1. #網關快取(gateway cache),或反向代理(reverse proxy),是位於你程式前面的獨立層。反向代理,快取的是回應,因為它們被你的程式回傳;還能在請求到達你的程式之前,透過快取的回應來回應請求。 Symfony提供了自己的反向代理,但任何反向代理都可以使用。

  2. HTTP cacheHTTP快取頭,在你的程式和客戶端之間,被用於同網關快取或其他快取進行通訊。 Symfony提供了合理的預設配置,以及強大的接口,用於與快取頭(cache headers)進行互動。

  3. HTTP過期與驗證(expiration and validation),這兩個模型被用來決定快取的內容是否新鮮/fresh(可從cache中復用),或是否陳舊/stale(應被程式重新產生)

  4. Edge Side Includes(ESI) ,邊緣端包容允許HTTP cache被用於頁面局部(甚至嵌套片段)的獨立快取。在ESI的幫助下,你甚至可以「快取整個頁面60分鐘,但側邊欄只快取5分鐘」。

由於HTTP cache並非Symfony專用,有許多相關文章。如果你對HTTP快取不太熟,強烈建議閱讀Ryan Tomayko的快取能做什麼(Things Caches Do)。另一個深度好文是Mark Nottingham的快取教學(Cache Tutorial)

使用Gateway Cache 

當使用HTTP快取時,cache是完全與你的程式分開的,它居於你的程式與發動請求的客戶端的之間。

快取的任務,就是接收客戶端請求,然後把它們再傳回你的程序,跟著推送回客戶端。這裡的快取是程式與瀏覽器之間的「請求-回應」通訊過程的「中間人」。

隨著時間推移,這些快取將儲存每一次被認為「可以快取(cacheable)」的回應(參考HTTP快取介紹)。如果相同的資源被再次要求,cache將發送快取了的回應至客戶端,完全無視你的程式。

這種類型的快取即是HTTP gateway cache(網關快取),存在於諸如Varnish反向代理模式下的Squid以及Symfony的反向代理之中。

快取類型 

但是Gateway快取並非唯一的快取類型。實際上,你的程式發送的HTTP快取頭,被假定於被最多三種方式的快取所解釋:

  • 瀏覽器快取(Browser caches) :每個瀏覽器都內建了自己的本地緩存,用於你點擊「回退」時使用,或用於圖片和其他assets資源。瀏覽器快取是私有(private)緩存,因為快取的資源不能被其他人使用;

  • 代理快取(Proxy caches) :代理,是指共享(shared)緩存,因為很多人可以跟在某個人的後面(來使用)。通常由大公司或ISP所使用,以減少存取延遲和網路流量。

  • 網關快取(Gateway caches):類似代理,它也是共用緩存,但卻在伺服器端。常為網路管理員所用,令網站更易升級、更可靠、效能更高。

Gateway caches有時特別指反向代理緩存,surrogate caches(代理快取),甚至HTTP加速器。

當快取的回應包含了某個特定使用者的內容(例如帳號資訊)這種情況被討論時,私有(private)快取和共享(shared)快取的重要性與日俱增。

#

程式的每一次回應,將會經歷前兩種快取類型中的一種或兩種。這些快取是在你的(程式)控制之外,卻遵守回應中設定好的HTTP快取的指令。

Symfony反向代理程式(Reverse Proxy) 

#Symfony內建了用PHP寫的反向代理(也稱為gateway快取)。 它並非Varnish這種全功能的反向代理快取,但卻是一個很好的開始。

關於Varnish設定的更多細節,參考 如何使用Varnish加速我的網站

開啟代理程式很容易:Symfony程式都預先建立了一個cache kernel快取核心(AppCache),它把預設的核心(AppKernel)給打包。這個快取核心就是 反向代理。

開啟快取很容易,修改你的前端控制器程式碼。你也可以在app_dev.php中做出這些改變,即可為dev環境添加快取:

// web/app.phpuse Symfony\Component\HttpFoundation\Request;
// ...$kernel = new AppKernel('prod', false);$kernel->loadClassCache(); 
// add (or uncomment) this new line! / 添加下面新行!
// wrap the default AppKernel with the AppCache one
// 用AppCache打包默认的AppKernel$kernel = new AppCache($kernel); $request = Request::createFromGlobals(); $response = $kernel->handle($request);$response->send(); $kernel->terminate($request, $response);

上面的快取核心,將立即作為反向代理來運作-從你的程式快取回應,然後把它們回到客戶端。

如果你正在使用framework.http_method_override選項,來從_method參數讀取HTTP方法,參考上面連結來調整到你需要的程度。

快取核心有一個特殊的getLog()方法,傳回一個字串,用以表示快取層中到底發生了什麼。在開發環境下,可以使用它來調試,或驗證你的快取策略。

1
#
error_log($kernel->getLog());

AppCache物件有一個適當的預設配置,但透過覆寫getOptions()方法來設定一組選項,該物件即可被精細調整。

// app/AppCache.phpuse Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; class AppCache extends HttpCache{
    protected function getOptions()
    {
        return array(
            'debug'                  => false,
            'default_ttl'            => 0,
            'private_headers'        => array('Authorization', 'Cookie'),
            'allow_reload'           => false,
            'allow_revalidate'       => false,
            'stale_while_revalidate' => 2,
            'stale_if_error'         => 60,
        );
    }}


除非在getOptions()方法中進行覆寫,否則debub選項將自動設為「被剝離出來的AppKernel」中的debug值。


以下是一些主要選項:

default_ttl

數值是秒,表示的是當回應中沒有提供明確的新鮮度資訊時,一個快取入口被認為是fresh的長度。明確指定Cache-ControlExpires頭,可以覆寫這個值(預設是0)。


private_headers

#一組請求頭,在沒有「透過Cache-Control指令(預設為AuthorizationCookie)明確聲明目前回應是public還是private狀態」的回應中,觸發「private」Cache -Control行為。


allow_reload

#指定客戶端是否可以在請求中包容一個Cache-Control的「no -cache”指令來強制重新載入快取。設為true即可遵守RFC2616(預設為false)。


allow_revalidate

#指定客戶端是否可以在請求中包容一個來Cache-Control的“ max-age=0”來強制重新驗證。設為true即可遵守RFC2616(預設為false)。


stale_while_revalidate

指定的預設秒數(以秒為間隔是因為Response的TTL精確度是秒),在此期間,儘管快取在後台對回應正進行重新驗證,但它能夠立即傳回一個不新鮮的回應(預設值是2);本設定可被HTTPCache-Control擴充的stale-while-revalidate覆蓋(參考RFC 5861)。


stale_if_error

#指定的預設秒數(間隔為秒),在此期間,快取可以對遇到錯誤的回應提供服務(預設值是60)。本設定可被HTTPCache-Control擴充的stale-if-error覆寫(參考RFC 5861)。


如果debug被設為true,Symfony將自動添加一個X-Symfony-Cache頭到回應中,在裡面有關於緩存命中和丟失的有用資訊。

從一個反向代理切換到另一個

在開發網站時,或者在部署網站到「除了php程式碼什麼都不能安裝」的共用主機過程中,Symfony的反向代理是個極為好用的工具。但由於是用PHP寫成,它不如用C寫的代理快。這也是為什麼強烈建議你在生產環境的伺服器上盡可能地使用Vanish或Squid。好消息是,從一個代理伺服器切換到另一個是很容易的,而且過程透明,因為你的程式中並沒有程式碼要修改。你可以安心使用Symfony反向代理,日後訪問量增長時可以隨時升級到Varnish。

#

Symfony反向代理的效能是獨立於程式複雜程度之外的。這是因為程式核心只在「request需要被傳送給它」時才會啟動。

令你的回應成為HTTP快取 

#為了利用可用的快取層,你的程式應該與以下資訊進行通訊:1 、哪些響應可被緩存。 2、能夠決定快取「何時/如何變成不新鮮」的規則。

記得,「HTTP」就是一種語言(簡單文字)而已,被客戶端和和伺服器用來進行相互通訊之用。而HTTP快取就是這種語言的一部分,允許客戶端和伺服器交換關於快取的資訊。

HTTP指定了以下四個用於回應的快取頭:

  • Cache-Control

  • #Expires

  • #ETag

  • Last -Modified

其中最重要且功能最強的當屬Cache-Control頭,它可說是多種快取資訊的集合。

每種頭都在HTTP Expiration,Validation和Invalidation小節中進行了詳解。

Cache-Control頭 

#Cache-Control頭是特殊的,它包含不只一條,而是很多條和響應的快取能力相關的資訊。每個資訊被以英文逗號分隔開來:

Cache-Control: private, max-age=0, must-revalidate
 
Cache-Control: max-age=3600, must-revalidate

Symfony提供了一個關於Cache-Control頭的抽象層,以便令它的創建更加易於管理:

// ... use Symfony\Component\HttpFoundation\Response; $response = new Response();
// mark the response as either public or private 标记响应是公有还是私有$response->setPublic();$response->setPrivate();
// set the private or shared max age 设置私有或公有的最大周期$response->setMaxAge(600);$response->setSharedMaxAge(600); 
// set a custom Cache-Control directive 设置一个自定义Cache-Control命令$response->headers->addCacheControlDirective('must-revalidate', true)

如果你要為控制器中不同的action設定快取頭,你或許需要看看FOSHttpCacheBundle。它提供了一種基於URL模式匹配和其他請求屬性的方式來定義快取頭。

#

Public回應和Private回應 

不管是gateway還是proxy緩存,都被認為是「shared」共享緩存,因為快取內容被更多用戶分享。如果一個「特定用戶專有」回應被錯誤地置於共享快取中,它可能在後面的時間裡被傳回給多位不同用戶。試想你的帳號資訊被緩存,然後發送給所有後續請求了自己帳號頁面的用戶是個什麼場面!

為應對這種情形,每一個回應應設為public或private:

public

##指示回應應該同時緩存為public和private快取。


private

指示所有或部分回應資訊僅針對某一用戶,因此禁止快取為public快取。


Symfony保守的設定每一次回應為private。為了利用好共享快取(例如Symfony反向代理),回應必須明確設定為public。

安全方法(Safe Method) 

HTTP快取只工作在「安全性」HTTP方法下(例如GET或HEAD)。所謂安全,是指你在對請求提供服務時(諸如記錄日誌,處理快取資訊等)永遠無法改變伺服器上的程式狀態。這就產生兩個極為有說服力的重要結論:

  • 你永遠不應該在GET或HEAD請求的回應中改變程式狀態。就算你不使用gateway cache,然而代理快取的本質是,任何GET或HEAD請求,可能或並沒有真正hit到你的伺服器;

  • 不要預期對PUT、POST或DELETE方法進行快取。這些方法意味著被用於你的程式狀態改變時(例如刪除一篇部落格)。快取它們將阻止特定的請求命中或改變你的程式。

快取規則和預設設定 

#HTTP1.1允許預設快取任何內容,除非明確指定了

Cache-Control 頭。實務中,多數快取在請求中包含cookie時、包含authorization頭時、使用了一個非安全方法時(例如PUT、POST或DELETE)或當回應有一個重定向狀態碼時,什麼也不做。

當開發者在回應頭中什麼也沒設定時,Symfony依據以下規則,自動設定了有意義的而且是偏保守的

Cache-Header頭。

  • 如果沒有快取頭資訊被定義(

    Cache-ControlExpiresETagLast -Modified),Cache-Control將設為no-cache,代表回應將不被快取;

  • ##如果
  • Cache-Control

    是空(但是另一個快取頭有被設定),其值將設為private, must-revalidate;

  • 但是如果至少有一個Cache-Control指令被設置,而且沒有publicprivate指令被明確添加的話,Symfony會自動加入private指令(除了當s-maxage被設定時)

HTTP Expiration,Validation和Invalidation # ¶

HTTP協定定義了兩個快取模型:

  • #利用expiration model(過期模型),透過包容 Cache-Control頭和/或Expires頭,即可直接指定一個回應應該被認為「新鮮」的時長。快取能夠理解過期時間,不再製造相同請求,直到該快取版本抵達過期時間,而且變得「不新鮮(stale)」。

  • 當頁面是真動態時(展現層經常改變),則validation model(驗證模型)的使用就十分有必要。利用這個模型,快取把回應儲存起來,但會在每次請求時向伺服器「提問」——是否快取了的回應仍然有效?程式使用了一個獨立的回應辨識器(即Etag頭)和/或一個時間戳記(即Last-Modified頭),來檢查目前頁面自被快取之後,是否發生了改變。

理解HTTP Specification

#HTTP specification定義了一個簡單但卻強大的語言,可以讓客戶端和伺服器進行通訊。身為web開發者,HTTP specification所擁有的request-response model(「請求-回應」模型)將支配你的快取工作。不幸的是,HTTP協定的真正文件:RFC2616,是難以讀懂的。

不過有一個正在進行中的HTTP Bis要覆寫RFC 2616。它並沒有描述新版本的HTTP,更多的是對原有HTTP協定進行疏理。文件的組織結構也隨著HTTP協定被分成七個部分而有所改進;關於HTTP caching的每一部分,都可以在以下兩個獨立章節找到(P4 - Conditional RequestsP6 - Caching: Browser and intermediary caches)。

身為web開發者,你被我們Symfony官方團隊 最強力勸 來閱讀HTTP協定相關內容。它是如此明晰與強大——即使是在它被創造出來的10年之後——Http Specification是無價的。我們特別提醒您,千萬別對這些協議的表象敷衍了事——其內容之美麗,百千萬億倍於其封面。

#

Expiration(過期) 

expiration model,是兩個快取模型裡效率更高、更直接的一個,因此應該被盡可能多地使用。當一個回應透過expiration被快取時,快取將保存回應,並且在過期之前直接返回它,而毋須命中程式。

過期模型,可以透過以下幾乎一樣的兩種HTTP頭之一來實現:ExpiresCache-Control

使用Expires頭控制過期 

根據HTTP specification,「Expires頭字段將在response被認為是stale之後給出date/時間。」。這裡的Expires頭可以被設為Response方法:setExpires()。它使用DateTime實例作為參數:

$date = new DateTime();
$date->modify('+600 seconds'); 
$response->setExpires($date);

該回應的HTTP頭資訊類似這種:

Expires: Thu, 01 Mar 2011 16:00:00 GMT

setExpires()方法將自動轉換日期為GMT時區,因為這是HTTP specification的要求。

注意,在HTTP 1.1版之前,並不需要原始伺服器來傳送Date頭。因此,快取(例如瀏覽器的)就需要本地時鐘來評估Expires頭,進而令快取週期的計算因時間傾斜而變得脆弱不堪。另外一個Expires頭限制是,正如HTTP協定中所描述的,「HTTP/1.1 不得發送Expires的日期超過一年。」

使用Cache- Control頭控制過期 

因為Expires頭的限制,多數情況下,你應該使用Cache-Control頭來替代。記得,Cache-Control頭被用於多種不同的快取指令。例如,max-ages-maxage。第一個用於全部緩存,而第二個僅在共享緩存時用到。

// Sets the number of seconds after which the response
// should no longer be considered fresh// 设置“响应过期”的秒数$response->setMaxAge(600);
 // Same as above but only for shared caches
// 同上,但仅用于共享缓存$response->setSharedMaxAge(600);

Cache-Control頭一般是下述格式(但有時會有其他指令):

1
Cache-Control: max-age=600, s-maxage=600
#

過期與驗證(Expiration and Validation) 

#你當然可以對同一個Response同時使用validation和expiration。因為expiration的優勢大過validation,你能很容易地從兩個世界中好的一面受益。也就是說,同時使用過期和驗證,你可以命令快取來服務已快取的內容,同時還能在某些區間(expiration)向後檢查以確認快取內容仍然有效。

你也可以透過annotation來為expiration和validation去定義HTTP快取頭。參考FrameworkExtraBundle文件。

更多Response方法 

#Response類別提供了很多方法來回應快取。以下是幾個特別有用的:

// Marks the Response stale 标记响应过期$response->expire(); 
// Force the response to return a proper 304 response with no content
// 强制响应返回一个没有内容的恰当的304响应$response->setNotModified();

另外,多數與快取相關的HTTP頭可以單獨使用setCache()方法來完成設定:

// Set cache settings in one call$response->setCache(array(
    'etag'          => $etag,
    'last_modified' => $date,
    'max_age'       => 10,
    's_maxage'      => 10,
    'public'        => true,
    // 'private'    => true,
    ));

總結 

Symfony的設計思想即是遵循業界公認標準:HTTP。快取功能也不例外。掌握Symfony的快取系統意味著你已然熟悉了HTTP cache模型並且能夠有效率地使用它。換句話說,毋須依賴Symfony文件和例程,你可以馳騁於HTTP caching和以Varnish為代表的gateway caches的世界。