Symfony2框架学习笔记之HTTP Cache用法详解


Posted in PHP onMarch 18, 2016

本文实例讲述了Symfony2框架HTTP Cache用法。分享给大家供大家参考,具体如下:

富web应用程序的本质意味着它们的动态。无论你的应用程序多么有效率,每个请求比起静态文件来说总会存在很多的耗费。对于大多数web程序来说,这没什么。 Symfony2非常的轻快,无论你做些严重超载的请求,每个请求将会得到很快的回复,而不会对你的服务器造成压力。但是随着你站点的成长,负载将成为一个严重的问题。对每个请求处理应该只被正常执行一次。这就是缓存真正要达成的目标。

站在巨人肩膀上的缓存:

提高一个应用程序执行效率的最有效方法是缓存一个页面的所有输出然后让后续的请求绕开整个应用程序。当然,这对于高动态性的站点来说并不是总是可能的。Symfony2 缓存系统是比较特别的,因为它依赖于在HTTP规范中定义的简单强大的HTTP cache。没有重新发明新的缓存方法,Symfony2 拥抱在web上定义基础交流的标准。一旦你理解了基础的HTTP校验和过期缓存模式,你就会完全掌握了Symfony2的缓存系统。

第一步:一个网关缓存(gateway cache),或者反向代理。是一个坐在你应用程序前面的对立的层。反向代理缓存来自于你应用程序的响应并使用这些缓存响应在某些请求到达你应用程序之前来回复它们。 Symfony2提供了自己的反向代理,也可以使用其它任何的反向代理。

第二步:HTTP缓存 (HTTP cache)头用于和网关缓存以及任何其位于客户和你的应用程序之间的其它缓存交流。Symfony2 提供了和缓存头交互的预设行为和强大接口。

第三步:HTTP 超时和校验时用于决定一个缓存内容是否新鲜和陈旧的两种模式。

第四步:ESI(Edge Side Includes)允许HTTP缓存被用于独立缓存页面片段(甚至是嵌套片段)。使用ESI,你甚至可以缓存一个完整的页面60分钟。但一个嵌入式边栏缓存只有5分钟。

使用网关缓存

当使用HTTP缓存时,缓存是跟你的应用程序完全分离的,它位于你的请求客户端和应用程序之间。该缓存的工作就是从客户端接收请求并把它们传递回你的应用程序。同时它也将接收从你的应用程序返回的响应并把它转给客户端。可以说它是你的应用程序和请求客户端之间请求-响应交互的中间人。

按照这个思路,缓存会保存被认为是“可缓存的”每一个响应回复。当同样的请求再次传来时,该缓存会把自己缓存的响应直接回复给请求客户端,而完全忽略你的应用程序。这种类型的缓存就是HTTP网关缓存。目前有很多这类缓存,比如Varnish,Squid in reverse proxy mode和Symfony2 反向代理等。

缓存类型

一个网关缓存不是缓存的唯一类型。事实上,有三种不同类型的缓存会截获并使用你的应用程序发出的HTTP缓存头。它们是:

浏览器缓存(Browser caches):浏览器拥有自己的本地缓存,这对你单击"前一步"或者查看图片和其它网络资产时起到了主要作用。

代理缓存(Proxy caches):一个代理缓存是一个多人位于一人之后的共享的缓存。它们大多是一些大公司或者ISP安装用来减少延迟和网络阻塞的。

网关缓存(Gateway caches):像一个代理,也是一个共享缓存但是是位于服务器端的。一般是网络管理员安装它们,它使得网站更具可伸缩性,可靠性和高效性。网关缓存有时候被称为反向代理缓存,代理缓存,更或者是HTTP加速器。

Symfony2 反向代理

Symfony2拥有一个用PHP编写的反向代理(也叫做网关缓存)。开启它后,来自你应用程序的可缓存的响应回复将会开始被立刻缓存。安装它相也当容易。每一个新的Symfony2应用程序都有一个预配置缓存内核(AppCache)包含了一个默认的AppKernel。该缓存内核就是个反向代理。要开启缓存,修改前端控制器代码使用缓存内核:

// 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包裹默认的AppKernel
$kernel = new AppCache($kernel);
$kernel->handle(Request::createFromGlobale())->send();

缓存内核会立刻扮演一个反向代理的角色,缓存来自你应用程序的回复把它们发回给请求客户端。

注意,该缓存内核有一个特别的getLog()方法返回一个能够表示在缓存层发生了什么的字符串。

可以在开发环境中来调试和校验你的缓存策略。

error_log($kernel->getLog());

AppCache 对象是一个合理的默认配置,当然你也可以通过重写getOptions()方法来设置可选项对它进行调优。

// 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,
    );
  }
}

注意,这里无论怎么重写getOptions()方法,其中debug选项将被包裹的AppKernel的debug值自动设置。

下面是一些重要的可选项:

default_ttl: 当没有显式的刷新信息在回复中提供时,一个缓冲实体应该被认为是新鲜的时间秒数。显式的设置Cache-Control 或者 Expires 头会覆盖这个参数值。默认值为0。
private_headers:请求头组,它在回复上触发"private" Cache-Control 行为,无论回复是通过Cache-Control 指令显式的声明是public还是private 。默认为Authorization和Cookie。
allow_reload: 指定是否允许客户端通过在请求中指定Cache-Control的"no-cache"指令来强迫缓存重新加载。设置它为true时符合RFC2616规范。默认值为false。
allow_revalidate:指定是否允许客户端通过在请求中指定Cache-Control的"max-age=0"指令来强迫缓存重新校验。设置它为true时符合RFC2616规范。默认值为false。
stale_while_revalidate:用于指定一个默认秒数(间隔是秒因为回复TTL精度是1秒),在期间缓存还在后台进行重新校验时可以立刻返回一个陈旧的回复(默认是2);该设置会被stale-while-revalidate HTTP Cache-Control扩展重写(RFC 5861)。
stale_if_error: 指定一个默认秒数(间隔是秒)在这期间缓存可以提供一个陈旧的回复当遇到一个错误时。默认值为60。该设置会被stale-if-error HTTP Cache-Contorl 扩展重写(RFC5861)

如果debug设置为true,Symfony2 会自动添加一个X-Symfony-Cache 头到回复保存着关于缓存点击和丢失的信息。

从一个反向代理到另一个的转换:

Symfony2反向代理是一个在开发你的站点或者部署你的站点到一个共享主机而你无法安装任何除PHP代码以外的东西时的非常有用的工具。但是因为使用PHP编写,它不能跟用C写成的反向代理那样快速。这就是为什么我们推荐使用Varnish或者Squid到你的运营服务器上的原因。好消息是从一个代理服务器到另外一个替换很容易,简单的不用你修改任何程序代码。你可以开始时使用Symfony2的反向代理等到了阻塞增加时升级到Varnish。

注意:Symfony2 反向代理执行效率独立于应用程序的复杂性。因为应用程序核心仅仅在请求需要被转发到它时才被启动。

HTTP缓存说明:

为了发挥可用缓存层的优势,你的应用程序必须能传达它的哪个回复可以被缓存,什么时候/怎样 缓存会变成陈旧的规则。这些是通过在回复(response)上设置HTTP 缓存头来实现的。

记住,"HTTP"只不过是一种web客户端和服务器之间交流的简单文本语言。当我们说HTTP 缓存时,我们说的是它允许客户端和服务器交换信息相关的缓存。

HTTP指定了4个Response缓存头,我们需要关注一下:

Cache-Control
Expires
ETag
Last-Modified

其中最重要的也是万能的头是Cache-Control头,它其实是一个各种缓存信息的集合。

Cache-Control Header

Cache-Control头是唯一的一个其内部包含了各种各样的关于一个response是否可以被缓存的信息。每条信息之间用逗号隔开。

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

Symfony 提供一个Cache-Control头的抽象,使它的创建更加可控。

$response = new Response();
// 标记response为public还是private
$response->setPublic();
$response->setPrivate();
// 设置private或者shared 的最大年龄 age
$response->setMaxAge(600);
$response->setSharedMaxAge(600);
// 设置一个自定义的Cache-Control 指令
$response->headers->addCacheControlDirective('must-revalidate', true);

