Varnish Max Threads hit & 后端和会话连接问题

Varnish Max Threads hit & backend and session connections issue

我们面临着清漆最大线程命中率和后端以及会话连接峰值的问题。我们不确定原因,但我们观察到当源服务器响应时间长并且最终 return 无法缓存 (502) 响应时会发生这种情况。

清漆用法:

我们在 nginx 代理后面配置了 varnish,因此传入请求首先到达 nginx,然后始终平衡到 n varnish。 varnish,万一错过,调用源nginx主机,这里example.com.

在我们的例子中,我们只缓存 HTTP GET 请求,并且所有请求都有 JSON 有效载荷作为响应,大小从 0.001 MB 到 2 MB 不等。

请求示例:

HTTP 获取:http://test.com/test/abc?arg1=val1&arg2=val2

预期的 xkey:test/abc

响应:Json有效载荷

大约 QPS:60-80 HTTP GET 请求

平均 obj ttl : 2d

平均目标宽限期:1d

附上 vcl 文件、统计信息和 varnish 运行 命令用于调试目的。

监控统计数据:

Requests

Cache status

Sessions

Threads

Backend Connections

Objects expired

清漆和 VCL 配置:

清漆版本:Linux,5.4.0,x86_64,varnish-6.5.1

varnishd -F -j unix,user=nobody -a :6081 -T localhost:6082 -f /etc/varnish/default.vcl -s file,/opt/varnishdata/cache,750G
vcl 4.0;
import xkey;
import std;

    acl purgers {
        "localhost";
    }

backend default {
    .host = "example.com";
    .port = "80";
}

sub vcl_recv {
    unset req.http.Cookie;
    if (req.method == "PURGE") {
        if (client.ip !~ purgers) {
            return (synth(403, "Forbidden"));
        }
        if (req.http.xkey) {
            set req.http.n-gone = xkey.softpurge(req.http.xkey);
            return (synth(200, "Invalidated "+req.http.n-gone+" objects"));
        }
        else {
           return (purge);
        }
    }
    # remove request id from request 
    set req.url = regsuball(req.url, "reqid=[-_A-z0-9+()%.]+&?", "");
    # remove trailing ? or &
    set req.url = regsub(req.url, "[?|&]+$", "");
    # set hostname for backend request
    set req.http.host = "example.com";
}

sub vcl_backend_response {
    # Sets default TTL in case the baackend does not send a Caching related header
    set beresp.ttl = std.duration(beresp.http.X-Cache-ttl, 2d);
    # Grace period to keep serving stale entries
    set beresp.grace = std.duration(beresp.http.X-Cache-grace, 1d);
    # extract xkey
    if (bereq.url ~ "/some-string/") {
        set beresp.http.xkey = regsub (bereq.url,".*/some-string/([^?]+).*","");
    }

    # This block will make sure that if the upstream return a 5xx, but we have the response in the cache (even if it's expired),
    # we fall back to the cached value (until the grace period is over).
    if ( beresp.status != 200 &&  beresp.status != 422 ){
        # This check is important. If is_bgfetch is true, it means that we've found and returned the cached object to the client,
        # and triggered an asynchronous background update. In that case, if it was a 5xx, we have to abandon, otherwise the previously cached object
        # would be erased from the cache (even if we set uncacheable to true).
        if (bereq.is_bgfetch)
        {
            return (abandon);
        }

        # We should never cache a 5xx response.
        set beresp.uncacheable = true;
    }
}

sub vcl_deliver {
    unset resp.http.X-Varnish;
    unset resp.http.Via;
    set resp.http.X-Cached = req.http.X-Cached;
}
sub vcl_hit {
    if (obj.ttl >= 0s) {
       set req.http.X-Cached = "HIT";
       return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        set req.http.X-Cached = "STALE";
        return (deliver);
    }
    set req.http.X-Cached = "MISS";
}
sub vcl_miss {
    set req.http.X-Cached = "MISS";
}

如果有任何改进当前配置的建议或调试相同配置所需的任何其他建议,请告诉我们。

谢谢

Abhishek 调查

测量线程短缺并增加线程数

如果您 运行 没有线程,从救火的角度来看,增加每个线程池的线程数是有意义的。

这是一个显示实时线程消耗和潜在线程限制的 varnishstat 命令:

varnishstat -f MAIN.threads -f MAIN.threads_limited

d 键显示值为零的字段。

如果 MAIN.threads_limited 增加,我们知道您已经超过了由 thread_pool_max 运行time 参数设置的每个池的最大线程数。

通过执行以下命令显示当前thread_pool_max值是有意义的:

varnishadm param.show thread_pool_max

您可以使用 varnishadm param.show 设置新的 thread_pool_max 值,但它不会保留并且不会在重启后继续存在。

最好的方法是通过 systemd 服务文件中的 -p 参数进行设置。

注意文件存储

我注意到您正在使用 file 装卸工来存储大量数据。我们强烈建议不要使用它,因为它对磁盘碎片非常敏感。当 Varnish 必须执行过多的磁盘寻道并且过于依赖内核的页面缓存以提高效率时,它会减慢 Varnish 的速度。

在开源 Varnish 上,-s malloc 仍然是您最好的选择。您可以通过水平扩展和拥有 2 层 Varnish 来增加缓存容量。

