实现高打击率

现在Varnish已经启动并运行,您可以通过Varnish访问您的Web应用程序了。除非您的应用程序是专门为在Web加速器后工作而编写的,否则您可能需要对配置或应用程序进行一些更改,以便在Varnish中获得高命中率。

Varnish不会缓存您的数据,除非它绝对确定这样做是安全的。因此,为了理解Varnish如何决定是否以及如何缓存页面,我们将通过几个工具来指导您理解Varnish设置中正在发生的事情。

请注意,您需要一个工具来查看在Varnish和后端之间飞行的HTTP头。在Varnish服务器上,完成此操作的最简单方法是使用 VarnishlogVarnish 但有时客户端工具是有意义的。以下是我们常用的几种方法。

工具:varnishtop

您可以使用varnishtop来确定哪些URL对后端的访问最多。 varnishtop -i BereqURL 是一个基本命令,它向您显示Varnish向后端发送的最多的请求。您可以看到一些其他的例子 Varnish 用法在 统计数据

工具:varnishlog

当您确定了一个经常发送到后端的URL时,您可以使用 Varnishlog 看一看这份申请。 varnishlog -q 'ReqURL ~ "^/foo/bar"' 将向您显示来自客户端的请求匹配 /foo/bar

了解更多有关如何 Varnishlog 作品请参阅 登录Varnish 或者是手册页。

工具:LWP-请求

lwp-request 是一个工具,它是Perl的万维网库的一部分。它是几个非常基本的程序,可以执行HTTP请求并向您显示结果。我们主要使用这两个程序, GETHEAD

Vg.no是第一个使用Varnish的网站,在那里运行Varnish的人非常有线索。因此,查看它们的HTTP头是很有趣的。让我们向他们的主页发送GET请求::

$ GET -H 'Host: www.vg.no' -Used http://vg.no/
GET http://vg.no/
Host: www.vg.no
User-Agent: lwp-request/5.834 libwww-perl/5.834

200 OK
Cache-Control: must-revalidate
Refresh: 600
Title: VG Nett - Forsiden - VG Nett
X-Age: 463
X-Cache: HIT
X-Rick-Would-Never: Let you down
X-VG-Jobb: http://www.finn.no/finn/job/fulltime/result?keyword=vg+multimedia Merk:HeaderNinja
X-VG-Korken: http://www.youtube.com/watch?v=Fcj8CnD5188
X-VG-WebCache: joanie
X-VG-WebServer: leon

好的。让我们来看看 GET 的确如此。 GET 通常发送HTTP0.9请求,该请求没有‘主机’标头。因此,我们添加了一个带有‘-H’选项的‘主机’标头。‘-U’打印请求标头,‘-S’打印响应状态,‘-e’打印响应标头,‘-d’丢弃实际内容。我们并不真正关心内容,只关心标题。

如您所见,VG在它们的报头中添加了相当多的信息。有些标题,比如“X-Rick-Will-Never”,是专门针对vg.no和他们有点奇怪的幽默感的。其他的,如‘X-VG-Webcache’是用于调试目的的。

因此,要检查站点是否为特定URL设置了Cookie,只需执行::

GET -Used http://example.com/ |grep ^Set-Cookie

工具:实时HTTP标头

还有一个名为Firefox的插件 Live HTTP Headers 。该插件可以向您显示正在发送和接收的报头。 Live HTTP Headers 可以在https://addons.mozilla.org/en-US/firefox/addon/3829/或通过谷歌搜索“Live HTTP Header”找到。

HTTP标头的作用

伴随着每个HTTP请求和响应的是一串携带元数据的头。Varnish将查看这些标头,以确定缓存内容是否合适,以及Varnish可以将内容缓存多长时间。

请注意,当Varnish考虑这些标头时,Varnish实际上认为自己 part of 实际的网络服务器。理由是两者都在你的控制之下。

这一术语 surrogate origin cache 并不是由IETF或RFC2616很好地定义的,所以Varnish的各种工作方式可能与您的预期不同。

让我们来看看你应该注意的重要标题:

曲奇饼

在默认配置中,Varnish将不会缓存来自后端且存在‘Set-Cookie’头的对象。此外,如果客户端发送Cookie标头,Varnish将绕过缓存,直接转到后端。

这可能过于保守了。许多网站使用Google Analytics(GA)来分析它们的流量。GA设置了一个Cookie来跟踪你。该cookie由客户端Java脚本使用,因此服务器不感兴趣。

来自客户端的Cookie

对于许多Web应用程序来说,完全忽略Cookie是有意义的,除非您正在访问网站的特殊部分。中的此VCL代码片段 vcl_recv 将忽略Cookie,除非您正在访问 /admin/ **

if (!(req.url ~ "^/admin/")) {
    unset req.http.Cookie;
}

很简单。然而,如果您需要做一些更复杂的事情,比如从几个cookie中取出一个,事情就会变得困难。不幸的是,Varnish没有很好的工具来操作Cookie。我们必须使用正则表达式来完成这项工作。如果您熟悉正则表达式,您就会明白是怎么回事。如果你不是,我们建议你要么拿起一本关于这个主题的书,读一读 pcre2pattern 手册页,或通读许多在线指南中的一个。

让我们以Varnish Software(VS)Web为例。非常简单的设置和使用可以被描述为一个基于Drupal的后端,前端有一个Varnish缓存。VS使用一些Cookie来进行Google Analytics跟踪和类似的工具。这些Cookie都已设置好,并由Java脚本使用。Varnish和Drupal不需要看到这些Cookie,并且由于Varnish将在客户端发送Cookie时停止缓存页面,因此Varnish将在VCL中丢弃这些不必要的Cookie。

在以下VCL中,我们将丢弃所有以下划线开头的Cookie:

# Remove has_js and Google Analytics __* cookies.
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", "");
# Remove a ";" prefix, if present.
set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");

让我们看一个示例,其中我们删除了名为“COOKIE1”和“Cookie2”的Cookie之外的所有内容,您可以惊叹于它的“美”:

sub vcl_recv {
    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, ";(COOKIE1|COOKIE2)=", "; \1=");
        set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");

        if (req.http.Cookie == "") {
            unset req.http.Cookie;
        }
    }
}

下面是一个更简单的示例,它可以实现几乎相同的功能。它不是过滤掉“其他”Cookie,而是挑选出所需的“一个”Cookie,将其复制到另一个标头,然后将其复制回请求,删除原始的Cookie标头。

sub vcl_recv {
    # save the original cookie header so we can mangle it
    set req.http.X-Varnish-PHP_SID = req.http.Cookie;
    # using a capturing sub pattern, extract the continuous string of
    # alphanumerics that immediately follows "PHPSESSID="
    set req.http.X-Varnish-PHP_SID =
       regsuball(req.http.X-Varnish-PHP_SID, ";? ?PHPSESSID=([a-zA-Z0-9]+)( |;| ;).*","\1");
    set req.http.Cookie = req.X-Varnish-PHP_SID;
    unset req.X-Varnish-PHP_SID;
}

在Varnish缓存Wiki中,还有其他可怕的例子可以在VCL中完成什么。

来自后端的Cookie

如果您的后端服务器使用‘Set-Cookie’头设置Cookie,则在使用默认配置时,Varnish将不会缓存页面。一个 hit-for-miss 对象(请参见 VCL操作 )被创建。因此,如果后端服务器行为愚蠢并设置了不需要的Cookie,只需取消设置‘Set-Cookie’头,一切都应该没有问题。

缓存控制

“Cache-Control”标头指示缓存如何处理内容。Varnish关心的是 max-age 参数,并使用该参数计算对象的TTL。

因此,请确保发出带有max-age标头的“缓存控制”标头。您可以查看Varnish Software的Drupal服务器发出的命令:

$ GET -Used http://www.varnish-software.com/|grep ^Cache-Control
Cache-Control: public, max-age=600

年龄