公共vs私有 Response

网关缓存和代理缓存都被认为是“共享”缓存,因为它们缓存的内容是被多用户共享的。如果一个特定用户的回复曾被错误的存储到共享缓存中,它以后可能被返回给无数的不用用户。想象一下,如果你的账户信息被缓存然后返回给每一个后来请求他们自己账户页面的用户。要处理这种情况,每个回复可能都要设置时public还是private。

public 说明给回复可能被private和共享的缓存保存。
private 说明所有的或者部分的回复信息时给一个单独用户的,所以不能缓存到共享缓存中。

Symfony 谨慎地默认每个回复为private。 要使用共享缓存的优点(比如Symfony2反向代理),回复必须被显式的设置为public。

安全方法:

HTTP缓存仅仅为安全方法工作(比如GET和HEAD)。要安全意味着当它为某个请求服务时从来不会改变服务器上应用程序的状态。(当然你可以写日志信息,缓存数据等)。这里有两个很合理的后果(consequences):

当你的应用程序回复一个GET或者HEAD请求时,你绝对不会改变你应用程序的状态。即使你不用网关缓存,代理缓存的存在意味着任何GET和HEAD请求可能会或者可能不会真的达到你的服务器。

不要期望PUT,POST或者DELETE方法被缓存。这些方法被使用意味着你应用程序状态的改变。缓存它们将阻止某种请求访问或者改变你的应用程序。

缓存规则和默认设置

HTTP 1.1 默认情况下允许缓存任何事情除非有一个显式的Cache-Control头。实践中,大多数缓存当请求有cookie,一个授权头,使用一个非安全的方法(比如PUT,POST,DELETE)或者当请求有一个重定向代码时,不会进行任何缓存活动。

当开发者没有做任何设置时,Symfony2 会自动按照下面的规则设置一个合理的比较保守的Cache-Control头:

如果没有缓存头被定义(Cache-Control,Expires,ETag 或者Last-Modified),Cache-Control被设置为no-cache,意味着该response将不会被缓存。

如果Cache-Control 为空(但是有另一个缓存头存在),它的值被设置为private,must-revalidate;

如果至少一个Cache-Control指令被设置,并且没有'public'或者‘private'指令被显式的添加,Symfony2 会自动添加一个private指令(除去s-maxage 被设置的情况)。

HTTP过期和校验

HTTP规范定义了两个缓存模型:
过期模型,你只需要通过包含一个Cache-Control和/或者一个Expires头来指定一个Response应该多长时间被考虑“新鲜”问题。缓存理解过期将不再让相同的请求回复,直到缓存的版本达到它过期时间成为“stale"陈旧。

校验模型,当页面时真正的动态页面时(他们的展现经常变化),校验模型就经常需要了。这种模型,缓存存储response,但是要求服务对每个请求是否缓存response依然进行校验。

应用程序使用唯一的response 标示符(ETag 头) 和/或者 时间戳(Last-Modified 头)来检查页面自从被缓存后是否放生了变化。

这两个模型的目标是通过依靠一个缓存存储并返回"新鲜" response,使得应用程序从不生成相同的response两次。

过期:

过期模型是在这两个模型中是更加有效和简单明确的模型,它应该在任何时候都有被使用的可能。当一个response使用一过期方式被缓存,缓存将存储response并为请求直接返回它而不去访问应用程序,直到它过期。

过期模型可以被熟练的使用一两个,几乎相同的,HTTP头:比如 Expires或cache - control。

过期和Expires 头

根据HTTP规范,Expires头字段提供一个日期/时间,过了这个日期或时间后它的response就被认为是陈旧的了。Expires头可以被Response的setExpires()方法设置。它要求一个DateTime实例作为输入参数。

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

生成的HTTP头的结果如下:

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

注意,因为规范的需要setExprise()方法会自动把日期转换为GMT时区。

我们注意到在HTTP规范1.1版之前,源服务不需要发送一个Date头。 因此缓存(比如浏览器)可能需要依靠它本地的始终来评估Expires头,造成计算生命周期时时钟偏差。 Expires头的另一个限制是规范规定:"HTTP/1.1服务不应该发送Expires日期未来超过一年。"