使用磁盘处理大量数据的最可靠方法是Varnish Enterprise's Massive Storage Engine。它不是免费和开源的,但它是专门为应对 file 装卸工人的糟糕表现而构建的。

正在寻找未缓存的内容

根据您对问题的描述,看起来 Varnish 必须花费太多时间来处理未缓存的响应。这需要后端连接。

幸运的是,Varnish 放开后端线程并允许客户端线程在 Varnish 等待后端响应时处理其他任务。

但是如果我们可以限制后端获取的数量,也许我们可以提高 Varnish 的整体性能。

我不太关心缓存未命中,因为缓存未命中是尚未发生的命中,但是我们可以通过 运行以下命令:

varnishtop -g request -i requrl -q "VCL_Call eq 'MISS'"

这将列出排名前几位的 URL。然后,您可以深入了解单个请求并找出导致缓存未命中的原因如此频繁。

您可以使用以下命令检查特定 URL 的日志:

varnishlog -g request -q "ReqUrl eq '/my-page'"

请将 /my-page 替换为您正在检查的端点的 URL。

对于缓存未命中,我们关心它们的 TTL。可能 TTL 设置得太低了。 TTL 标签会告诉你使用了哪个 TTL 值。

还要注意 Timestamp 标签,因为它们可以突出显示任何潜在的减速。

正在寻找不可缓存的内容

不可缓存的内容比未缓存的内容更危险。缓存未命中最终会导致命中,而缓存绕过将始终无法缓存,并且始终需要后端获取。

以下命令将列出您的顶级缓存绕过 URL:

varnishtop -g request -i requrl -q "VCL_Call eq 'PASS'"

再一次,您可以使用以下命令向下钻取

varnishlog -g request -q "ReqUrl eq '/my-page'"

理解为什么 Varnish 会绕过某些请求的缓存很重要。 built-in VCL 描述了这个过程。有关 built-in VCL 的更多信息,请参阅 https://www.varnish-software.com/developers/tutorials/varnish-builtin-vcl/

您应该寻找的典型事物:

  • HTTP 请求的请求方法不是 GETHEAD
  • 带有 Authorization header
  • 的 HTTP 请求
  • 带有 Cookie header
  • 的 HTTP 请求
  • 带有 Set-Cookie header
  • 的 HTTP 响应
  • Cache-Control header
  • 中使用 s-maxage=0max-age=0 指令的 HTTP 响应
  • Cache-Control header
  • 中使用 privateno-cacheno-store 指令的 HTTP 响应
  • 包含 Vary: * header
  • 的 HTTP 响应

您还可以运行以下命令来计算您的系统上发生了多少遍:

varnishstat -f MAIN.s_pass

如果太高,您可能需要编写一些 VCL 来处理 Authorization headers、Cookie headers 和 Set-Cookie headers.

结论也可以是你需要优化你的Cache-Controlheaders.

如果您已完成所有可能的优化,但仍然绕过很多缓存,则需要进一步扩展您的平台。

留意零 TTL

引起我注意的一行 VCL 如下:

set beresp.ttl = std.duration(beresp.http.X-Cache-ttl, 2d);

您正在使用 X-Cache-ttl 响应 header 来设置 TTL。如果有一个常规的 Cache-Control header 你为什么要这样做?

一个额外的风险是 built-in VCL 无法处理这个并且无法正确地将这些请求标记为不可缓存。

可能发生的最危险的事情是你通过这个设置beresp.ttl = 0header 并且您遇到了在您的 VCL 中达到 set beresp.uncacheable = true 的情况。

如果此时 beresp.ttl 保持为零,Varnish 将无法在缓存中存储 Hit-For-Miss objects情况。这意味着对该资源的后续请求将被添加到等待列表中。但是因为我们处理的是不可缓存的内容,Varnish 的 request coalescing 机制永远不会满足这些请求。

结果是等待列表将被串行处理,这会增加等待时间,这可能会导致超出可用线程。

我的建议是在设置 set beresp.uncacheable = true; 之前添加 set beresp.ttl = 120s。这将确保为不可缓存的内容创建 Hit-For-Miss objects。

使用s-maxage & stale-while-revalidate

要构建整个常规 header 论点,请从您的 VCL 中删除以下代码行:

    # Sets default TTL in case the baackend does not send a Caching related header
    set beresp.ttl = std.duration(beresp.http.X-Cache-ttl, 2d);
    # Grace period to keep serving stale entries
    set beresp.grace = std.duration(beresp.http.X-Cache-grace, 1d);

Cache-Control headers.

的正确使用替换此逻辑

这是一个 Cache-Control header 的示例,具有 3600 秒 TTL1 天宽限期

Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400

This feedback is not related to your problem, but is just a general best practice.

结论

目前还不清楚问题的根本原因是什么。您谈论线程和慢后端。

一方面,我为您提供了检查线程池使用情况的方法以及增加每个池线程数的方法。

另一方面,我们需要查看可能破坏系统平衡的潜在缓存未命中和缓存绕过情况。

如果某些 headers 导致不需要的缓存绕过,我们可以通过编写适当的 VCL

来改善这种情况

最后,我们需要确保您不会将无法缓存的请求添加到候补名单。