如何使用Varnish加速我的网站

3.4 版本
维护中的版本

因为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-cacheno-storeprivate。为了确保行为一致,若你仍在使用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 属性(onerroralt 属性则被忽略)。

首先,配置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以及用于缓存失效的其他反向代理。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)