过期和Cache-Control 头

因为Expires头的限制,大多时候,你应该采用Cache-Control头来替代它。回想一下,Cache-Control头是用来指定多个不同缓存指令的。对于过期来说,有两个指令,max-age 和 s-maxage。第一个被所有的缓存使用,然而第二个仅仅被用于共享缓存。

// 设置一个秒数,过了这个秒数后response就被认为是陈旧的了。
$response->setMaxAge(600);
// 同上,但是只用于共享缓存。
$response->setSharedMaxAge(600);
Cache-Control头将使用如下格式(它可能还有其它指令):
Cache-Control: max-age=600, s-maxage=600

校验:

一旦底层数据发生变化需要立刻对缓存资源进行更新时,过期模型就显得力不从心了。在过期模型下,应用程序不会被要求返回更新的response直到缓存最后过期变为陈旧内容以后。

校验模型解决了这个问题。在校验模型下,缓存持续保存response。不同的是,对每一个请求request,缓存都询问应用程序缓存的response是否依然有效。如果缓存仍然有效,你的应用程序应该返回一个304状态码和一个空内容。这告诉缓存它可以为请求用户返回它缓存的response。

在这个模型下,你主要节省了带宽因为描述不会发送两次到相同的客户端(而是发送一个304回复代替)。但是,如果你仔细设计你的应用程序,你可能能忍受304 response需要的最小数据并节省CPU。

304状态码意味着没有修改。它很重要因为它没有包含整整的被请求内容,而只是一个轻量级的导向集,它告诉缓存它应该使用它现在保存的版本回复请求。跟过期类似,也有两个不同的HTTP头可以被用来实现校验模型: ETag和Last-Modifed

校验和ETag头

ETag头是一个字符串(也叫"entity-tag")它是目标资源一个表现的唯一标识。它完全由你的应用程序来生成和设置。 比如,如果 /about 资源被缓存保存时取决于日期和你应用程序的返回内容。一个ETag像一个手印,被用来快速的比较一个资源的两个不同版本是否等效。
像手印,同一个资源的所有表示形式中每个ETag必须是唯一的。让我们来简单实现一个生成ETag使用md5加密的回复内容作为内容:

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一起发送的ETag与Response上的ETag进行比较。如果两个匹配,方法自动设置Response状态码为304。

这个算法非常简单也非常通用,但是你需要在能计算ETag之前创建一个完整的Response,校验模型是次优选择。换句话说,它节省了带宽,单没有节省CPU利用。  

Symfony2还通过向setETag()方法传入true作为第二个参数,来支持弱ETag。

校验和Last-Modified 头

Last-Modified头是校验模型的第二种形式。根据HTTP规范,”Last-Modified 头字段指定日期和时间,在这个时间源服务器相信该表现是最后被修改版。“
换句话说,应用程序决定基于自动缓存内容被缓存后是否被更新过来判断缓存的内容是否要被更新过。举个例子,你可以使用最新更新日期为所有需要计算资源表现的对象作为Last-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() 方法比较请求Request中的If-Modified-Since头和Response中的Last-Modified 头。如果他们相等,Response会被设置一个304状态码。

注意,If-Modified-since 请求头等于最终发送到客户端特定资源Last-Modified头。这就是如何客户端和服务端相互交流决定资源自从它被缓存后是否被更新。

使用校验优化你的代码:

任何缓存策略的主要目的都是减轻应用程序的加载。换句话说,你的应用程序做的越少来返回304 response,越好。Response::isNotModified()方法通过暴露一个简单有效的模式做到了。

public funcation showAction($articleSlug)
{
  //获取最小信息来计算ETag或者Last-Modified值(基于Request,数据是从数据库或者一个键值对存储实例中获取。
  $article = //...
  //创建一个Response带有一个ETag 和/或者 一个Last-Modified 头
  $response = new Response();
  $response->setETag($article->computeETag());
  $response->setLastModified($article->getPublishedAt());
  //为给定的Request检查Response没有被修改
  if($response->isNotModified($this->getRequest())){
    //立刻返回304 Response
    return $response;
  }else{
    //做一些更多的工作-比如获取更多的数据
    $comment=//...
    //或者用你已经开启的$response渲染一个模版
    return $this->render('MyBundle:MyController:article.html.twig',
        array('article'=>$article, 'comments' =>$comments),
        $response
    );
  }
}

