HTTP緩存
Diff本文源自BOOK,不同于官方現(xiàn)有文檔。本文在某些地方解釋得更深入、更細致。因此我們沒有強行與官方同步。
富網(wǎng)絡(luò)應(yīng)用程序的天然屬性是,它們是動態(tài)的。不管你的程序多有效率,每次請求始終承受著遠遠大過靜態(tài)文件的開銷。
而更多的web程序,并沒有受大的影響。Symfony閃電般快,除非你在做一些超級重載,每一次請求都會很快恢復(fù),而沒有把過多壓力留給服務(wù)器。
但你的網(wǎng)站在成長,過載有可能成為問題。針對通常請求的處理,只應(yīng)完成一次。而這正是緩存鎖定的目標。
緩存于巨人的肩膀 ?
改善一套程序的性能,最有效的方式是緩存頁面的全部輸出,然后無視整個后續(xù)請求。當(dāng)然,對于高動態(tài)網(wǎng)站而言,不可能總是這樣。本章,你將了解Symfony的緩存系統(tǒng)是如何運作的,以及為何這是最佳方案。
Symfony緩存系統(tǒng)與眾不同,因為它依靠的是HTTP specification所定義的HTTP cache之簡單與強大。不同于重新發(fā)明一套緩存方法,Symfony強調(diào)的是定義了web基本通信的標準。一旦你掌握了“HTTP驗證”,以及“緩存models的過期”等基本知識,你已經(jīng)可以去掌握Symfony的緩存系統(tǒng)。
學(xué)習(xí)Symfony緩存的過程,可分為四個步驟:
網(wǎng)關(guān)緩存(gateway cache),或者反向代理(reverse proxy),是位于你程序前面的獨立層。反向代理,緩存的是響應(yīng),因為它們被你的程序返回;還能在請求到達你的程序之前,通過緩存的響應(yīng)來回應(yīng)請求。Symfony提供了自己的反向代理,但是任何反向代理都可以使用。
HTTP cacheHTTP緩存頭,在你的程序和客戶端之間,被用于同網(wǎng)關(guān)緩存或其他緩存進行通信。Symfony提供了合理的默認配置,以及強大的接口,用于與緩存頭(cache headers)進行互動。
HTTP過期與驗證(expiration and validation),這兩個模型被用于決定緩存的內(nèi)容是否新鮮/fresh(可從cache中復(fù)用),或者是否陳舊/stale(應(yīng)當(dāng)被程序重新生成)
Edge Side Includes(ESI),邊緣端包容允許HTTP cache被用于頁面局部(甚至嵌套片段)的獨立緩存。在ESI的幫助下,你甚至可以“緩存整個頁面60分鐘,但側(cè)邊欄只緩存5分鐘”。
由于HTTP cache并非Symfony專用,有很多相關(guān)文章。如果你對HTTP緩存不太熟,強烈推薦閱讀Ryan Tomayko的緩存能做什么(Things Caches Do)。另一個深度好文是Mark Nottingham的緩存教程(Cache Tutorial)。
使用Gateway Cache ?
當(dāng)用HTTP緩存時,cache是完全與你的程序分開的,它居于你的程序與發(fā)動請求的客戶端的之間。
緩存的任務(wù),就是接收客戶端請求,然后把它們再傳回你的程序,跟著推送回客戶端。這里的緩存是程序與瀏覽器之間的“請求-響應(yīng)”通信過程的“中間人”。
隨著時間推移,這些緩存將存儲每一次被認為“可以緩存(cacheable)”的響應(yīng)(參考HTTP緩存介紹)。如果相同的資源被再次請求,cache將發(fā)送緩存了的響應(yīng)至客戶端,完全無視你的程序。
這種類型的緩存即是HTTP gateway cache(網(wǎng)關(guān)緩存),存在于諸如Varnish、反向代理模式下的Squid以及Symfony的反向代理之中。
緩存類型 ?
但是Gateway緩存并非唯一的緩存類型。實際上,你的程序發(fā)送的HTTP緩存頭,被假定于被最多三種方式的緩存所解釋:
瀏覽器緩存(Browser caches):每個瀏覽器都內(nèi)置了自己的本地緩存,用于你點擊“回退”時使用,或者用于圖片和其他assets資源。瀏覽器緩存是私有(private)緩存,因為緩存的資源不能被其他人使用;
代理緩存(Proxy caches):代理,是指共享(shared)緩存,因為很多人可以跟在某個人的后面(來使用)。通常被大公司或ISP所使用,以減低訪問延遲和網(wǎng)絡(luò)流量。
網(wǎng)關(guān)緩存(Gateway caches):類似代理,它也是共享緩存,但卻是在服務(wù)器端。常為網(wǎng)絡(luò)管理員所用,令網(wǎng)站更易升級、更可靠、性能更高。
Gateway caches有時特指反向代理緩存,surrogate caches(代理緩存),甚至HTTP加速器。
當(dāng)緩存的響應(yīng)包含了某個特定用戶的內(nèi)容(比如賬號信息)這種情況被討論時,私有(private)緩存和共享(shared)緩存的重要性與日俱增。
程序的每一次響應(yīng),將會經(jīng)歷前兩種緩存類型中的一種或兩種。這些緩存是在你的(程序)控制之外,卻遵守響應(yīng)中設(shè)置好的HTTP緩存的指令。
Symfony反向代理(Reverse Proxy) ?
Symfony內(nèi)置了用PHP寫的反向代理(也被稱為gateway緩存)。它并非Varnish這種全功能的反向代理緩存,但卻是一個很好的起步。
關(guān)于Varnish設(shè)置的更多細節(jié),參考 如何使用Varnish加速我的網(wǎng)站。
開啟代理很容易:Symfony程序都預(yù)建了一個cache kernel緩存核心(AppCache
),它把默認的核心(AppKernel
)給打包。這個緩存核心就是 反向代理。
開啟緩存很容易,修改你的前端控制器代碼。你也可以在app_dev.php
中做出這些改變,即可為dev
環(huán)境添加緩存:
// 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);
上面的緩存核心,將立即作為反向代理來運作——從你的程序中緩存響應(yīng),然后把它們返回到客戶端。
如果你正使用framework.http_method_override選項,來從_method
參數(shù)中讀取HTTP方法,參考上面鏈接來調(diào)整到你需要的程度。
緩存核心有一個特殊的getLog()
方法,返回一個字符串,用以表明緩存層中到底發(fā)生了什么。在開發(fā)環(huán)境下,可以使用它來除錯,或者驗證你的緩存戰(zhàn)略。
1 | error_log($kernel->getLog()); |
AppCache
對象有一個合適的默認配置,但是通過覆寫getOptions()
方法來設(shè)置一組選項,該對象即可被精細調(diào)整。
// 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
選項將被自動設(shè)成“被剝離出來的AppKernel
”中的debug值。
下面是一些主要選項:
default_ttl
數(shù)值是秒,表達的是當(dāng)響應(yīng)中沒有提供明確的新鮮度信息時,一個緩存入口被認為是fresh的時長。顯式指定Cache-Control
或Expires
頭,可以覆寫這個值(默認是0
)。
private_headers
一組請求頭,在沒有“通過Cache-Control
指令(默認是Authorization
和Cookie
)明確聲明當(dāng)前響應(yīng)是public
還是private
狀態(tài)”的響應(yīng)中,觸發(fā)“private”Cache-Control
行為。
allow_reload
指定客戶端是否可以在請求中包容一個Cache-Control
的“no-cache”指令來強制重新加載緩存。設(shè)為true
即可遵守RFC2616(默認是false
)。
allow_revalidate
指定客戶端是否可以在請求中包容一個來Cache-Control
的“max-age=0”來強制重新驗證。設(shè)為true
即可遵守RFC2616(默認是false
)。
stale_while_revalidate
指定的默認秒數(shù)(以秒為間隔是因為Response的TTL精度是秒),在此期間,盡管緩存在后臺對響應(yīng)正進行重新驗證,但它能夠立即返回一個不新鮮的響應(yīng)(默認值是2
);本設(shè)置可被HTTPCache-Control
擴展的stale-while-revalidate
覆寫(參考RFC 5861)。
stale_if_error
指定的默認秒數(shù)(間隔是秒),在此期間,緩存可以對遇到錯誤的響應(yīng)提供服務(wù)(默認值是60
)。本設(shè)置可被HTTPCache-Control
擴展的stale-if-error
覆寫(參考RFC 5861)。
如果debug
被設(shè)為true
,Symfony將自動添加一個X-Symfony-Cache
頭到響應(yīng)中,里面有關(guān)于緩存命中和丟失的有用信息。
Symfony反向代理的性能是獨立于程序復(fù)雜程度之外的。這是因為程序內(nèi)核只在“request需要被發(fā)送給它”時才會啟動。
令你的響應(yīng)成為HTTP緩存 ?
為了利用可用的緩存層,你的程序應(yīng)該與以下信息進行通信:1、哪些響應(yīng)可被緩存。2、能夠決定緩存“何時/如何變成不新鮮”的規(guī)則。
記得,“HTTP”就是一種語言(簡單文本)而已,被客戶端和和服務(wù)器用來進行相互通信之用。而HTTP緩存就是這種語言的一部分,允許客戶端和服務(wù)器交換關(guān)于緩存的信息。
HTTP指定了以下四種用于響應(yīng)的緩存頭:
Cache-Control
Expires
ETag
Last-Modified
其中最為重要和功能最強的當(dāng)屬Cache-Control
頭,它可說是多種緩存信息的集合。
每種頭都在HTTP Expiration,Validation和Invalidation小節(jié)中進行了詳解。
Cache-Control頭 ?
Cache-Control
頭是特殊的,它包含不止一條,而是很多條和響應(yīng)的緩存能力相關(guān)的信息。每種信息被以英文逗號分隔開來:
Cache-Control: private, max-age=0, must-revalidate Cache-Control: max-age=3600, must-revalidate
Symfony提供了一個關(guān)于Cache-Control
頭的抽象層,以便令它的創(chuàng)建更加易于管理:
// ... use Symfony\Component\HttpFoundation\Response; $response = new Response(); // mark the response as either public or private 標記響應(yīng)是公有還是私有$response->setPublic();$response->setPrivate(); // set the private or shared max age 設(shè)置私有或公有的最大周期$response->setMaxAge(600);$response->setSharedMaxAge(600); // set a custom Cache-Control directive 設(shè)置一個自定義Cache-Control命令$response->headers->addCacheControlDirective('must-revalidate', true)
如果你要為控制器中不同的action設(shè)置緩存頭,你也許需要看看FOSHttpCacheBundle。它提供了一種基于URL模式匹配和其他請求屬性的方式來定義緩存頭。
Public響應(yīng)和Private響應(yīng) ?
不管是gateway還是proxy緩存,都被認為是“shared”共享緩存,因為緩存內(nèi)容被更多用戶分享。如果一個“特定用戶專有”響應(yīng)被錯誤地置于共享緩存中,它可能在后面的時間里被返回給多位不同用戶。試想你的賬號信息被緩存,然后發(fā)送給所有后續(xù)請求了自己賬號頁面的用戶是個什么場面!
為應(yīng)對這種情形,每一個響應(yīng)應(yīng)當(dāng)被設(shè)為public或private:
public
指示響應(yīng)應(yīng)該被同時緩存為public和private緩存。
private
指示所有或部分響應(yīng)信息僅針對某一用戶,因此禁止緩存為public緩存。
Symfony保守的設(shè)置每一次響應(yīng)為private。為了利用好共享緩存(比如Symfony反向代理),響應(yīng)必須顯式設(shè)定為public。
安全方法(Safe Method) ?
HTTP緩存只工作在“安全”HTTP方法下(比如GET或HEAD)。所謂安全,是指你在對請求提供服務(wù)時(諸如記錄日志,處理緩存信息等)永遠不能改變服務(wù)器上的程序狀態(tài)。這就產(chǎn)生兩個極為有說服力的重要結(jié)論:
你永遠不應(yīng)該在GET或HEAD請求的響應(yīng)中改變程序狀態(tài)。就算你不使用gateway cache,然而代理緩存的本質(zhì)是,任何GET或HEAD請求,可能或并沒有真正hit到你的服務(wù)器;
不要預(yù)期對PUT、POST或DELETE方法進行緩存。這些方法意味著被用于你的程序狀態(tài)發(fā)生改變時(比如刪除一篇博客)。緩存它們將阻止特定的請求命中或改變你的程序。
緩存規(guī)則和默認設(shè)置 ?
HTTP1.1允許默認緩存任何內(nèi)容,除非顯式指定了Cache-Control
頭。實踐中,多數(shù)緩存在請求中包含cookie時、包含authorization頭時、使用了一個非安全方法時(比如PUT、POST或DELETE)或當(dāng)響應(yīng)有一個重定向狀態(tài)碼時,什么也不做。
當(dāng)開發(fā)者在響應(yīng)頭中什么也沒設(shè)置時,Symfony依據(jù)以下規(guī)則,自動設(shè)置了有意義的而且是偏保守的Cache-Header
頭。
如果沒有緩存頭信息被定義(
Cache-Control
、Expires
、ETag
或Last-Modified
),Cache-Control
將被設(shè)為no-cache
,代表響應(yīng)將不被緩存;如果
Cache-Control
是空(但是另外一個緩存頭有被設(shè)置),其值將被設(shè)為private, must-revalidate
;但是如果至少有一個
Cache-Control
指令被設(shè)置,而且沒有public
或private
指令被顯式添加的話,Symfony會自動添加private
指令(除了當(dāng)s-maxage
被設(shè)置時)
HTTP Expiration,Validation和Invalidation ?
HTTP協(xié)議定義了兩種緩存模型:
利用expiration model(過期模型),通過包容
Cache-Control
頭和/或Expires
頭,即可直接指定一個響應(yīng)應(yīng)該被認為“新鮮”的時長。緩存能夠理解過期時間,不再制造相同請求,直到該緩存版本抵達過期時間,而且變得“不新鮮(stale)”。當(dāng)頁面是真動態(tài)時(展現(xiàn)層經(jīng)常改變),則validation model(驗證模型)的使用就十分有必要。利用這個模型,緩存把響應(yīng)存儲起來,但會在每次請求時向服務(wù)器“提問”——是否緩存了的響應(yīng)仍然有效?程序使用了一個獨立的響應(yīng)識別器(即
Etag
頭)和/或一個時間戳(即Last-Modified
頭),來檢查當(dāng)前頁面自被緩存之后,是否發(fā)生了改變。
Expiration(過期) ?
expiration model,是兩個緩存模型里效率更高、更直接的一個,因此應(yīng)該被盡可能多地使用。當(dāng)一個響應(yīng)通過expiration被緩存時,緩存將保存響應(yīng),并且在過期之前直接返回它,而毋須命中程序。
過期模型,可以通過以下幾乎一樣的兩種HTTP頭之一來實現(xiàn):Expires
或Cache-Control
。
使用Expires頭控制過期 ?
根據(jù)HTTP specification,“Expires
頭字段將在response被認為是stale之后給出date/time?!?。這里的Expires
頭可以被設(shè)為Response
方法:setExpires()
。它使用DateTime
實例作為參數(shù):
$date = new DateTime(); $date->modify('+600 seconds'); $response->setExpires($date);
該響應(yīng)的HTTP頭信息類似這種:
Expires: Thu, 01 Mar 2011 16:00:00 GMT
setExpires()
方法將自動轉(zhuǎn)換日期為GMT時區(qū),因為這是HTTP specification的要求。
注意,在HTTP 1.1版之前,并不需要原始服務(wù)器來發(fā)送Date
頭。因此,緩存(比如瀏覽器的)就需要本地時鐘來評估Expires
頭,進而令緩存周期的計算因時間傾斜而變得脆弱不堪。另外一個Expires
頭限制是,正如HTTP協(xié)議中所描述的,“HTTP/1.1 不得發(fā)送Expires
的日期超過一年?!?/p>
使用Cache-Control頭控制過期 ?
因為Expires
頭的限制,多數(shù)情況下,你應(yīng)該使用Cache-Control
頭來替代。記得,Cache-Control
頭被用于多種不同的緩存指令。例如,max-age
和s-maxage
。第一個用于全部緩存,而第二個僅在共享緩存時用到。
// Sets the number of seconds after which the response // should no longer be considered fresh// 設(shè)置“響應(yīng)過期”的秒數(shù)$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) ?
你當(dāng)然可以對同一個Response
同時使用validation和expiration。因為expiration的優(yōu)勢大過validation,你能很容易地從兩個世界中好的一面受益。也就是說,同時使用過期和驗證,你可以命令緩存來服務(wù)于已緩存的內(nèi)容,同時還能在某些區(qū)間(expiration)向后檢查以確認緩存內(nèi)容仍然有效。
你也可以通過annotation來為expiration和validation去定義HTTP緩存頭。參考FrameworkExtraBundle文檔。
更多Response方法 ?
Response
類提供了很多方法以應(yīng)對緩存。下面是幾個特別有用的:
// Marks the Response stale 標記響應(yīng)過期$response->expire(); // Force the response to return a proper 304 response with no content // 強制響應(yīng)返回一個沒有內(nèi)容的恰當(dāng)?shù)?04響應(yīng)$response->setNotModified();
另外,多數(shù)與緩存相關(guān)的HTTP頭可以單獨使用setCache()
方法來完成設(shè)置:
// Set cache settings in one call$response->setCache(array( 'etag' => $etag, 'last_modified' => $date, 'max_age' => 10, 's_maxage' => 10, 'public' => true, // 'private' => true, ));
總結(jié) ?
Symfony的設(shè)計思想即是遵循業(yè)界公認標準:HTTP。緩存功能也不例外。掌握Symfony的緩存系統(tǒng)意味著你已然熟悉了HTTP cache模型并且能夠高效地使用它。換句話說,毋須依賴Symfony文檔和例程,你可以馳騁于HTTP caching和以Varnish為代表的gateway caches的世界。