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