我们正面临清漆Max Threads hit&后端和会话连接峰值。我们不确定原因,但我们观察到,当源服务器的响应时间很长,并最终返回不可缓存的(502)响应时,就会发生这种情况。
清漆用途:
我们在nginx代理后面配置了varnish,所以传入的请求首先到达nginx,然后一直平衡到n varnish。Varnish,如果未命中,请调用原始nginx主机,这里是example.com.
在我们的案例中,我们只缓存HTTPGET请求,所有请求都有JSON有效负载作为响应,大小从0.001MB到2MB不等。
请求示例:
HTTP获取:http://test.com/test/abc?arg1=val1&arg2=val2
预期xkey:test/abc
响应:Json有效载荷
大约QPS:60-80 HTTP GET请求
平均目标ttl:2d
平均目标宽限期:1d
附加vcl文件、统计信息和清漆运行命令以进行调试。
监控状态:
请求
缓存状态
会话
线程
后端连接
对象已过期
清漆和VCL配置:
清漆版本:Linux,5.4.0,x86_64,清漆-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";
}
如果有任何改进当前配置的建议或调试相同配置所需的其他建议,请告诉我们。
感谢
Abhishek Surve
测量线程短缺并增加线程数
如果线程用完,从消防的角度来看,增加每个线程池的线程是有意义的。
这里有一个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
装卸工来存储大量数据。我们强烈建议不要使用它,因为它对磁盘碎片非常敏感。当Varnish必须执行过多的磁盘查找并且过于依赖内核的页面缓存以提高效率时,它可能会减慢速度。
在开源Varnish上,-s malloc
仍然是您的最佳选择。您可以通过水平扩展和两层Varnish来增加缓存容量。
将磁盘用于大容量数据的最可靠方法是Varnish Enterprise的海量存储引擎。它不是免费和开源的,但它是专门为应对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会为某些请求绕过缓存。内置的VCL描述了这个过程。看见https://www.varnish-software.com/developers/tutorials/varnish-builtin-vcl/有关内置VCL的更多信息。
你应该寻找的典型事物:
- 使用
GET
或HEAD
以外的请求方法的HTTP请求 - 具有
Authorization
标头的HTTP请求 - 具有
Cookie
标头的HTTP请求 - 带有
Set-Cookie
标头的HTTP响应 Cache-Control
标头中包含s-maxage=0
或max-age=0
指令的HTTP响应Cache-Control
标头中包含private
、no-cache
或no-store
指令的HTTP响应- 包含
Vary: *
标头的HTTP响应
您还可以运行以下命令来计算系统上发生的传球次数:
varnishstat -f MAIN.s_pass
如果这个值太高,您可能需要编写一些处理Authorization
标头、Cookie
标头和Set-Cookie
标头的VCL。
结论还可能是,您需要优化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&重新验证时过期
要在整个传统标头参数的基础上构建,请从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 TTL和1天宽限期:
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400
此反馈与您的问题无关,只是一般的最佳实践。
结论
目前还不清楚问题的根本原因是什么。你谈论的是线程和缓慢的后端。
一方面,我为您提供了检查线程池使用情况的方法,以及增加每个池线程数的方法。
另一方面,我们需要关注可能破坏系统平衡的潜在缓存未命中和缓存绕过场景。
如果某些头导致不需要的缓存旁路,我们可以通过编写适当的VCL 来改善这种情况
最后,我们需要确保,如果请求不可缓存,则不会将其添加到等待列表中。