Varnish添加了一个‘Age’标头来指示对象在Varnish中保留了多长时间。你可以把“Age”中的“Age”去掉 Varnishlog 使用 varnishlog -I RespHeader:^Age

普拉格玛

HTTP 1.0服务器可能会发送标头 Pragma: nocache 。VARNISH忽略此标头。您可以很容易地在VCL中添加对此标头的支持。

在……里面 vcl_backend_response **

if (beresp.http.Pragma ~ "nocache") {
    set beresp.uncacheable = true;
    set beresp.ttl = 120s; # how long not to cache this url.
}

授权

如果Varnish看到‘Authorization’标头,它将传递请求。如果这不是您想要的,您可以取消设置标题。

覆盖生存时间(TTL)

有时,您的后端会行为不端。根据您的设置,覆盖Varnish中的TTL可能比修复有些麻烦的后端更容易。

您需要VCL来标识您想要的对象,然后将‘beresp.ttl’设置为您想要的任何内容::

sub vcl_backend_response {
    if (bereq.url ~ "^/legacy_broken_cms/") {
        set beresp.ttl = 5d;
    }
}

本例将您站点上的旧遗留内容的TTL设置为5天。

强制对某些请求和某些响应进行缓存

由于您可能仍然有这个笨重的后端,使用起来不是很友好,所以您可能想要覆盖更多的Varnish内容。我们建议您尽可能多地依赖默认缓存规则。强制Varnish在缓存中查找对象非常容易,但实际上并不推荐这样做。

规范您的命名空间

有些站点是通过大量的主机名访问的。Http://www.varnish-software.com/http://varnish-software.com/和http://varnishsoftware.com/都指向同一站点。因为Varnish不知道它们是相同的,所以Varnish将为每个主机名缓存每个页面的不同版本。您可以通过设置重定向或使用以下VCL在您的Web服务器配置中缓解此问题:

if (req.http.host ~ "(?i)^(www.)?varnish-?software.com") {
    set req.http.host = "varnish-software.com";
}

Http变化

HTTP Vary is not a trivial concept. It is by far the most misunderstood HTTP header.

许多响应头告诉客户端有关正在交付的HTTP对象的一些信息。客户端可以根据自己的喜好请求不同的HTTP对象变体。他们的偏好可能包括编码或语言等内容。当客户更喜欢英国英语时,这通过 Accept-Language: en-uk 。缓存需要将这些不同的变量分开,这是通过HTTP响应头‘Variable’来完成的。

当后端服务器发出 Vary: Accept-Language 它告诉Varnish,它需要为来自客户端的每种不同的接受语言缓存一个单独的版本。

如果两个客户端分别表示他们接受语言“en-us,en-UK”和“da,de”,如果后端指示Varnish需要改变‘Accept-Language’头,则Varnish将缓存并提供页面的两个不同版本。

请注意,“不同”引用的标头需要匹配 exactly 才会有匹配的机会。因此,Varnish将保留一页的两份副本,如果其中一份是为“en-us,en-uk”创建的,另一份是为“en-us,en-uk”创建的。缺少空格将迫使Varnish缓存另一个版本。

因此,要在使用Variat的同时实现高命中率,关键是对后端不同的标头进行标准化。请记住,大小写的不同可能会强制不同的缓存条目。

下面的VCL代码将‘Accept-Language’标头标准化为“en”、“de”或“fr”,按优先顺序排列:

if (req.http.Accept-Language) {
    if (req.http.Accept-Language ~ "en") {
        set req.http.Accept-Language = "en";
    } elsif (req.http.Accept-Language ~ "de") {
        set req.http.Accept-Language = "de";
    } elsif (req.http.Accept-Language ~ "fr") {
        set req.http.Accept-Language = "fr";
    } else {
        # unknown language. Remove the accept-language header and
        # use the backend default.
        unset req.http.Accept-Language
    }
}

不同的分析错误

如果Variable标头无法解析,或者Variable标头中列出的任何客户端标头超过65k个字符的限制,Varnish将返回“503内部服务器错误”页面。在这些情况下,会添加一个‘SLT_ERROR’日志条目。