当Response没有被修改后,isNotModified()自动设置response的状态码为304,移除response的内容,移除一些不需要为304存在的头。

不同的回复响应:

到目前为止,我们已经假设了每个URI只有一个目标资源的表示。默认情况下,HTTP缓存通过使用URI的资源作为缓存键被执行。如果两个人请求同一个可缓存资源的URI,第二个用户将获取缓存版本。有时候这些不够,不同版本的用一个URI需要被按照一个或者多个请求头的值来被缓存。举个例子,如果当客户端支持你压缩页面时,任何给定的URI都有两种表示:一个是客户端支持压缩时,一个是不支持时的表示。这时候请求头的Accept-Encoding值将决定使用哪个。

在这种情况下,我们需要回复的特定URI缓存一个压缩版本和一个非压缩版本,基于请求的Accept-Encoding值返回它们。这是通过Vary Response头,Vary是一个不同头用逗号分隔,它的值触发请求资源的不同表示。

Vary:Accept-Encoding,User-Agent

注意,这个特别的Vary头,将基于URI和Accept-Encoding和User-Agent 请求头为每个资源的不同版本进行缓存。

Response对象提供一个干净的接口来管理Vary 头:

// 设置一个vary 头
$response->setVary('Accept-Encoding');
// 设置多个vary头
$response->setVary(array('Accept-Encoding', 'User-Agent'));

setVary()方法需要一个头名字或者一个头名字数组对应不同的response。

过期和校验:

你当然可以在同一个Response中同时使用校验和过期。因为过期胜过校验,你可以轻易的从它们两个中根据好处做出选择。换句话说,通过同时使用过期和校验,你可以指示缓存服务于缓存的内容,同时后台间隔检查来调查内容是否依然合法。

更多Response方法:

Response类提供了许多和缓存相关的方法。下面是主要的一些:

// 标志Response过期陈旧
$response->expire();
// 强迫response返回一个适合 304 的没有内容的response
$response->setNotModified();

另外,跟缓存最相关的HTTP头可以被通过一个单独的方法setCache()设置。

// 通过一个调用设置缓存参数
$response->setCache(array(
  'etag'     => $etag,
  'last_modified' => $date,
  'max_age'    => 10,
  's_maxage'   => 10,
  'public'    => true,
  // 'private'  => true,
));

使用ESI(Edge Side Includes)

网关缓存是一个提高你网站执行效率的很好的途径。但是它们有一个限制:只能缓存整个页面。如果你不想缓存整个页面或者页面的某一部分很动态,你就没那么幸运了。

幸运的是,Symfony2为这些情况提供一个解决方案,基于ESI技术。它允许页面指定的部分和主页比起来有一个不同的缓存策略。

ESI规范描述标签你可以嵌入到你的页面来和网关缓存交流。Symfony2中只实现了一个标签,include, 因为这是唯一一个能在Akami上下文之外使用的标签。

<html>
  <body>
    Some content
    <!-- 嵌入一个其它页的内容 -->
    <esi:include src="http://..." />
    More content
  </body>
</html>

从这个例子中注意到每个ESI标签有一个全限定URL。一个ESI标签表示可以通过一个给定的URL获取的一个页面片段。

当请求被处理时,网关缓存从它的缓存或者从背后的应用程序中请求回复获取整个页面。换句话说,网关缓存既从缓存中获取包含的页面片段也会再次从背后的应用程序中获取回复请求的页面片段。当所有的ESI标签被解析后,网关缓存合并每一个ESI内容到一个主页并返回最后的内容到客户端。所有的这一切都透明的发生在网关缓存级(在你的程序外)。你将看到,如果你选择ESI标签,Symfony2让这包含它们的这一过程几乎不费劲。

在Symfony2中使用ESI

首先,使用ESI需要确认在你的应用程序配置中已经打开。

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),
));

