![强制请求未命中缓存但仍存储响应](https://linux22.com/image/595278/%E5%BC%BA%E5%88%B6%E8%AF%B7%E6%B1%82%E6%9C%AA%E5%91%BD%E4%B8%AD%E7%BC%93%E5%AD%98%E4%BD%86%E4%BB%8D%E5%AD%98%E5%82%A8%E5%93%8D%E5%BA%94.png)
我有一个运行缓慢的 Web 应用,我在它前面放置了 Varnish。所有页面都是静态的(不会因用户不同而变化),但它们需要每 5 分钟更新一次,以便包含最新数据。
我有一个简单的脚本(wget --mirror
),每 15 分钟抓取一次整个网站。每次抓取大约需要 5 分钟。抓取的目的是更新 Varnish 缓存中的每个页面,这样用户就不必等待页面生成(因为所有页面都是最近由蜘蛛生成的)。
时间线如下:
- 00:00:00:缓存已刷新
- 00:00:00:蜘蛛开始爬行,用新页面更新缓存
- 00:05:00:蜘蛛结束爬行,所有页面更新至00:15:00
在 0:00:00 到 0:05:00 之间发出的请求可能会访问尚未更新的页面,并且将被迫等待几秒钟才能得到响应。这是不可接受的。
我想做的是,也许使用一些 VCL 魔法,始终将请求从蜘蛛转发到后端,但仍将响应存储在缓存中。这样,用户将绝不必须等待页面生成,因为没有 5 分钟的窗口期使缓存的某些部分为空(服务器启动时除外)。
我怎样才能做到这一点?
答案1
req.hash_always_miss
应该可以解决问题。
不要在蜘蛛运行开始时执行完全缓存刷新。相反,只需将蜘蛛设置为工作 - 并在您的 中将vcl_recv
蜘蛛的请求设置为始终错过缓存查找;它们将从后端获取新副本。
acl spider {
"127.0.0.1";
/* or whereever the spider comes from */
}
sub vcl_recv {
if (client.ip ~ spider) {
set req.hash_always_miss = true;
}
/* ... and continue as normal with the rest of the config */
}
在此过程中,直到新的响应进入缓存中,客户端将继续无缝地获取旧的缓存(只要它仍在其 TTL 范围内)。
答案2
Shane 的上述回答比这个好。这是一个替代解决方案,但更复杂,而且存在其他问题。请为 Shane 的回答点赞,而不是这个。我只是展示了另一种解决问题的方法。
我最初的想法是return (pass);
在vcl_recv
,然后在请求被获取之后,以vcl_fetch
某种方式指示 Varnish应该缓存该对象,即使它之前已经被明确传递过。
事实证明这不可能:
如果您选择在较早的 VCL 函数(例如:vcl_recv)中传递请求,您仍将执行 vcl_fetch 的逻辑,但是即使您提供了缓存时间,对象也不会进入缓存。
因此,次优方案是像普通请求一样触发查找,但要确保它总是失败。没有办法影响查找过程,所以它总是会命中(假设它是缓存;如果没有,那么它无论如何都会丢失并存储)。但我们可以影响vcl_hit
:
sub vcl_hit {
# is this our spider?
if (req.http.user-agent ~ "Wget" && client.ip ~ spider) {
# it's the spider, so purge the existing object
set obj.ttl = 0s;
return (restart);
}
return (deliver);
}
我们无法强制它不使用缓存,但我们可以从缓存中清除该对象并重新启动整个过程。现在它回到开头,在vcl_recv
,它最终会再次进行查找。由于我们已经清除了我们尝试更新的对象,因此它将丢失,然后获取数据并更新缓存。
有点复杂,但确实有效。用户在清除和存储响应之间卡住的唯一时间是单个请求处理的时间。虽然不完美,但还不错。