陷阱-不同:用户-代理

某些应用程序或应用程序服务器发送 Vary: User-Agent 以及它们的内容。这会指示Varnish为每个版本的“用户代理”缓存一个单独的副本,而且数量很多。即使是同一浏览器的单个补丁级别,也会根据它们运行的操作系统生成至少10个不同的“用户代理”头文件。

所以如果你 really 需要根据用户代理的不同而有所不同。确保将头球标准化,否则你的命中率将受到严重影响。使用上面的代码作为模板。

缓存未命中

当Varnish在缓存中找不到请求的对象时,默认情况下,它会基于响应可能被缓存的假设从后端执行FETCH。这有两个重要的后果:

  • 对同一对象的并发后端请求为 coalesced --一次只执行一个提取,其他挂起的提取等待结果(除非您已经实现了下面中所述的状态之一 无法缓存的内容 )。这是为了防止您的后端在缓存的响应过期时,或者如果它从一开始就没有被缓存,就会被“雷鸣般的牛群”击中。如果对第一次提取的响应被缓存,则该缓存对象可以立即传递给其他挂起的请求。

  • 如果Varnish在缓存中没有要验证的对象,则缓存未命中的后端请求不能是有条件的;也就是说,它不能包含标头 If-Modified-SinceIf-None-Match ,这可能会导致后端返回没有响应正文的状态“304 Not Modify”。否则,可能没有对缓存的响应。如果这些标头出现在客户端请求中,则会从后端请求中删除它们。

通过为缓存的对象设置宽限时间(默认为10秒),您允许Varnish在等待合并的获取时提供过时的内容,合并的获取在过时的响应被发送到客户端时以异步方式运行。有关详情,请参阅 Grace模式和Keep

尽管在缓存未命中时,条件请求的标头会从后端获取中删除,但Varnish仍然可以使用“304 NOT MODIFIED”来响应客户端请求,如果结果响应允许的话。在传递时,如果客户端请求具有 If-None-Match 标头与 ETag 响应中的标头,或者如果 If-Modified-Since 请求标头等于或晚于 Last-Modified 响应头,Varnish将向客户端发送304响应。无论命中还是未命中,都会发生这种情况。

如果Varnish在缓存中有一个可以对其执行验证的对象,它可以向后端发送条件请求。通过设置,可以确保为此目的保留对象 beresp.keep 在……里面 vcl_backend_response **

sub vcl_backend_response {
  # Keep the response in cache for 4 hours if the response has
  # validating headers.
  if (beresp.http.ETag || beresp.http.Last-Modified) {
    set beresp.keep = 4h;
  }
}

过期对象在持续时间内不会从缓存中删除 beresp.keep 在其TTL和宽限期到期之后。这将增加缓存的存储需求,但如果您有空间,可能值得保留可以验证相当长时间的陈旧对象。如果后端可以在TTL过期很久之后发送304响应,则可以节省获取带宽并减少存储压力;如果不能,则它与任何其他缓存未命中没有什么不同。

但是,如果您希望后端获取不是有条件的,只需删除 vcl_backend_fetch **

sub vcl_backend_fetch {
  # To prevent conditional backend fetches.
  unset bereq.http.If-None-Match;
  unset bereq.http.If-Modified-Since;
}

这应该只有在条件获取对后端有问题时才是必要的,例如,如果评估响应是否不变对后端应用程序来说代价太高,或者如果响应只是有错误的。从Varnish的角度来看,304响应显然是更可取的;使用空响应正文进行提取节省了带宽,并且不必在缓存中分配存储,因为现有的缓存对象得到了重用。

总而言之,即使在缓存未命中的情况下,也可以通过以下方式提高性能:

  • 确保缓存的对象具有宽限时间,在此期间,当在后台执行获取时,可以将过时的对象提供给客户端,以及

  • 为缓存对象设置保持时间,这些缓存对象在过期后可以使用304响应进行验证。

无法缓存的内容

