如果所有后端都被标记为“有问题”,是否有办法让 Varnish 4 忽略缓存中的任何命中传递对象?
以下是我试图改进的失败场景:
- 起初,后端都很健康,运行良好,返回状态为 200 的有效内容。Varnish 正在缓存这些页面并根据其 TTL 为其提供服务。
- 出现问题,比如数据库查询开始花费比平时更长的时间。后端开始缓慢返回页面。然后最终请求开始全部超时,后端开始返回“内部服务器错误”(状态 500)。
- Varnish 看到这些响应并将它们标记为命中通过,TTL 为 120 秒,根据默认
vcl_backend_response
。 - 最后,健康检查开始进行,Varnish 最终将所有后端标记为有问题。
- 现在,随着更多请求的到来,Varnish 会在缓存中看到一个命中通过的对象,并决定需要执行后端提取。但所有后端都出现问题,因此导致 503“后端提取失败”。
这些 503 响应最多持续 2 分钟,取决于第一个不可缓存的响应(标记为命中通过)的时间以及所有后端均被标记为故障的时间。 - 当命中通过对象从缓存中过期(120 秒)后,Varnish 会再次将这些请求视为常规命中,并以宽限模式提供缓存的 200 状态页面(根据默认
vcl_hit
--”if (obj.ttl + obj.grace > 0) ....
)
我想到的一个解决方法是,如果命中通过对象来自状态 500 响应,则缩短其 TTL:
sub vcl_backend_response {
if (beresp.ttl <= 0s && beresp.status == 500) {
set beresp.ttl = 10s;
set beresp.uncacheable = true;
# return inside this if statement to allow builtin vcl_backend_response to run
return (deliver);
}
}
其他可能性是调整健康检查的间隔和阈值,或者提出更好的健康检查。
但除此之外,有没有办法明确地告诉 Varnish“是的,你有一个命中通过的方法,但是看 - 所有后端都坏了!别费心了,现在切换到宽限模式。”
答案1
如果你想要这种行为,当你从后端获得 500 个响应时,你应该避免创建命中通过对象。
假设 (1) 您始终请求相同的可缓存 URL/对象 X;(2) 对象 X 当前存储在 Varnish 存储中,TTL 为 1 小时,宽限期为 24 小时。现在假设以下时间线:
- t=0:一些客户端向 Varnish 请求 X。对象已缓存且最新,因此 Varnish 将其返回给客户端。无后端请求。
- t=1:后端出现故障,从现在起它会对所有请求回复 500。
- 时间=3601:某个客户端向 Varnish 请求 X。该对象已缓存但处于停滞状态,因此 Varnish 将其返回给客户端并触发后台后端请求以更新缓存的对象 X。
- 时间=3602:后台后端请求获得 500 响应。存储一个命中通过对象,TTL 为 120 秒。此对象覆写停滞的对象 X。
- 时间=3603:某个客户端向 Varnish 发出 X 请求。发现一个命中通过的对象,并执行后端请求。向客户端发送 500 响应。
- 时间=4000:某个客户端向 Varnish 请求 X。对象 X 不再位于缓存中,并且后端在一段时间前被标记为故障。将向客户端发送 503 响应:Varnish 无法与后端联系(它被标记为故障)并且 Varnish 无法返回宽限内容(它被 t=3602 上的命中传递对象覆盖)。
此处的解决方案是,当您从后端获得 500 个响应时,避免创建命中通过对象。相反,只需放弃请求即可。这样,在 t=3603 和 t=4000 时,您将向客户端发送一个停滞的对象副本。