现在假设我们有一个页面时相对静态的,除了一个新闻自动收报机在内容的底部。使用ESI,我们可以缓存新闻自动收报机独立于页面其它部分。

public function indexAction()
{
  $response = $this->render('MyBundle:MyController:index.html.twig');
  $response->setSharedMaxAge(600);
  return $response;
}

在该示例中,我们给全页面缓存周期为10分钟。接下来,通过嵌入一个action让新闻ticker包含到模板中。这是通过render帮助来实现的。因为嵌入的内容来自其它页面,Symfony2使用一个标准的render帮助来配置ESI标签:

Twig格式:

{% render '...:news' with {}, {'standalone': true} %}

PHP格式:

<?php echo $view['actions']->render('...:news', array(), array('standalone' => true)) ?>

通过把standalone设置为true,告诉Symfony2这个action应该被渲染为一个ESI标签。

你可能想知道为什么要使用一个helper方法来代替直接写ESI标签。这是因为使用helper让你的应用程序工作即使没有网关缓存被安装。让我们来看看它是怎样工作的。

当standalone为false时(也是默认值),Symfony2在发送response到客户端之前合并包含的页面内容到一个主页。

但是当standalone为true时,并且如果Symfony2发现它跟支持ESI的网关缓存对话时,它生成一个ESI include标签。

如果没有网关缓存或者网关缓存不支持ESI,Symfony2将只合并包含的标签页面内容到一个主要的像它在standalone为false时所做的一样。

嵌入的action现在可以指定自己的缓存规则了,完全独立于主页。

public function newsAction()
{
  //...
  $response->setShareMaxAge(60);
}

使用ESI,整个页面缓存将被保持600秒有效,但是新闻组建缓存将只持续60秒。

ESI的一个必备条件是嵌入的action可以通过一个URL被访问,这样网关缓存才可以独立于页面其它部分获取它。当然,一个action不能被通过一个URL访问除非有一个路由指向它。Symfony2 通过一个通用的路由和controller负责这个。

为了ESI包含标签能正常的工作,你必须定义_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;

因为路由允许所有的action通过一个URL被访问,你可以通过使用Symfony2防火墙(允许访问你的反向代理的IP范围)内容保护它。

缓存策略的一大优势是你可以让你的应用程序根据动态的需要同时又尽量的减少触及应用程序。

一旦你开始使用ESI,请记住一定使用s-maxage指令代替max-age。因为浏览器只接受聚合的资源,它不知道子组件,所以它会按照max-age指令缓存整个页面。这是你不希望它做的。

render helper支持的两外两个有用选项:
alt:用作ESI标签的alt属性,当src找不到时,它允许你指定一个替代URL。
ignore_errors:如果设置为true,一个onerror属性将被添加到ESI,并且属性值设置为continue,在一个失败事件中,网关缓存将只默默的移除ESI标签。

缓存失效:

“计算机科学中有两大难题:缓存失效和命名事物”---Phil Karlton

你永远都不需要失效缓存数据,因为失效早已在HTTP缓存模型中被考虑到了。如果你使用校验,你永远都不需要通过定义校验任何事情;如果你使用过期并失效某个资源,它意味着你设置一个未来的过期日期。因为在任何类型的反向代理中失效都是一个顶级规范,如果你不担心失效,你可以在不改变任何应用程序代码的情况下在反向代理间切换。

其实,所有的反向代理都提供了清除缓存数据的方式,但是你需要尽量的避免使用它们。最标准的清除给定URL的缓存的方式是通过指定请求的HTTP方法为PURGE 来进行。

下面是如何配置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;
  }
}

注意,你必须保护你的PURGE HTTP方法以避免随便一个人使用某些方法清除你的缓存数据。

总结:

Symfony2旨在遵循一条被证明了的道路规则:HTTP。 缓存也不例外。掌握Symfony2缓存系统意味着熟悉HTTP缓存模式和有效的使用它们。

这就意味着,你不能只依赖于symfony2文档和代码示例,你必须了解有关HTTP缓存和网关缓存的更宽阔的知识,比如Varnish。