由于各种原因,某些回复无法缓存。内容可能是个性化的,具体取决于 Cookie 标头,或者它可能只是在每次请求时重新生成的那类东西。缓存对此无能为力,但您可以做出一些决定,帮助Varnish以最适合您的需求的方式处理不可缓存的响应。

需要考虑的问题包括:

  • 阻止请求合并

  • 同一对象的响应是否(以及多久)可能再次变为可缓存

  • 不管你想不想传递 If-Modified-SinceIf-None-Match 从客户端请求到后端的标头,以允许后端以状态304响应

正在传递客户端请求

根据您的站点的工作方式,您可能能够识别客户端对无法缓存的响应的请求,例如,如果URL与某些模式匹配,或由于请求标头的内容。在这种情况下,可以将FETCH设置为 pass 使用 return(pass) 从… vcl_recv **

sub vcl_recv {
  if (req.url ~ "^/this/is/personal/") {
    return(pass);
  }
}

对于传球,不存在合并请求。由于PASS指示响应将不可缓存,因此等待可能被缓存的响应是没有意义的,并且对象的所有挂起的获取都是并发的。否则,等待最终被证明不可缓存的对象的提取可能会被序列化--挂起的提取将等待第一个提取,当结果没有输入到缓存中时,下一个提取开始,而所有其他提取都在等待,依此类推。

当请求被传递时,可以在 vcl_backend_* 子例程的事实是 bereq.uncacheableberesp.uncachable 都是真的。后端响应将不会被缓存,即使它满足否则将允许它的条件,例如 Cache-Control 设置正TTL。

PASS是默认设置(即, builtin.vcl 打电话 return(pass) 在……里面 vcl_recv )如果客户端请求满足以下条件:

  • 请求方法是标准的HTTP/1.1方法,但不是 GETHEAD

  • 要么有一个 Cookie 或者是一个 Authorization 标头,指示响应可能是个性化的

如果要覆盖默认设置,例如,如果您确定即使存在Cookie,响应也是可缓存的,请确保 return 在可能通过您自己的路径选择的任何路径的末尾调用 vcl_recv 。但如果你这样做了,内置的 vcl_recv 被处决;所以仔细看看 vcl_recv 在……里面 builtin.vcl ,并复制您自己所需的任何部分 vcl_recv

与缓存命中和未命中一样,如果客户端请求头和响应头允许,Varnish决定在传递后向客户端发送304响应。这可能意味着,即使后端看到相同的请求头,Varnish也会向客户端发送304响应 (If-Modified-Since 和/或 If-None-Match ),但决定不以状态304进行响应,同时仍设置响应头 ETag 和/或 Last-Modified 因此,304似乎是有根据的。如果您不希望Varnish这样做,那么删除 vcl_pass **

sub vcl_pass {
  # To prevent 304 client responses after a pass.
  unset req.http.If-None-Match;
  unset req.http.If-Modified-Since;
}

命中未命中

您可能无法识别对中不可缓存内容的所有请求 vcl_recv 。您可能希望允许后端通过设置 Cache-Control 标头,但直到Varnish收到后端响应才能看到,因此 vcl_recv 不能知道这件事。

默认情况下,如果请求未通过并且后端响应不可缓存,则通过设置 beresp.uncacheabletrue 在……里面 vcl_backend_response 。在高速缓存中保存一个最小的对象,以便在后续的查找中可以识别“命中未命中”状态。(缓存用于记住对象在有限的时间内是不可缓存的。)在这种情况下,不会执行请求合并,因此获取可以并发运行。否则,命中未命中的提取就像缓存未命中一样,这意味着:

  • 响应可能在以后的请求中变得可缓存,例如,如果它使用 Cache-Control ,以及

  • 提取不能是有条件的,因此 If-Modified-SinceIf-None-Match 从后端请求中删除头部。

什么时候 beresp.uncacheable 设置为 true ,那么 beresp.ttl 确定命中未命中状态最多可以持续多长时间。命中未命中状态在这段时间过去之后结束,或者如果后端在它过去之前返回了可缓存的响应(经过 beresp.ttl 只是意味着最小缓存对象过期,就像任何其他缓存对象过期一样)。如果返回可缓存的响应,则该对象替换命中未命中对象,并且对它的后续请求将是缓存命中。如果之前未返回可缓存的响应 beresp.ttl 则对该对象的下一个请求将是普通的未命中,因此将受到请求合并的影响。

