使用Edge Side Include
Gateway cache是一种很好的方式,能够令你的网站表现更佳。但它们有一个缺点:只能缓存整个页面。如果不想缓存全部页面,或者页面中的某些部分是“更为动态”的,你就没办法了。幸运的是,Symfony为这些场景提供了一种解决方案,基于一种被称为ESI的技术,或者叫Edge Side Includes。Akamai在十年前写了这个协议,允许页面的特定部分拥有一种不同于整个页面的缓存策略。
ESI协议,描述了你可以嵌入页面的标签,用来与gateway cache进行通信。只有一个标签被Symfony实现,即include
,因为这是在Akamai上下文之外唯一有用的。
注意,例子中每个ESI标记都有一个fully-qualified的URL(译注:绝对完整路径)。一个ESI标记,展现的是一个可以通过给定URL取出的页面片段。
当一个请求被处理时,gateway cache从其缓存中取出整个页面,或者通过后台程序取出。如果响应中包含了一或多个ESI标记,它们都将被以相同方式处理。也就是说,gateway cache既从缓存中也通过再次请求后台程序来取出页面中的包容片段。当所有的ESI标记被解析之后,网关缓存合并每一个片段到页面中,然后发送最终内容到客户端。
在Symfony中使用ESI ¶
首先,要使用ESI必须先在程序级配置中开启它:
1 2 3 4 |
# app/config/config.yml
framework:
# ...
esi: { enabled: true } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/symfony"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<!-- ... -->
<framework:esi enabled="true" />
</framework:config>
</container> |
1 2 3 4 5 |
// app/config/config.php
$container->loadFromExtension('framework', array(
// ...
'esi' => array('enabled' => true),
)); |
现在,假设你有一个页面,它是相对静态的,除了底部的一些新闻播报。使用ESI,你可以将新闻播报的缓存独立于页面其他部分之外。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// src/AppBundle/Controller/DefaultController.php
// ...
class DefaultController extends Controller
{
public function aboutAction()
{
$response = $this->render('static/about.html.twig');
// set the shared max age - which also marks the response as public
// 设置共享最大周期 - 它将标记响应为public
$response->setSharedMaxAge(600);
return $response;
}
} |
本例中,全页缓存是10分钟的周期。接下来,在模板中内嵌action来包容新闻播报。这可以通过render
helper来实现。(参考Embedding Controller以了解细节)
当嵌入的内容从另外的页面(或者是控制器)过来之后,Symfony使用标准的render
helper来配置ESI标签:
1 2 3 4 5 6 7 8 |
{# app/Resources/views/static/about.html.twig #}
{# you can use a controller reference #}
{# 在此引用一个控制器 #}
{{ render_esi(controller('AppBundle:News:latest', { 'maxPerPage': 5 })) }}
{# ... or a URL 或者是一个URL #}
{{ render_esi(url('latest_news', { 'maxPerPage': 5 })) }} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- app/Resources/views/static/about.html.php -->
<!-- you can use a controller reference -->
<?php echo $view['actions']->render(
new \Symfony\Component\HttpKernel\Controller\ControllerReference(
'AppBundle:News:latest',
array('maxPerPage' => 5)
),
array('strategy' => 'esi')
) ?>
<!-- ... or a URL -->
<?php echo $view['actions']->render(
$view['router']->url('latest_news', array('maxPerPage' => 5)),
array('strategy' => 'esi'),
) ?> |
通过使用esi
render(借助render_esi
Twig函数),你告之Symfony这个action应当作为一个ESI标记来渲染。你可能好奇为何要使用helper而不是自己书写ESI标记。这是因为,使用helper可以令你的程序正常运行,哪怕并gateway cache根本没有被安装。
下面你将会看到,你传入的maxPerPage
变量是可以作为你的控制器参数来使用的(也就是$maxPerPage
)。被render_esi
传入的变量,也将成为缓存的键的一部分,以便你能够为“变量-值”组合设置独立的缓存。
当使用默认的render
函数(或者设置renderer为inline
)时,Symfony在发送响应到客户端之前,会合并被包容的页面内容到主要页面中。但是如果你使用esi
renderer(也就是调用renderer_esi
),并且Symfony检测到它要调用支持ESI的gateway cache的话,程序会生成一个ESI包容标签。但如果没有gateway cache或者缓存不支持ESI,Symfony将直接合并页面内容到主要页面,就像你使用render
时的程序行为一样。
Symfony之所以能够检测到网关缓存是否支持ESI,全靠另外一个被Symfony反向代理支持得极好的的Akamai协议。
内嵌的action,现在已经可以指定他的缓存规则了,完全独立于宿主页面之外:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/AppBundle/Controller/NewsController.php
namespace AppBundle\Controller;
// ...
class NewsController extends Controller
{
public function latestAction($maxPerPage)
{
// ...
$response->setSharedMaxAge(60);
return $response;
}
} |
在ESI的加持下,全页缓存是600秒的有效期,但是新闻播送部分的缓存,将仅持续60秒。
当(在模板中)使用控制器的引用时,ESI标签会引用这个内嵌action作为一个可以访问的URL,这样的话,网关缓存才能够把它单独地从页面中取出,而有别于其他内容。Symfony负责为任何一个controller reference生成独立URL,并且能够正确发送路由,多亏了FragmentListener
,这个监听必须在你的配置文件中开启:
1 2 3 4 |
# app/config/config.yml
framework:
# ...
fragments: { path: /_fragment } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/framework"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony
http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<!-- ... -->
<framework:config>
<framework:fragments path="/_fragment" />
</framework:config>
</container> |
1 2 3 4 5 |
// app/config/config.php
$container->loadFromExtension('framework', array(
// ...
'fragments' => array('path' => '/_fragment'),
)); |
ESI renderer的一个特别先进之处在于,你可以令程序尽可能的动态化,同时还能尽可能少地(让请求)命中程序。
片段监听(fragment listener )只对签名请求负责。而请求只在使用fragment renderer和render_esi
Twig函数时才会被签名。
一旦你使用ESI,记得始终要用s-maxage
指令来代替max-age
。因为浏览器只接收聚合资源(aggregated resource),它并不对sub-component子组件保持认知,因此它并不遵守max-age
指令,进而缓存了整个页面。显然你不希望那样。
render_esi
helper还支持以下两个常用选项:
alt
在ESI标记中使用alt
属性,你就可以指定一个可选URL,它将在src
找不到时被使用。
ignore_errors
如果设为true
,一个onerror
属性会被添加到ESI标记中,该属性的值是continue
,用于指示在failure事件中,gateway cache静默移除ESI标记。