希望本文所述对大家基于Symfony框架的PHP程序设计有所帮助。

PHP 相关文章推荐
php5.2.0内存管理改进
Jan 22 PHP
PHP 截取字符串 分别适合GB2312和UTF8编码情况
Feb 12 PHP
php MYSQL 数据备份类
Jun 19 PHP
php下连接mssql2005的代码
Jan 17 PHP
PHP的简易冒泡法代码分享
Aug 28 PHP
PHP统计二维数组元素个数的方法
Nov 12 PHP
CI框架中zip类应用示例
Jun 17 PHP
destoon实现底部添加你是第几位访问者的方法
Jul 15 PHP
PHP中array_slice函数用法实例详解
Nov 25 PHP
PHP中Enum(枚举)用法实例详解
Dec 07 PHP
微信封装的调用微信签名包的类库
Jun 08 PHP
Laravel中GraphQL接口请求频率实战记录
Sep 01 PHP
解读PHP的Yii框架中请求与响应的处理流程
Mar 17 #PHP
PHP的Yii框架中使用数据库的配置和SQL操作实例教程
Mar 17 #PHP
编写PHP程序检查字符串中的中文字符个数的实例分享
Mar 17 #PHP
实例讲解如何在PHP的Yii框架中进行错误和异常处理
Mar 17 #PHP
解析PHP的Yii框架中cookie和session功能的相关操作
Mar 17 #PHP
简要剖析PHP的Yii框架的组件化机制的基本知识
Mar 17 #PHP
PHP的Yii框架中YiiBase入口类的扩展写法示例
Mar 17 #PHP
You might like
PHP父类调用子类方法的代码例子
2014/04/09 PHP
枚举JavaScript对象的函数
2006/12/22 Javascript
微博@符号的用户名提示效果。(想@到谁?)
2010/11/05 Javascript
在jQuery 1.5中使用deferred对象的代码(翻译)
2011/03/10 Javascript
JavaScript 对任意元素,自定义右键菜单的实现方法
2013/05/08 Javascript
js浮点数精确计算(加、减、乘、除)
2013/12/26 Javascript
javascript 通用loading动画效果实例代码
2014/01/14 Javascript
JavaSript中变量的作用域闭包的深入理解
2014/05/12 Javascript
兼容主流浏览器的jQuery+CSS 实现遮罩层的简单代码
2014/10/14 Javascript
JavaScript入门系列之知识点总结
2016/03/24 Javascript
谈一谈JS消息机制和事件机制的理解
2016/04/14 Javascript
浅谈jquery页面初始化的4种方式
2016/11/27 Javascript
js实现密码强度检验
2017/01/15 Javascript
关于AngularJs数据的本地存储详解
2017/01/20 Javascript
在antd4.0中Form使用initialValue操作
2020/11/02 Javascript
Python+threading模块对单个接口进行并发测试
2019/06/25 Python
Python画图实现同一结点多个柱状图的示例
2019/07/07 Python
Python 从subprocess运行的子进程中实时获取输出的例子
2019/08/14 Python
python实现淘宝购物系统
2019/10/25 Python
Pytorch释放显存占用方式
2020/01/13 Python
Python小整数对象池和字符串intern实例解析
2020/03/21 Python
python字典的值可以修改吗
2020/06/29 Python
Booking.com德国:预订最好的酒店和住宿
2020/02/16 全球购物
澳大利亚家具商店:Freedom
2020/12/17 全球购物
英语专业大学生求职简历的自我评价
2013/10/18 职场文书
销售工作岗位职责
2013/12/24 职场文书
最新个人职业生涯规划书
2014/01/22 职场文书
毕业证委托书范文
2014/09/26 职场文书
2014年学生工作总结
2014/11/20 职场文书
优秀共青团员事迹材料
2014/12/25 职场文书
总经理助理岗位职责
2015/01/31 职场文书
十七岁的单车观后感
2015/06/12 职场文书
个人催款函范文
2015/06/24 职场文书
2015年教务主任工作总结
2015/07/22 职场文书
大学生奶茶店创业计划书
2019/06/25 职场文书
利用javaScript处理常用事件详解
2021/04/14 Javascript