当Varnish看到它在新请求上命中了一个命中未命中对象时,它会执行 vcl_miss 因此,您为缓存未命中编写的任何自定义VCL也将适用于命中未命中的情况。

builtin.vcl 集合 beresp.uncacheabletrue 在指示无法缓存响应的多个条件下,调用命中未命中状态,例如,如果TTL被计算为0或者如果存在 Set-Cookie 标题。 beresp.ttl 设置为两分钟,直到 builtin.vcl 在这种情况下,这就是默认情况下命中失误的持续时间。

您可以设置 beresp.uncacheable 如果您在其他情况下需要命中未命中:

sub vcl_backend_response {
  if (beresp.http.X-This-Is == "personal") {
    set beresp.uncacheable = true;
  }
}

请注意,有一次 beresp.uncacheable 已设置为 true 不能将其设置回 false ;在VCL中尝试执行此操作将被忽略。

尽管后端获取从来不是命中不命中的条件,但如果客户端请求头和响应头,Varnish可以决定(像在所有其他情况下一样)向客户端发送304响应 ETagLast-Modified 允许它。如果要防止出现这种情况,请删除中的if-客户端请求头 ``vcl_miss`` *

sub vcl_miss {
  # To prevent 304 client responses on hit-for-miss.
  unset req.http.If-None-Match;
  unset req.http.If-Modified-Since;
}

击出一次传球

命中未命中的后果是后端获取不能是有条件的,因为命中未命中允许后续响应可缓存。对于非常大且不可缓存的响应,这可能是有问题的,但可能会用304响应进行验证。例如,您可能希望客户端每次都通过后端验证对象,仅在对象更改时发送响应。

对于这种情况,可以使用以下命令将对象设置为“命中传球” return(pass(DURATION)) 从… vcl_backend_response ,其中持续时间确定击球传球状态持续的时间::

sub vcl_backend_response {
  # Set hit-for-pass for two minutes if TTL is 0 and response headers
  # allow for validation.
  if (beresp.ttl <= 0s && (beresp.http.ETag || beresp.http.Last-Modified)) {
    return(pass(120s));
  }
}

与命中未命中一样,最小对象被输入到缓存中,以便在后续请求中识别命中传递状态。然后,该请求被作为PASS处理,就像 vcl_recv 传回了传球。这意味着没有请求合并,并且 If-Modified-SinceIf-None-Match 客户端请求中的报头被传递到后端,从而后端响应可以是304。

Varnish可以执行 vcl_pass 当它击中一个击球传球对象时。因此,您可以在VCL中使用相同的代码安排自己的传球和击球处理。

如果您想要阻止Varnish向后端发送条件请求,那么在 vcl_backend_fetch ,如上面针对高速缓存未命中所示。如果您希望阻止Varnish在交付时根据客户端请求和响应头决定向客户端发送304响应,则从 vcl_pass ,如上图所示的PASS。

当“命中传球TTL”在 return 声明已过。与传递一样,对命中传递获取的响应永远不会被缓存,即使它本来可以满足可缓存的条件。因此,与命中未命中不同,不可能通过可缓存的响应提前结束命中传球状态。在“命中传球TTL”过去之后,对该对象的下一个请求被当作普通的未命中来处理。

可以通过设置来结束缓存对象的命中传递状态 req.hash_always_misstrue 在……里面 vcl_recv 对于将命中对象的请求(您必须编写实现该对象的VCL)。发生这种情况的请求被强制为缓存未命中,之后对象的状态取决于后端响应的处理--它可能变成缓存命中、命中未命中,或者可能再次被设置为命中换传递。

命中未命中是对不可缓存内容的默认处理。不属于 builtin.vcl 调用一击即传,因此如果需要它,您必须添加必要的 return 对您自己的VCL的声明。