HTTP快取
Diff本文源自BOOK,不同於官方現有文件。本文在某些地方解釋得更深入、更細緻。因此我們沒有強行與官方同步。
富網絡應用程式的天然屬性是,它們是動態的。不管你的程式多有效率,每次請求都一直承受著遠遠大於靜態檔案的開銷。
而更多的web程序,並沒有受大的影響。 Symfony閃電般快,除非你在做一些超級重載,每一次請求都會很快恢復,而沒有把過多壓力留給伺服器。
但你的網站正在成長,過載有可能成為問題。針對通常請求的處理,只應完成一次。而這正是快取鎖定的目標。
緩存於巨人的肩膀 ¶
改善一套程式的效能,最有效的方式是快取頁面的全部輸出,然後無視整個後續請求。當然,對於高動態網站而言,不可能總是這樣。本章,你將了解Symfony的快取系統是如何運作的,以及為何這是最佳方案。
Symfony快取系統與眾不同,因為它依賴的是HTTP specification所定義的HTTP cache之簡單與強大。有別於重新發明一套快取方法,Symfony強調的是定義了web基本通訊的標準。一旦你掌握了“HTTP驗證”,以及“快取models的過期”等基本知識,你已經可以去掌握Symfony的快取系統。
學習Symfony快取的過程,可分為四個步驟:
#網關快取(gateway cache),或反向代理(reverse proxy),是位於你程式前面的獨立層。反向代理,快取的是回應,因為它們被你的程式回傳;還能在請求到達你的程式之前,透過快取的回應來回應請求。 Symfony提供了自己的反向代理,但任何反向代理都可以使用。
HTTP cacheHTTP快取頭,在你的程式和客戶端之間,被用於同網關快取或其他快取進行通訊。 Symfony提供了合理的預設配置,以及強大的接口,用於與快取頭(cache headers)進行互動。
HTTP過期與驗證(expiration and validation),這兩個模型被用來決定快取的內容是否新鮮/fresh(可從cache中復用),或是否陳舊/stale(應被程式重新產生)
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-Control
或Expires
頭,可以覆寫這個值(預設是0
)。
private_headers
#一組請求頭,在沒有「透過Cache-Control
指令(預設為Authorization
和Cookie
)明確聲明目前回應是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
頭到回應中,在裡面有關於緩存命中和丟失的有用資訊。
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和private快取。public
指示所有或部分回應資訊僅針對某一用戶,因此禁止快取為public快取。private
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)或當回應有一個重定向狀態碼時,什麼也不做。
Cache-Header頭。
- 如果沒有快取頭資訊被定義(
Cache-Control
、
Expires、
ETag或
Last -Modified),
Cache-Control將設為
no-cache,代表回應將不被快取;
##如果 - Cache-Control
是空(但是另一個快取頭有被設定),其值將設為
private, must-revalidate
; 但是如果至少有一個
Cache-Control
指令被設置,而且沒有public
或private
指令被明確添加的話,Symfony會自動加入private
指令(除了當s-maxage
被設定時)
HTTP Expiration,Validation和Invalidation # ¶
HTTP協定定義了兩個快取模型:
#利用expiration model(過期模型),透過包容
Cache-Control
頭和/或Expires
頭,即可直接指定一個回應應該被認為「新鮮」的時長。快取能夠理解過期時間,不再製造相同請求,直到該快取版本抵達過期時間,而且變得「不新鮮(stale)」。當頁面是真動態時(展現層經常改變),則validation model(驗證模型)的使用就十分有必要。利用這個模型,快取把回應儲存起來,但會在每次請求時向伺服器「提問」——是否快取了的回應仍然有效?程式使用了一個獨立的回應辨識器(即
Etag
頭)和/或一個時間戳記(即Last-Modified
頭),來檢查目前頁面自被快取之後,是否發生了改變。
Expiration(過期) ¶
expiration model,是兩個快取模型裡效率更高、更直接的一個,因此應該被盡可能多地使用。當一個回應透過expiration被快取時,快取將保存回應,並且在過期之前直接返回它,而毋須命中程式。
過期模型,可以透過以下幾乎一樣的兩種HTTP頭之一來實現:Expires
或Cache-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-age
和s-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的世界。