【问题标题】:Varnish Max Threads hit & backend and session connections issueVarnish Max Threads hit & backend and session 连接问题
【发布时间】:2022-01-12 11:19:49
【问题描述】:

我们正面临 varnish Max Threads hit & backend 和 session 连接峰值的问题。我们不确定原因,但我们观察到,当源服务器响应时间较长并最终返回不可缓存 (502) 响应时会发生这种情况。

清漆使用:

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

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

示例请求:

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

预期的 xkey : test/abc

响应:Json 有效负载

大约 QPS:60-80 个 HTTP GET 请求

平均 obj ttl : 2d

平均 obj 优雅: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/([^?]+).*","\1");
    }

    # 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";
}

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

谢谢

阿布舍克调查

【问题讨论】:

    标签: varnish varnish-vcl


    【解决方案1】:

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

    如果线程用完了,从消防的角度来看,增加每个线程池的线程数是有意义的。

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

    varnishstat -f MAIN.threads -f MAIN.threads_limited
    

    d 键以显示具有零值的字段。

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

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

    varnishadm param.show thread_pool_max
    

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

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

    注意文件存储

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

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

    使用磁盘存储大量数据的最可靠方法是Varnish Enterprise's Massive Storage Engine。它不是免费和开源的,但它是专门为解决file stevedore 的性能不佳而构建的。

    寻找未缓存的内容

    根据您对问题的描述,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 为何会绕过某些请求的缓存非常重要。内置的 VCL 描述了这个过程。有关内置 VCL 的更多信息,请参阅https://www.varnish-software.com/developers/tutorials/varnish-builtin-vcl/

    您应该寻找的典型事物:

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

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

    varnishstat -f MAIN.s_pass
    

    如果这个值太高,您可能需要编写一些 VCL 来处理 Authorization 标头、Cookie 标头和 Set-Cookie 标头。

    结论也可能是您需要优化Cache-Control 标头。

    如果您已经完成了所有可以做的优化,但仍然会绕过很多缓存,那么您需要进一步扩展您的平台。

    注意零 TTL

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

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

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

    一个额外的风险是内置 VCL 无法处理此问题,也无法将这些请求正确标记为不可缓存。

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

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

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

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

    使用 s-maxage 和 stale-while-revalidate

    要构建整个传统的标头参数,请从您的 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 标头。

    这是一个Cache-Control 标头的示例,该标头具有3600s TTL1 天宽限期

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

    此反馈与您的问题无关,只是一般的最佳实践。

    结论

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

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

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

    如果某些标头导致不必要的缓存绕过,我们也许可以通过编写适当的 VCL 来改善这种情况

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

    【讨论】:

    • 感谢您的意见。将考虑这些要点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 2017-08-11
    • 2015-07-08
    • 1970-01-01
    • 2020-05-14
    • 2020-08-09
    相关资源
    最近更新 更多