Varnish Max Threads触顶及后端、会话连接突增问题排查与优化咨询
问题概述
遇到Varnish Max Threads触顶、后端连接与会话连接突增问题,暂未明确根因,观测到该问题仅在源站响应耗时较高、最终返回不可缓存的502响应时触发。
Varnish部署说明
Varnish部署在Nginx代理后方,入站请求先到达Nginx,再通过一致性负载均衡转发至多台Varnish节点;缓存未命中时,Varnish会请求源站Nginx节点(本例为example.com)。当前场景下仅缓存HTTP GET请求,所有响应均为JSON载荷,大小范围为0.001MB~2MB。
请求示例
HTTP GET请求地址:http://test.com/test/abc?arg1=val1&arg2=val2
预期xkey:test/abc
响应:JSON载荷
运行指标
- 预估QPS:60~80 HTTP GET请求
- 平均对象TTL:2d
- 平均对象grace时长:1d
Varnish及相关配置
版本信息
Varnish版本:varnish-6.5.1,运行于Linux 5.4.0 x86_64环境
启动命令
varnishd -F -j unix,user=nobody -a :6081 -T localhost:6082 -f /etc/varnish/default.vcl -s file,/opt/varnishdata/cache,750G
VCL配置
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); } } # 移除请求中的reqid参数 set req.url = regsuball(req.url, "reqid=[-_A-z0-9+()%.]+&?", ""); # 移除末尾的?或&符号 set req.url = regsub(req.url, "[?|&]+$", ""); # 设置后端请求的host头 set req.http.host = "example.com"; } sub vcl_backend_response { # 若后端未返回缓存相关头,设置默认TTL set beresp.ttl = std.duration(beresp.http.X-Cache-ttl, 2d); # 设置stale内容服务的grace时长 set beresp.grace = std.duration(beresp.http.X-Cache-grace, 1d); # 提取xkey if (bereq.url ~ "/some-string/") { set beresp.http.xkey = regsub (bereq.url,".*/some-string/([^?]+).*","\1"); } # 该逻辑保证若上游返回5xx,只要缓存中存在对应响应(即使已过期),就返回缓存内容(直到grace时长耗尽) if ( beresp.status != 200 && beresp.status != 422 ){ # 该判断十分重要:若is_bgfetch为true,说明已向客户端返回缓存对象,且触发了异步后台更新。此时若后端返回5xx,需要放弃更新,否则即使设置uncacheable为true,原有缓存对象也会被清除 if (bereq.is_bgfetch) { return (abandon); } # 禁止缓存5xx响应 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"; }
优化建议
根因初步判断
故障触发逻辑符合源站降级场景下的缓存雪崩特征:源站响应变慢+返回不可缓存502时,缓存无法命中,所有请求直接透传至源站,每个请求占用Varnish线程的时间被大幅拉长,线程池快速耗尽,进而引发会话、后端连接突增的连锁反应。
配置优化点
1. VCL逻辑优化
- 补全异步刷新逻辑:当前配置仅实现了grace期返回过期内容,未触发后台异步刷新,缓存过期后首个请求必须同步等待源站响应,故障场景下会加剧线程占用。需要在
vcl_recv中添加set req.grace = 1d;,同时在vcl_hit的STALE分支后添加return (restart);触发后台异步回源,避免用户请求同步等待源站。 - 增加请求合并逻辑:开启Varnish默认的请求合并(request coalescing)功能,避免同一个缓存Key的大量请求同时回源,故障场景下可减少90%以上的无效回源量,可在
vcl_recv中显式配置set req.hash_always_miss = false;避免逻辑被覆盖。 - 新增5xx错误短缓存规则:当前5xx响应完全不缓存,故障场景下会导致所有请求持续打源站。可在设置
beresp.uncacheable = true;的同时添加set beresp.ttl = 5s;,短时间内复用错误响应,避免源站压力进一步恶化。 - 修复xkey提取逻辑:当前xkey仅匹配
/some-string/路径,和示例中的/test/abc路径不匹配,软清理功能完全失效,需要调整正则匹配规则覆盖所有业务路径。 - 补充请求方法过滤:明确仅缓存GET请求,在
vcl_recv中添加非GET请求直接透传的逻辑,避免非预期请求占用缓存资源。
2. 启动参数优化
- 调整线程池配置:当前启动命令未配置线程参数,默认线程上限较低,容易被打满。可添加参数
-p thread_pool_min=50 -p thread_pool_max=1000 -p thread_pool_timeout=120,根据实际业务规模调整上限,预留足够的线程缓冲空间。 - 新增源站超时配置:避免请求长时间挂在源站响应上占用线程,添加参数
-p connect_timeout=5s -p first_byte_timeout=30s -p between_bytes_timeout=10s,超过阈值直接终止请求返回错误或降级内容。
3. 降级策略优化
可新增源站异常降级逻辑,当源站5xx占比超过阈值时,直接返回兜底数据,无需持续等待源站响应。
需补充的排查信息
- 故障时刻的
varnishstat输出,重点关注MAIN.thread_limited(线程受限次数)、MAIN.backend_fail(后端失败次数)、MAIN.cache_miss(缓存未命中数)、MAIN.queued_requests(排队请求数)核心指标 - 正常运行期和故障期的缓存命中率对比数据
- 故障时刻的
varnishlog采样,确认请求处理链路的耗时分布 - 源站故障时刻的响应耗时、502错误占比数据
内容的提问来源于stack exchange,提问作者Abhishek Surve
相关产品推荐
相关产品推荐

