Symfony2框架學(xué)習(xí)筆記之HTTP Cache用法詳解
本文實(shí)例講述了Symfony2框架HTTP Cache用法。分享給大家供大家參考,具體如下:
富web應(yīng)用程序的本質(zhì)意味著它們的動(dòng)態(tài)。無(wú)論你的應(yīng)用程序多么有效率,每個(gè)請(qǐng)求比起靜態(tài)文件來(lái)說(shuō)總會(huì)存在很多的耗費(fèi)。對(duì)于大多數(shù)web程序來(lái)說(shuō),這沒(méi)什么。 Symfony2非常的輕快,無(wú)論你做些嚴(yán)重超載的請(qǐng)求,每個(gè)請(qǐng)求將會(huì)得到很快的回復(fù),而不會(huì)對(duì)你的服務(wù)器造成壓力。但是隨著你站點(diǎn)的成長(zhǎng),負(fù)載將成為一個(gè)嚴(yán)重的問(wèn)題。對(duì)每個(gè)請(qǐng)求處理應(yīng)該只被正常執(zhí)行一次。這就是緩存真正要達(dá)成的目標(biāo)。
站在巨人肩膀上的緩存:
提高一個(gè)應(yīng)用程序執(zhí)行效率的最有效方法是緩存一個(gè)頁(yè)面的所有輸出然后讓后續(xù)的請(qǐng)求繞開(kāi)整個(gè)應(yīng)用程序。當(dāng)然,這對(duì)于高動(dòng)態(tài)性的站點(diǎn)來(lái)說(shuō)并不是總是可能的。Symfony2 緩存系統(tǒng)是比較特別的,因?yàn)樗蕾囉谠贖TTP規(guī)范中定義的簡(jiǎn)單強(qiáng)大的HTTP cache。沒(méi)有重新發(fā)明新的緩存方法,Symfony2 擁抱在web上定義基礎(chǔ)交流的標(biāo)準(zhǔn)。一旦你理解了基礎(chǔ)的HTTP校驗(yàn)和過(guò)期緩存模式,你就會(huì)完全掌握了Symfony2的緩存系統(tǒng)。
第一步:一個(gè)網(wǎng)關(guān)緩存(gateway cache),或者反向代理。是一個(gè)坐在你應(yīng)用程序前面的對(duì)立的層。反向代理緩存來(lái)自于你應(yīng)用程序的響應(yīng)并使用這些緩存響應(yīng)在某些請(qǐng)求到達(dá)你應(yīng)用程序之前來(lái)回復(fù)它們。 Symfony2提供了自己的反向代理,也可以使用其它任何的反向代理。
第二步:HTTP緩存 (HTTP cache)頭用于和網(wǎng)關(guān)緩存以及任何其位于客戶和你的應(yīng)用程序之間的其它緩存交流。Symfony2 提供了和緩存頭交互的預(yù)設(shè)行為和強(qiáng)大接口。
第三步:HTTP 超時(shí)和校驗(yàn)時(shí)用于決定一個(gè)緩存內(nèi)容是否新鮮和陳舊的兩種模式。
第四步:ESI(Edge Side Includes)允許HTTP緩存被用于獨(dú)立緩存頁(yè)面片段(甚至是嵌套片段)。使用ESI,你甚至可以緩存一個(gè)完整的頁(yè)面60分鐘。但一個(gè)嵌入式邊欄緩存只有5分鐘。
使用網(wǎng)關(guān)緩存
當(dāng)使用HTTP緩存時(shí),緩存是跟你的應(yīng)用程序完全分離的,它位于你的請(qǐng)求客戶端和應(yīng)用程序之間。該緩存的工作就是從客戶端接收請(qǐng)求并把它們傳遞回你的應(yīng)用程序。同時(shí)它也將接收從你的應(yīng)用程序返回的響應(yīng)并把它轉(zhuǎn)給客戶端。可以說(shuō)它是你的應(yīng)用程序和請(qǐng)求客戶端之間請(qǐng)求-響應(yīng)交互的中間人。
按照這個(gè)思路,緩存會(huì)保存被認(rèn)為是“可緩存的”每一個(gè)響應(yīng)回復(fù)。當(dāng)同樣的請(qǐng)求再次傳來(lái)時(shí),該緩存會(huì)把自己緩存的響應(yīng)直接回復(fù)給請(qǐng)求客戶端,而完全忽略你的應(yīng)用程序。這種類(lèi)型的緩存就是HTTP網(wǎng)關(guān)緩存。目前有很多這類(lèi)緩存,比如Varnish,Squid in reverse proxy mode和Symfony2 反向代理等。
緩存類(lèi)型
一個(gè)網(wǎng)關(guān)緩存不是緩存的唯一類(lèi)型。事實(shí)上,有三種不同類(lèi)型的緩存會(huì)截獲并使用你的應(yīng)用程序發(fā)出的HTTP緩存頭。它們是:
瀏覽器緩存(Browser caches):瀏覽器擁有自己的本地緩存,這對(duì)你單擊"前一步"或者查看圖片和其它網(wǎng)絡(luò)資產(chǎn)時(shí)起到了主要作用。
代理緩存(Proxy caches):一個(gè)代理緩存是一個(gè)多人位于一人之后的共享的緩存。它們大多是一些大公司或者ISP安裝用來(lái)減少延遲和網(wǎng)絡(luò)阻塞的。
網(wǎng)關(guān)緩存(Gateway caches):像一個(gè)代理,也是一個(gè)共享緩存但是是位于服務(wù)器端的。一般是網(wǎng)絡(luò)管理員安裝它們,它使得網(wǎng)站更具可伸縮性,可靠性和高效性。網(wǎng)關(guān)緩存有時(shí)候被稱(chēng)為反向代理緩存,代理緩存,更或者是HTTP加速器。
Symfony2 反向代理
Symfony2擁有一個(gè)用PHP編寫(xiě)的反向代理(也叫做網(wǎng)關(guān)緩存)。開(kāi)啟它后,來(lái)自你應(yīng)用程序的可緩存的響應(yīng)回復(fù)將會(huì)開(kāi)始被立刻緩存。安裝它相也當(dāng)容易。每一個(gè)新的Symfony2應(yīng)用程序都有一個(gè)預(yù)配置緩存內(nèi)核(AppCache)包含了一個(gè)默認(rèn)的AppKernel。該緩存內(nèi)核就是個(gè)反向代理。要開(kāi)啟緩存,修改前端控制器代碼使用緩存內(nèi)核:
// web/app.php require_once __DIR__.'/../app/bootstrap.php.cache'; require_once __DIR__.'/../app/AppKernel.php'; require_once __DIR__.'/../app/AppCache.php'; use Symfony\Component\HttpFoundation\Request; $kernel = new AppKernel('prod', false); $kernel->loadClassCache(); //使用AppCache包裹默認(rèn)的AppKernel $kernel = new AppCache($kernel); $kernel->handle(Request::createFromGlobale())->send();
緩存內(nèi)核會(huì)立刻扮演一個(gè)反向代理的角色,緩存來(lái)自你應(yīng)用程序的回復(fù)把它們發(fā)回給請(qǐng)求客戶端。
注意,該緩存內(nèi)核有一個(gè)特別的getLog()方法返回一個(gè)能夠表示在緩存層發(fā)生了什么的字符串。
可以在開(kāi)發(fā)環(huán)境中來(lái)調(diào)試和校驗(yàn)?zāi)愕木彺娌呗浴?/p>
error_log($kernel->getLog());
AppCache 對(duì)象是一個(gè)合理的默認(rèn)配置,當(dāng)然你也可以通過(guò)重寫(xiě)getOptions()方法來(lái)設(shè)置可選項(xiàng)對(duì)它進(jìn)行調(diào)優(yōu)。
// app/AppCache.php use 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, ); } }
注意,這里無(wú)論怎么重寫(xiě)getOptions()方法,其中debug選項(xiàng)將被包裹的AppKernel的debug值自動(dòng)設(shè)置。
下面是一些重要的可選項(xiàng):
default_ttl: 當(dāng)沒(méi)有顯式的刷新信息在回復(fù)中提供時(shí),一個(gè)緩沖實(shí)體應(yīng)該被認(rèn)為是新鮮的時(shí)間秒數(shù)。顯式的設(shè)置Cache-Control 或者 Expires 頭會(huì)覆蓋這個(gè)參數(shù)值。默認(rèn)值為0。
private_headers:請(qǐng)求頭組,它在回復(fù)上觸發(fā)"private" Cache-Control 行為,無(wú)論回復(fù)是通過(guò)Cache-Control 指令顯式的聲明是public還是private 。默認(rèn)為Authorization和Cookie。
allow_reload: 指定是否允許客戶端通過(guò)在請(qǐng)求中指定Cache-Control的"no-cache"指令來(lái)強(qiáng)迫緩存重新加載。設(shè)置它為true時(shí)符合RFC2616規(guī)范。默認(rèn)值為false。
allow_revalidate:指定是否允許客戶端通過(guò)在請(qǐng)求中指定Cache-Control的"max-age=0"指令來(lái)強(qiáng)迫緩存重新校驗(yàn)。設(shè)置它為true時(shí)符合RFC2616規(guī)范。默認(rèn)值為false。
stale_while_revalidate:用于指定一個(gè)默認(rèn)秒數(shù)(間隔是秒因?yàn)榛貜?fù)TTL精度是1秒),在期間緩存還在后臺(tái)進(jìn)行重新校驗(yàn)時(shí)可以立刻返回一個(gè)陳舊的回復(fù)(默認(rèn)是2);該設(shè)置會(huì)被stale-while-revalidate HTTP Cache-Control擴(kuò)展重寫(xiě)(RFC 5861)。
stale_if_error: 指定一個(gè)默認(rèn)秒數(shù)(間隔是秒)在這期間緩存可以提供一個(gè)陳舊的回復(fù)當(dāng)遇到一個(gè)錯(cuò)誤時(shí)。默認(rèn)值為60。該設(shè)置會(huì)被stale-if-error HTTP Cache-Contorl 擴(kuò)展重寫(xiě)(RFC5861)
如果debug設(shè)置為true,Symfony2 會(huì)自動(dòng)添加一個(gè)X-Symfony-Cache 頭到回復(fù)保存著關(guān)于緩存點(diǎn)擊和丟失的信息。
從一個(gè)反向代理到另一個(gè)的轉(zhuǎn)換:
Symfony2反向代理是一個(gè)在開(kāi)發(fā)你的站點(diǎn)或者部署你的站點(diǎn)到一個(gè)共享主機(jī)而你無(wú)法安裝任何除PHP代碼以外的東西時(shí)的非常有用的工具。但是因?yàn)槭褂肞HP編寫(xiě),它不能跟用C寫(xiě)成的反向代理那樣快速。這就是為什么我們推薦使用Varnish或者Squid到你的運(yùn)營(yíng)服務(wù)器上的原因。好消息是從一個(gè)代理服務(wù)器到另外一個(gè)替換很容易,簡(jiǎn)單的不用你修改任何程序代碼。你可以開(kāi)始時(shí)使用Symfony2的反向代理等到了阻塞增加時(shí)升級(jí)到Varnish。
注意:Symfony2 反向代理執(zhí)行效率獨(dú)立于應(yīng)用程序的復(fù)雜性。因?yàn)閼?yīng)用程序核心僅僅在請(qǐng)求需要被轉(zhuǎn)發(fā)到它時(shí)才被啟動(dòng)。
HTTP緩存說(shuō)明:
為了發(fā)揮可用緩存層的優(yōu)勢(shì),你的應(yīng)用程序必須能傳達(dá)它的哪個(gè)回復(fù)可以被緩存,什么時(shí)候/怎樣 緩存會(huì)變成陳舊的規(guī)則。這些是通過(guò)在回復(fù)(response)上設(shè)置HTTP 緩存頭來(lái)實(shí)現(xiàn)的。
記住,"HTTP"只不過(guò)是一種web客戶端和服務(wù)器之間交流的簡(jiǎn)單文本語(yǔ)言。當(dāng)我們說(shuō)HTTP 緩存時(shí),我們說(shuō)的是它允許客戶端和服務(wù)器交換信息相關(guān)的緩存。
HTTP指定了4個(gè)Response緩存頭,我們需要關(guān)注一下:
Cache-Control
Expires
ETag
Last-Modified
其中最重要的也是萬(wàn)能的頭是Cache-Control頭,它其實(shí)是一個(gè)各種緩存信息的集合。
Cache-Control Header
Cache-Control頭是唯一的一個(gè)其內(nèi)部包含了各種各樣的關(guān)于一個(gè)response是否可以被緩存的信息。每條信息之間用逗號(hào)隔開(kāi)。
Cache-Control:private,max-age=0,must-revalidate Cache-Control:max-age=3600,must-revalidate
Symfony 提供一個(gè)Cache-Control頭的抽象,使它的創(chuàng)建更加可控。
$response = new Response(); // 標(biāo)記response為public還是private $response->setPublic(); $response->setPrivate(); // 設(shè)置private或者shared 的最大年齡 age $response->setMaxAge(600); $response->setSharedMaxAge(600); // 設(shè)置一個(gè)自定義的Cache-Control 指令 $response->headers->addCacheControlDirective('must-revalidate', true);
公共vs私有 Response
網(wǎng)關(guān)緩存和代理緩存都被認(rèn)為是“共享”緩存,因?yàn)樗鼈兙彺娴膬?nèi)容是被多用戶共享的。如果一個(gè)特定用戶的回復(fù)曾被錯(cuò)誤的存儲(chǔ)到共享緩存中,它以后可能被返回給無(wú)數(shù)的不用用戶。想象一下,如果你的賬戶信息被緩存然后返回給每一個(gè)后來(lái)請(qǐng)求他們自己賬戶頁(yè)面的用戶。要處理這種情況,每個(gè)回復(fù)可能都要設(shè)置時(shí)public還是private。
public 說(shuō)明給回復(fù)可能被private和共享的緩存保存。
private 說(shuō)明所有的或者部分的回復(fù)信息時(shí)給一個(gè)單獨(dú)用戶的,所以不能緩存到共享緩存中。
Symfony 謹(jǐn)慎地默認(rèn)每個(gè)回復(fù)為private。 要使用共享緩存的優(yōu)點(diǎn)(比如Symfony2反向代理),回復(fù)必須被顯式的設(shè)置為public。
安全方法:
HTTP緩存僅僅為安全方法工作(比如GET和HEAD)。要安全意味著當(dāng)它為某個(gè)請(qǐng)求服務(wù)時(shí)從來(lái)不會(huì)改變服務(wù)器上應(yīng)用程序的狀態(tài)。(當(dāng)然你可以寫(xiě)日志信息,緩存數(shù)據(jù)等)。這里有兩個(gè)很合理的后果(consequences):
當(dāng)你的應(yīng)用程序回復(fù)一個(gè)GET或者HEAD請(qǐng)求時(shí),你絕對(duì)不會(huì)改變你應(yīng)用程序的狀態(tài)。即使你不用網(wǎng)關(guān)緩存,代理緩存的存在意味著任何GET和HEAD請(qǐng)求可能會(huì)或者可能不會(huì)真的達(dá)到你的服務(wù)器。
不要期望PUT,POST或者DELETE方法被緩存。這些方法被使用意味著你應(yīng)用程序狀態(tài)的改變。緩存它們將阻止某種請(qǐng)求訪問(wèn)或者改變你的應(yīng)用程序。
緩存規(guī)則和默認(rèn)設(shè)置
HTTP 1.1 默認(rèn)情況下允許緩存任何事情除非有一個(gè)顯式的Cache-Control頭。實(shí)踐中,大多數(shù)緩存當(dāng)請(qǐng)求有cookie,一個(gè)授權(quán)頭,使用一個(gè)非安全的方法(比如PUT,POST,DELETE)或者當(dāng)請(qǐng)求有一個(gè)重定向代碼時(shí),不會(huì)進(jìn)行任何緩存活動(dòng)。
當(dāng)開(kāi)發(fā)者沒(méi)有做任何設(shè)置時(shí),Symfony2 會(huì)自動(dòng)按照下面的規(guī)則設(shè)置一個(gè)合理的比較保守的Cache-Control頭:
如果沒(méi)有緩存頭被定義(Cache-Control,Expires,ETag 或者Last-Modified),Cache-Control被設(shè)置為no-cache,意味著該response將不會(huì)被緩存。
如果Cache-Control 為空(但是有另一個(gè)緩存頭存在),它的值被設(shè)置為private,must-revalidate;
如果至少一個(gè)Cache-Control指令被設(shè)置,并且沒(méi)有'public'或者‘private'指令被顯式的添加,Symfony2 會(huì)自動(dòng)添加一個(gè)private指令(除去s-maxage 被設(shè)置的情況)。
HTTP過(guò)期和校驗(yàn)
HTTP規(guī)范定義了兩個(gè)緩存模型:
過(guò)期模型,你只需要通過(guò)包含一個(gè)Cache-Control和/或者一個(gè)Expires頭來(lái)指定一個(gè)Response應(yīng)該多長(zhǎng)時(shí)間被考慮“新鮮”問(wèn)題。緩存理解過(guò)期將不再讓相同的請(qǐng)求回復(fù),直到緩存的版本達(dá)到它過(guò)期時(shí)間成為“stale"陳舊。
校驗(yàn)?zāi)P?,?dāng)頁(yè)面時(shí)真正的動(dòng)態(tài)頁(yè)面時(shí)(他們的展現(xiàn)經(jīng)常變化),校驗(yàn)?zāi)P途徒?jīng)常需要了。這種模型,緩存存儲(chǔ)response,但是要求服務(wù)對(duì)每個(gè)請(qǐng)求是否緩存response依然進(jìn)行校驗(yàn)。
應(yīng)用程序使用唯一的response 標(biāo)示符(ETag 頭) 和/或者 時(shí)間戳(Last-Modified 頭)來(lái)檢查頁(yè)面自從被緩存后是否放生了變化。
這兩個(gè)模型的目標(biāo)是通過(guò)依靠一個(gè)緩存存儲(chǔ)并返回"新鮮" response,使得應(yīng)用程序從不生成相同的response兩次。
過(guò)期:
過(guò)期模型是在這兩個(gè)模型中是更加有效和簡(jiǎn)單明確的模型,它應(yīng)該在任何時(shí)候都有被使用的可能。當(dāng)一個(gè)response使用一過(guò)期方式被緩存,緩存將存儲(chǔ)response并為請(qǐng)求直接返回它而不去訪問(wèn)應(yīng)用程序,直到它過(guò)期。
過(guò)期模型可以被熟練的使用一兩個(gè),幾乎相同的,HTTP頭:比如 Expires或cache - control。
過(guò)期和Expires 頭
根據(jù)HTTP規(guī)范,Expires頭字段提供一個(gè)日期/時(shí)間,過(guò)了這個(gè)日期或時(shí)間后它的response就被認(rèn)為是陳舊的了。Expires頭可以被Response的setExpires()方法設(shè)置。它要求一個(gè)DateTime實(shí)例作為輸入?yún)?shù)。
$date = new DateTime(); $date->modify('+600 seconds'); $response->setExpires($date);
生成的HTTP頭的結(jié)果如下:
Expires: Thu, 01 Mar 2011 16:00:00 GMT
注意,因?yàn)橐?guī)范的需要setExprise()方法會(huì)自動(dòng)把日期轉(zhuǎn)換為GMT時(shí)區(qū)。
我們注意到在HTTP規(guī)范1.1版之前,源服務(wù)不需要發(fā)送一個(gè)Date頭。 因此緩存(比如瀏覽器)可能需要依靠它本地的始終來(lái)評(píng)估Expires頭,造成計(jì)算生命周期時(shí)時(shí)鐘偏差。 Expires頭的另一個(gè)限制是規(guī)范規(guī)定:"HTTP/1.1服務(wù)不應(yīng)該發(fā)送Expires日期未來(lái)超過(guò)一年。"
過(guò)期和Cache-Control 頭
因?yàn)镋xpires頭的限制,大多時(shí)候,你應(yīng)該采用Cache-Control頭來(lái)替代它?;叵胍幌?,Cache-Control頭是用來(lái)指定多個(gè)不同緩存指令的。對(duì)于過(guò)期來(lái)說(shuō),有兩個(gè)指令,max-age 和 s-maxage。第一個(gè)被所有的緩存使用,然而第二個(gè)僅僅被用于共享緩存。
// 設(shè)置一個(gè)秒數(shù),過(guò)了這個(gè)秒數(shù)后response就被認(rèn)為是陳舊的了。 $response->setMaxAge(600); // 同上,但是只用于共享緩存。 $response->setSharedMaxAge(600); Cache-Control頭將使用如下格式(它可能還有其它指令): Cache-Control: max-age=600, s-maxage=600
校驗(yàn):
一旦底層數(shù)據(jù)發(fā)生變化需要立刻對(duì)緩存資源進(jìn)行更新時(shí),過(guò)期模型就顯得力不從心了。在過(guò)期模型下,應(yīng)用程序不會(huì)被要求返回更新的response直到緩存最后過(guò)期變?yōu)殛惻f內(nèi)容以后。
校驗(yàn)?zāi)P徒鉀Q了這個(gè)問(wèn)題。在校驗(yàn)?zāi)P拖?,緩存持續(xù)保存response。不同的是,對(duì)每一個(gè)請(qǐng)求request,緩存都詢問(wèn)應(yīng)用程序緩存的response是否依然有效。如果緩存仍然有效,你的應(yīng)用程序應(yīng)該返回一個(gè)304狀態(tài)碼和一個(gè)空內(nèi)容。這告訴緩存它可以為請(qǐng)求用戶返回它緩存的response。
在這個(gè)模型下,你主要節(jié)省了帶寬因?yàn)槊枋霾粫?huì)發(fā)送兩次到相同的客戶端(而是發(fā)送一個(gè)304回復(fù)代替)。但是,如果你仔細(xì)設(shè)計(jì)你的應(yīng)用程序,你可能能忍受304 response需要的最小數(shù)據(jù)并節(jié)省CPU。
304狀態(tài)碼意味著沒(méi)有修改。它很重要因?yàn)樗鼪](méi)有包含整整的被請(qǐng)求內(nèi)容,而只是一個(gè)輕量級(jí)的導(dǎo)向集,它告訴緩存它應(yīng)該使用它現(xiàn)在保存的版本回復(fù)請(qǐng)求。跟過(guò)期類(lèi)似,也有兩個(gè)不同的HTTP頭可以被用來(lái)實(shí)現(xiàn)校驗(yàn)?zāi)P停?ETag和Last-Modifed
校驗(yàn)和ETag頭
ETag頭是一個(gè)字符串(也叫"entity-tag")它是目標(biāo)資源一個(gè)表現(xiàn)的唯一標(biāo)識(shí)。它完全由你的應(yīng)用程序來(lái)生成和設(shè)置。 比如,如果 /about 資源被緩存保存時(shí)取決于日期和你應(yīng)用程序的返回內(nèi)容。一個(gè)ETag像一個(gè)手印,被用來(lái)快速的比較一個(gè)資源的兩個(gè)不同版本是否等效。
像手印,同一個(gè)資源的所有表示形式中每個(gè)ETag必須是唯一的。讓我們來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)生成ETag使用md5加密的回復(fù)內(nèi)容作為內(nèi)容:
public function indexAction() { $response = $this->render('MyBundle:Main:index.html.twig'); $response->setETag(md5($response->getContent())); $response->isNotModified($this->getRequest()); return $response; }
Response::isNotModified()方法把和Request一起發(fā)送的ETag與Response上的ETag進(jìn)行比較。如果兩個(gè)匹配,方法自動(dòng)設(shè)置Response狀態(tài)碼為304。
這個(gè)算法非常簡(jiǎn)單也非常通用,但是你需要在能計(jì)算ETag之前創(chuàng)建一個(gè)完整的Response,校驗(yàn)?zāi)P褪谴蝺?yōu)選擇。換句話說(shuō),它節(jié)省了帶寬,單沒(méi)有節(jié)省CPU利用。
Symfony2還通過(guò)向setETag()方法傳入true作為第二個(gè)參數(shù),來(lái)支持弱ETag。
校驗(yàn)和Last-Modified 頭
Last-Modified頭是校驗(yàn)?zāi)P偷牡诙N形式。根據(jù)HTTP規(guī)范,”Last-Modified 頭字段指定日期和時(shí)間,在這個(gè)時(shí)間源服務(wù)器相信該表現(xiàn)是最后被修改版?!?br /> 換句話說(shuō),應(yīng)用程序決定基于自動(dòng)緩存內(nèi)容被緩存后是否被更新過(guò)來(lái)判斷緩存的內(nèi)容是否要被更新過(guò)。舉個(gè)例子,你可以使用最新更新日期為所有需要計(jì)算資源表現(xiàn)的對(duì)象作為L(zhǎng)ast-Modified頭的值:
public function showAction($articleSlug) { //... $articleDate = new \DateTime($article->getUdateAt()); $authorDate = new \DateTime($author->getUpdateAt());\ $date = $authorDate>$articleDate ? $authorDate : $articleDate; $response->setLastModified($date); $response->isNotModified($this->getRequest()); return $response; }
Response::isNotModified() 方法比較請(qǐng)求Request中的If-Modified-Since頭和Response中的Last-Modified 頭。如果他們相等,Response會(huì)被設(shè)置一個(gè)304狀態(tài)碼。
注意,If-Modified-since 請(qǐng)求頭等于最終發(fā)送到客戶端特定資源Last-Modified頭。這就是如何客戶端和服務(wù)端相互交流決定資源自從它被緩存后是否被更新。
使用校驗(yàn)優(yōu)化你的代碼:
任何緩存策略的主要目的都是減輕應(yīng)用程序的加載。換句話說(shuō),你的應(yīng)用程序做的越少來(lái)返回304 response,越好。Response::isNotModified()方法通過(guò)暴露一個(gè)簡(jiǎn)單有效的模式做到了。
public funcation showAction($articleSlug) { //獲取最小信息來(lái)計(jì)算ETag或者Last-Modified值(基于Request,數(shù)據(jù)是從數(shù)據(jù)庫(kù)或者一個(gè)鍵值對(duì)存儲(chǔ)實(shí)例中獲取。 $article = //... //創(chuàng)建一個(gè)Response帶有一個(gè)ETag 和/或者 一個(gè)Last-Modified 頭 $response = new Response(); $response->setETag($article->computeETag()); $response->setLastModified($article->getPublishedAt()); //為給定的Request檢查Response沒(méi)有被修改 if($response->isNotModified($this->getRequest())){ //立刻返回304 Response return $response; }else{ //做一些更多的工作-比如獲取更多的數(shù)據(jù) $comment=//... //或者用你已經(jīng)開(kāi)啟的$response渲染一個(gè)模版 return $this->render('MyBundle:MyController:article.html.twig', array('article'=>$article, 'comments' =>$comments), $response ); } }
當(dāng)Response沒(méi)有被修改后,isNotModified()自動(dòng)設(shè)置response的狀態(tài)碼為304,移除response的內(nèi)容,移除一些不需要為304存在的頭。
不同的回復(fù)響應(yīng):
到目前為止,我們已經(jīng)假設(shè)了每個(gè)URI只有一個(gè)目標(biāo)資源的表示。默認(rèn)情況下,HTTP緩存通過(guò)使用URI的資源作為緩存鍵被執(zhí)行。如果兩個(gè)人請(qǐng)求同一個(gè)可緩存資源的URI,第二個(gè)用戶將獲取緩存版本。有時(shí)候這些不夠,不同版本的用一個(gè)URI需要被按照一個(gè)或者多個(gè)請(qǐng)求頭的值來(lái)被緩存。舉個(gè)例子,如果當(dāng)客戶端支持你壓縮頁(yè)面時(shí),任何給定的URI都有兩種表示:一個(gè)是客戶端支持壓縮時(shí),一個(gè)是不支持時(shí)的表示。這時(shí)候請(qǐng)求頭的Accept-Encoding值將決定使用哪個(gè)。
在這種情況下,我們需要回復(fù)的特定URI緩存一個(gè)壓縮版本和一個(gè)非壓縮版本,基于請(qǐng)求的Accept-Encoding值返回它們。這是通過(guò)Vary Response頭,Vary是一個(gè)不同頭用逗號(hào)分隔,它的值觸發(fā)請(qǐng)求資源的不同表示。
Vary:Accept-Encoding,User-Agent
注意,這個(gè)特別的Vary頭,將基于URI和Accept-Encoding和User-Agent 請(qǐng)求頭為每個(gè)資源的不同版本進(jìn)行緩存。
Response對(duì)象提供一個(gè)干凈的接口來(lái)管理Vary 頭:
// 設(shè)置一個(gè)vary 頭 $response->setVary('Accept-Encoding'); // 設(shè)置多個(gè)vary頭 $response->setVary(array('Accept-Encoding', 'User-Agent'));
setVary()方法需要一個(gè)頭名字或者一個(gè)頭名字?jǐn)?shù)組對(duì)應(yīng)不同的response。
過(guò)期和校驗(yàn):
你當(dāng)然可以在同一個(gè)Response中同時(shí)使用校驗(yàn)和過(guò)期。因?yàn)檫^(guò)期勝過(guò)校驗(yàn),你可以輕易的從它們兩個(gè)中根據(jù)好處做出選擇。換句話說(shuō),通過(guò)同時(shí)使用過(guò)期和校驗(yàn),你可以指示緩存服務(wù)于緩存的內(nèi)容,同時(shí)后臺(tái)間隔檢查來(lái)調(diào)查內(nèi)容是否依然合法。
更多Response方法:
Response類(lèi)提供了許多和緩存相關(guān)的方法。下面是主要的一些:
// 標(biāo)志Response過(guò)期陳舊 $response->expire(); // 強(qiáng)迫response返回一個(gè)適合 304 的沒(méi)有內(nèi)容的response $response->setNotModified();
另外,跟緩存最相關(guān)的HTTP頭可以被通過(guò)一個(gè)單獨(dú)的方法setCache()設(shè)置。
// 通過(guò)一個(gè)調(diào)用設(shè)置緩存參數(shù) $response->setCache(array( 'etag' => $etag, 'last_modified' => $date, 'max_age' => 10, 's_maxage' => 10, 'public' => true, // 'private' => true, ));
使用ESI(Edge Side Includes)
網(wǎng)關(guān)緩存是一個(gè)提高你網(wǎng)站執(zhí)行效率的很好的途徑。但是它們有一個(gè)限制:只能緩存整個(gè)頁(yè)面。如果你不想緩存整個(gè)頁(yè)面或者頁(yè)面的某一部分很動(dòng)態(tài),你就沒(méi)那么幸運(yùn)了。
幸運(yùn)的是,Symfony2為這些情況提供一個(gè)解決方案,基于ESI技術(shù)。它允許頁(yè)面指定的部分和主頁(yè)比起來(lái)有一個(gè)不同的緩存策略。
ESI規(guī)范描述標(biāo)簽?zāi)憧梢郧度氲侥愕捻?yè)面來(lái)和網(wǎng)關(guān)緩存交流。Symfony2中只實(shí)現(xiàn)了一個(gè)標(biāo)簽,include, 因?yàn)檫@是唯一一個(gè)能在Akami上下文之外使用的標(biāo)簽。
<html> <body> Some content <!-- 嵌入一個(gè)其它頁(yè)的內(nèi)容 --> <esi:include src="http://..." /> More content </body> </html>
從這個(gè)例子中注意到每個(gè)ESI標(biāo)簽有一個(gè)全限定URL。一個(gè)ESI標(biāo)簽表示可以通過(guò)一個(gè)給定的URL獲取的一個(gè)頁(yè)面片段。
當(dāng)請(qǐng)求被處理時(shí),網(wǎng)關(guān)緩存從它的緩存或者從背后的應(yīng)用程序中請(qǐng)求回復(fù)獲取整個(gè)頁(yè)面。換句話說(shuō),網(wǎng)關(guān)緩存既從緩存中獲取包含的頁(yè)面片段也會(huì)再次從背后的應(yīng)用程序中獲取回復(fù)請(qǐng)求的頁(yè)面片段。當(dāng)所有的ESI標(biāo)簽被解析后,網(wǎng)關(guān)緩存合并每一個(gè)ESI內(nèi)容到一個(gè)主頁(yè)并返回最后的內(nèi)容到客戶端。所有的這一切都透明的發(fā)生在網(wǎng)關(guān)緩存級(jí)(在你的程序外)。你將看到,如果你選擇ESI標(biāo)簽,Symfony2讓這包含它們的這一過(guò)程幾乎不費(fèi)勁。
在Symfony2中使用ESI
首先,使用ESI需要確認(rèn)在你的應(yīng)用程序配置中已經(jīng)打開(kāi)。
YAML格式:
# app/config/config.yml framework: # ... esi: { enabled: true }
XML格式:
<!-- app/config/config.xml --> <framework:config ...> <!-- ... --> <framework:esi enabled="true" /> </framework:config>
PHP代碼格式:
// app/config/config.php $container->loadFromExtension('framework', array( // ... 'esi' => array('enabled' => true), ));
現(xiàn)在假設(shè)我們有一個(gè)頁(yè)面時(shí)相對(duì)靜態(tài)的,除了一個(gè)新聞自動(dòng)收?qǐng)?bào)機(jī)在內(nèi)容的底部。使用ESI,我們可以緩存新聞自動(dòng)收?qǐng)?bào)機(jī)獨(dú)立于頁(yè)面其它部分。
public function indexAction() { $response = $this->render('MyBundle:MyController:index.html.twig'); $response->setSharedMaxAge(600); return $response; }
在該示例中,我們給全頁(yè)面緩存周期為10分鐘。接下來(lái),通過(guò)嵌入一個(gè)action讓新聞ticker包含到模板中。這是通過(guò)render幫助來(lái)實(shí)現(xiàn)的。因?yàn)榍度氲膬?nèi)容來(lái)自其它頁(yè)面,Symfony2使用一個(gè)標(biāo)準(zhǔn)的render幫助來(lái)配置ESI標(biāo)簽:
Twig格式:
{% render '...:news' with {}, {'standalone': true} %}
PHP格式:
<?php echo $view['actions']->render('...:news', array(), array('standalone' => true)) ?>
通過(guò)把standalone設(shè)置為true,告訴Symfony2這個(gè)action應(yīng)該被渲染為一個(gè)ESI標(biāo)簽。
你可能想知道為什么要使用一個(gè)helper方法來(lái)代替直接寫(xiě)ESI標(biāo)簽。這是因?yàn)槭褂胔elper讓你的應(yīng)用程序工作即使沒(méi)有網(wǎng)關(guān)緩存被安裝。讓我們來(lái)看看它是怎樣工作的。
當(dāng)standalone為false時(shí)(也是默認(rèn)值),Symfony2在發(fā)送response到客戶端之前合并包含的頁(yè)面內(nèi)容到一個(gè)主頁(yè)。
但是當(dāng)standalone為true時(shí),并且如果Symfony2發(fā)現(xiàn)它跟支持ESI的網(wǎng)關(guān)緩存對(duì)話時(shí),它生成一個(gè)ESI include標(biāo)簽。
如果沒(méi)有網(wǎng)關(guān)緩存或者網(wǎng)關(guān)緩存不支持ESI,Symfony2將只合并包含的標(biāo)簽頁(yè)面內(nèi)容到一個(gè)主要的像它在standalone為false時(shí)所做的一樣。
嵌入的action現(xiàn)在可以指定自己的緩存規(guī)則了,完全獨(dú)立于主頁(yè)。
public function newsAction() { //... $response->setShareMaxAge(60); }
使用ESI,整個(gè)頁(yè)面緩存將被保持600秒有效,但是新聞組建緩存將只持續(xù)60秒。
ESI的一個(gè)必備條件是嵌入的action可以通過(guò)一個(gè)URL被訪問(wèn),這樣網(wǎng)關(guān)緩存才可以獨(dú)立于頁(yè)面其它部分獲取它。當(dāng)然,一個(gè)action不能被通過(guò)一個(gè)URL訪問(wèn)除非有一個(gè)路由指向它。Symfony2 通過(guò)一個(gè)通用的路由和controller負(fù)責(zé)這個(gè)。
為了ESI包含標(biāo)簽?zāi)苷5墓ぷ?,你必須定義_internal 路由:
YAML格式:
# app/config/routing.yml _internal: resource: "@FrameworkBundle/Resources/config/routing/internal.xml" prefix: /_internal
XML格式:
<!-- app/config/routing.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <import resource="@FrameworkBundle/Resources/config/routing/internal.xml" prefix="/_internal" /> </routes>
PHP代碼格式:
// app/config/routing.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; $collection->addCollection($loader->import('@FrameworkBundle/Resources/config/routing/internal.xml', '/_internal')); return $collection;
因?yàn)槁酚稍试S所有的action通過(guò)一個(gè)URL被訪問(wèn),你可以通過(guò)使用Symfony2防火墻(允許訪問(wèn)你的反向代理的IP范圍)內(nèi)容保護(hù)它。
緩存策略的一大優(yōu)勢(shì)是你可以讓你的應(yīng)用程序根據(jù)動(dòng)態(tài)的需要同時(shí)又盡量的減少觸及應(yīng)用程序。
一旦你開(kāi)始使用ESI,請(qǐng)記住一定使用s-maxage指令代替max-age。因?yàn)闉g覽器只接受聚合的資源,它不知道子組件,所以它會(huì)按照max-age指令緩存整個(gè)頁(yè)面。這是你不希望它做的。
render helper支持的兩外兩個(gè)有用選項(xiàng):
alt:用作ESI標(biāo)簽的alt屬性,當(dāng)src找不到時(shí),它允許你指定一個(gè)替代URL。
ignore_errors:如果設(shè)置為true,一個(gè)onerror屬性將被添加到ESI,并且屬性值設(shè)置為continue,在一個(gè)失敗事件中,網(wǎng)關(guān)緩存將只默默的移除ESI標(biāo)簽。
緩存失效:
“計(jì)算機(jī)科學(xué)中有兩大難題:緩存失效和命名事物”---Phil Karlton
你永遠(yuǎn)都不需要失效緩存數(shù)據(jù),因?yàn)槭г缫言贖TTP緩存模型中被考慮到了。如果你使用校驗(yàn),你永遠(yuǎn)都不需要通過(guò)定義校驗(yàn)任何事情;如果你使用過(guò)期并失效某個(gè)資源,它意味著你設(shè)置一個(gè)未來(lái)的過(guò)期日期。因?yàn)樵谌魏晤?lèi)型的反向代理中失效都是一個(gè)頂級(jí)規(guī)范,如果你不擔(dān)心失效,你可以在不改變?nèi)魏螒?yīng)用程序代碼的情況下在反向代理間切換。
其實(shí),所有的反向代理都提供了清除緩存數(shù)據(jù)的方式,但是你需要盡量的避免使用它們。最標(biāo)準(zhǔn)的清除給定URL的緩存的方式是通過(guò)指定請(qǐng)求的HTTP方法為PURGE 來(lái)進(jìn)行。
下面是如何配置Symfony2的反向代理支持PURGE HTTP方法:
// app/AppCache.php use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; class AppCache extends HttpCache { protected function invalidate(Request $request) { if ('PURGE' !== $request->getMethod()) { return parent::invalidate($request); } $response = new Response(); if (!$this->getStore()->purge($request->getUri())) { $response->setStatusCode(404, 'Not purged'); } else { $response->setStatusCode(200, 'Purged'); } return $response; } }
注意,你必須保護(hù)你的PURGE HTTP方法以避免隨便一個(gè)人使用某些方法清除你的緩存數(shù)據(jù)。
總結(jié):
Symfony2旨在遵循一條被證明了的道路規(guī)則:HTTP。 緩存也不例外。掌握Symfony2緩存系統(tǒng)意味著熟悉HTTP緩存模式和有效的使用它們。
這就意味著,你不能只依賴于symfony2文檔和代碼示例,你必須了解有關(guān)HTTP緩存和網(wǎng)關(guān)緩存的更寬闊的知識(shí),比如Varnish。
希望本文所述對(duì)大家基于Symfony框架的PHP程序設(shè)計(jì)有所幫助。
- Symfony2框架創(chuàng)建項(xiàng)目與模板設(shè)置實(shí)例詳解
- 使用symfony命令創(chuàng)建項(xiàng)目的方法
- Symfony頁(yè)面的基本創(chuàng)建實(shí)例詳解
- symfony2.4的twig中date用法分析
- Symfony2之session與cookie用法小結(jié)
- Symfony2實(shí)現(xiàn)從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)的方法小結(jié)
- Symfony2實(shí)現(xiàn)在controller中獲取url的方法
- Symfony2框架學(xué)習(xí)筆記之表單用法詳解
- Symfony2學(xué)習(xí)筆記之插件格式分析
- Symfony2學(xué)習(xí)筆記之系統(tǒng)路由詳解
- Symfony2學(xué)習(xí)筆記之控制器用法詳解
- Symfony2學(xué)習(xí)筆記之模板用法詳解
- Symfony2創(chuàng)建頁(yè)面實(shí)例詳解
相關(guān)文章
php微信公眾號(hào)開(kāi)發(fā)之音樂(lè)信息
這篇文章主要為大家詳細(xì)介紹了php微信公眾號(hào)開(kāi)發(fā)之音樂(lè)信息,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10laravel框架 laravel-admin上傳圖片到oss的方法
今天小編就為大家分享一篇laravel框架 laravel-admin上傳圖片到oss的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-10-10Laravel中七個(gè)非常有用但很少人知道的Carbon方法
在編寫(xiě)PHP應(yīng)用時(shí)經(jīng)常需要處理日期和時(shí)間,Carbon繼承自 PHP DateTime 類(lèi)的 API 擴(kuò)展,它使得處理日期和時(shí)間更加簡(jiǎn)單,這篇文章主要給大家分享了Laravel中七個(gè)非常有用但很少人知道的Carbon方法,需要的朋友可以參考下。2017-09-09smarty模板引擎使用內(nèi)建函數(shù)foreach循環(huán)取出所有數(shù)組值的方法
這篇文章主要介紹了smarty模板引擎使用內(nèi)建函數(shù)foreach循環(huán)取出所有數(shù)組值的方法,實(shí)例分析了foreach循環(huán)遍歷數(shù)組的幾種常用技巧,需要的朋友可以參考下2015-01-01Yii框架數(shù)據(jù)模型的驗(yàn)證規(guī)則rules()被執(zhí)行的方法
這篇文章主要介紹了Yii框架數(shù)據(jù)模型的驗(yàn)證規(guī)則rules()被執(zhí)行的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12PHP解析html類(lèi)庫(kù)simple_html_dom的轉(zhuǎn)碼bug
這篇文章主要介紹了PHP解析html類(lèi)庫(kù)simple_html_dom的轉(zhuǎn)碼bug ,需要的朋友可以參考下2014-05-05PHP獲取IP地址所在地信息的實(shí)例(使用純真IP數(shù)據(jù)庫(kù)qqwry.dat)
下面小編就為大家?guī)?lái)一篇PHP獲取IP地址所在地信息的實(shí)例(使用純真IP數(shù)據(jù)庫(kù)qqwry.dat)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11