如何使用Varnish加速我的网站
因为Symfony缓存使用了标准的Http cache headers(HTTP缓存头),Symfony Reverse Proxy (Symfony反向代理)可以很容易地被其他任何反向代理替换。 Varnish 是强大的开源HTTP加速器,支持快速缓存内容并且支持Edge Side Includes。
令Symfony信任反向代理 ¶
Varnish把IP作为 X-Forwarded-For
进行自动转发(automatically forwards),并且在请求中留下了 X-Forwarded-Proto
头。如果你没有把Varnish配置为trusted proxy,Symfony将从Varnish主机而不是真实客户端中,把全部请求看作是“传入的不安全连接”。
记得,在Symfony配置信息中设置 framework.trusted_proxies,以便Varnish可以被看成是一个trusted proxy,同时使用的是 X-Forwarded 头。
Varnish在默认的配置中发送 X-Forwarded-For
头但不过滤 Forwarded
头。如果你有Varnish配置文件的访问权限,可以配置Varnish来删除 Forwarded
头:
1 2 3 |
sub vcl_recv {
unset req.http.Forwarded;
} |
如果你不能访问到Varnish配置信息,你可以配置Symfony来不信任 Forwarded
头,如同 如何配置Symfony才能让它在负载均衡和反向代理背后工作 一文所述。
路由和X-FORWARED头 ¶
要确保symfony的路由能够配合Varnish生成正确的URL,X-Forwarded-Port
头必须呈现给Symfony,以便使用正确的端口号。
这个端口号对应的是你设置用来接收外部连接的端口(80
是HTTP连接的默认值)。如果程序也接受HTTPS连接,则可能还有其他代理(因为Varnish本身不做HTTPS)存在于默认的HTTPS 443端口,这个端口是用来处理SSL终止的,同时利用 X-Forwarded-Proto
把请求以“HTTP请求到Varnish”进行转发。本例中,你需要添加下述配置码段:
1 2 3 4 5 6 7 |
sub vcl_recv {
if (req.http.X-Forwarded-Proto == "https" ) {
set req.http.X-Forwarded-Port = "443";
} else {
set req.http.X-Forwarded-Port = "80";
}
} |
cookies和缓存 ¶
默认时,当一个请求以 cookies或basic authentication header 被发送时,一个明智的缓存代理是不去缓存任何东西的。这是因为页面内容被假定为依赖这个cookie值或是authentication头。
如果你确定后端从不使用sessions或basic authentication,可令Varnish从请求中删除相应的头以防止客户端无视缓存。实践中,你的网站总有些地方需要sessions,比如使用 CSRF防护 的表单时。这时,确保 仅当真正需要时才启动session,并当不再需要session时删掉它。另一个办法,你可以参考 对包含了CSRF防护的表单页面进行缓存。
Cookies被JavaScript创建并且只在前端使用,比如,当使用Google访问量统计时,是不发送到服务器的。这些cookies与后台无关并且不应该影响到缓存决策。配置你的Varnish缓存以 清除cookie头。如果有的话,你愿意保留session cooke,同时清除所有其他cookie以便当没有活动的session时,页面能被缓存。除非你改变了默认的PHP配置,你的session cookie将使用PHPSESSID
名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sub vcl_recv {
// Remove all cookies except the session ID. / 删除全部cookies,除了session ID
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
// If there are no more cookies, remove the header to get page cached.
// 如果没有更多的cookies,删除header以令页面得以缓存
unset req.http.Cookie;
}
}
} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
sub vcl_recv {
// Remove all cookies except the session ID. / 删除全部cookies,除了session ID
if (req.http.Cookie) {
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.Cookie == "") {
// If there are no more cookies, remove the header to get page cached.
// 如果没有更多的cookies,删除header以令页面得以缓存
remove req.http.Cookie;
}
}
} |
如果内容并非对每位用户都不相同,但却依赖于用户的roles(译注:此为sf权限系统之角色),一种解决方案是把每个(用户)群组的缓存独立出来。这种模式已经被 FOSHttpCacheBundle 所实现和解释,相关概念是 User Context。
确保一致的缓存行为 ¶
Varnish使用“由你的程序”发送出的缓存头以决定如何缓存内容。但是,Varnish4之前的版本并不遵守 Cache-Control: no-cache
、no-store
和private
。为了确保行为一致,若你仍在使用Varnish 3,使用下列配置:
1 2 3 4 5 6 7 8 9 10 11 |
sub vcl_fetch {
/* By default, Varnish3 ignores Cache-Control: no-cache and private
https://www.varnish-cache.org/docs/3.0/tutorial/increasing_your_hitrate.HTML#cache-control
*/
if (beresp.http.Cache-Control ~ "private" ||
beresp.http.Cache-Control ~ "no-cache" ||
beresp.http.Cache-Control ~ "no-store"
) {
return (hit_for_pass);
}
} |
通过VCL文件你可以看到Varnish的默认行为:Varnish 3是 default.vcl,Varnish 4是 builtin.vcl。
开启Edge Side Includes(ESI) ¶
如同在 Edge Side Includes章节 所解释的,Symfony会探查是否有和“能理解ESI的反向代理”的对话。当你使用Symfony反向代理时,毋须做任何事。但如果令Varnish取代Symfony来解析ESI标签,你需要在Varnish中进行一些配置。Symfony使用的是来自Akamai所描述的 Edge Architecture(Edge架构)的 Surrogate-Capability
头。
Varnish支持ESI的 src
属性(onerror
和 alt
属性则被忽略)。
首先,配置Varnish,以便它能够为“被转发至(forwarded to)后端程序”的请求添加一个 Surrogate-Control
头,来声明它对ESI的支持:
1 2 3 4 5 |
sub vcl_recv {
// Add a Surrogate-Capability header to announce ESI support.
// 添加一个Surrogate-Capability header来声明对ESI的支持
set req.http.Surrogate-Capability = "abc=ESI/1.0";
} |
头信息的abc
部分并不重要,除非你有多个“surrogates”(代理)需要其能力。参考 Surrogate-Capability Header 以了解细节。
然后,优化Varnish,以便在使用由Symfony自动添加的 Surrogate-Control
头来检查“至少存在一个ESI标签”的时候,它只传递响应内容:
1 2 3 4 5 6 7 8 |
sub vcl_backend_response {
// Check for ESI acknowledgement and remove Surrogate-Control header
// 检查ESI的确认并删除Surrogate-Control头
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
} |
1 2 3 4 5 6 7 8 |
sub vcl_fetch {
// Check for ESI acknowledgement and remove Surrogate-Control header
// 检查ESI的确认并删除Surrogate-Control头
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
} |
如果你遵循了“确保缓存行为一致”之建议,VCL函数已经在那里。只需把那些代码附加到函数最末,它们将不会各自干扰。
缓存失效 ¶
如果你需要缓存内容定期改变,并且仍然能够向用户展示最近的版本,你需要令那个内容“失效”(invalidate)。虽然cache invalidation(缓存失效)允许你在内容过期之前从代理中清除它们,却也增加了你的缓存设置之复杂度。
开源的FOSHttpCacheBundle减轻了“缓存无效化”(cache invalidation)时的痛苦,帮你组织你的缓存设置和失效设置。
FOSHttpCacheBundle文档解释了如何配置Varnish以及用于缓存失效的其他反向代理。