让 Varnish 在获取新数据时从缓存中发送旧数据吗?

让 Varnish 在获取新数据时从缓存中发送旧数据吗?

我正在缓存动态生成的页面(PHP-FPM,NGINX)并在它们前面使用了 varnish,效果很好。

但是,一旦达到缓存超时,我就会看到以下情况:

  • 新客户请求页面
  • varnish 识别缓存超时
  • 客户等待
  • varnish 从后端获取新页面
  • varnish 将新页面传送给客户端(并且也缓存了页面,以便下次请求时可以立即获取)

我想要做的是:

  • 客户端请求页面
  • varnish 识别超时
  • varnish 将旧页面发送给客户端
  • varnish 从后端获取新页面并将其放入缓存中

就我而言,网站中过时的信息并不是一个大问题,尤其是当我们谈论几分钟的缓存超时时。

但是,我不想惩罚用户排队等候,而是想立即提供一些东西。这可以吗?

为了说明起见,这里有针对我的服务器运行 siege 5 分钟的示例输出,该服务器配置为缓存一分钟:

HTTP/1.1,200,  1.97,  12710,/,1,2013-06-24 00:21:06
...
HTTP/1.1,200,  1.88,  12710,/,1,2013-06-24 00:21:20
...
HTTP/1.1,200,  1.93,  12710,/,1,2013-06-24 00:22:08
...
HTTP/1.1,200,  1.89,  12710,/,1,2013-06-24 00:22:22
...
HTTP/1.1,200,  1.94,  12710,/,1,2013-06-24 00:23:10
...
HTTP/1.1,200,  1.91,  12709,/,1,2013-06-24 00:23:23
...
HTTP/1.1,200,  1.93,  12710,/,1,2013-06-24 00:24:12
...

我忽略了数百个正在运行的请求0.02。但我仍然担心用户将不得不等待近 2 秒钟才能获得原始 HTML。

我们就不能做得更好吗?

(我碰到Varnish 发送缓存,听起来很相似但不完全是我想要做的。)

解决方案

Shane Madden 的回答包含了解决方案,但我没有立即意识到这一点。还有一个细节我没有包含在我的问题中,因为我认为它不相关,但实际上它是相关的。

我目前使用的 CMS 解决方案有一个 varnish 数据库监听器,因此能够通知 varnish 禁止内容已更改的页面。它发送了一个PURGE带有一些正则表达式的请求来禁止某些页面。

总而言之,有两种情况我遇到了不幸的用户:

  1. 页面的正常 varnish TTL 过期
  2. 后端用户更改内容,这会向 varnish 发送清除请求

在这两种情况下,我遇到的都是“不幸”的用户。在第二种情况下,后端用户通常会在页面更改后检查页面,因此这种情况有所缓解;但不一定。

尽管如此,对于第二种情况,我创建了一个解决方案(是的,我意识到这个问题开始于为第一种情况寻求答案......我的问题提得不好):

我没有发送清除请求,而是采用了 Shanes 的建议并调整了 VCL,以便我的 varnish 数据库监听器可以发送特殊请求来获取hash_always_miss设置为 的页面true

在目前的架构下,我实际上无法执行真正的异步请求,但是借助如何在 PHP 中发出异步 GET 请求?我可以编写一个 GET 请求给 varnish,它不会等待页面加载,但足以触发 varnish 从后端获取页面并缓存它。

最终结果是数据库监听器将请求发送给 varnish,当我轮询特定页面时,它从未使我的请求“不幸”,但是一旦 varnish 从后端完全获取页面(这可能在 300 毫秒到 2 秒之间),它就突然出现在那里。

我仍然需要找到一个解决方案,以避免在正常 TTL 用完时出现同样的问题,但我想解决方案也与 Shane 建议的完全一样:使用 wget 来触发hash_always_miss,我只需要足够聪明来获取我必须刷新的页面列表。

答案1

我用来解决这个问题的方案是确保页面上的 TTL 在刷新之前永远不会过期 - 强制在我的某个系统上运行的 HTTP 客户端获取缓慢的加载而不是不幸的客户端请求。

就我而言,这涉及wget在 cron 上发送一个特殊的标头来标记请求并req.hash_always_miss基于此进行设置,强制将内容的新副本提取到缓存中。

acl purge {
    "localhost";
}

sub vcl_recv {
    /* other config here */
    if (req.http.X-Varnish-Nuke == "1" && client.ip ~ purge) {
        set req.hash_always_miss = true;
    }
    /* ... */
}

对于您的内容,这可能意味着将 Varnish TTL 设置为 5 分钟左右,但将 cron'd wget 配置为每分钟发出一次缓存刷新请求。

答案2

@编辑:

需要快速告知的是,此功能似乎刚刚在主分支的最新版本中实现,您的版本可能不支持 true重新验证时过期然而 / 我发布的示例将处理 9999/10000 个请求,而一个可怜的家伙仍然需要等待请求在后端完成(但总比没有好 ;)...


好吧,我不太确定为什么之前的评论说它不起作用,但是根据:https://www.varnish-software.com/static/book/Saving_a_request.html

  • 请求宽限期- 定义对象可以逾期多长时间以便 Varnish 仍将其视为宽限模式。
  • beresp.grace- 定义 Varnish 在 beresp.ttl-time 之后保留对象的时间。
  • 请求宽限期- 通常根据后端的状态在 vcl_recv 中修改。

我目前正在使用手册中所述的配置,并且运行良好...这是我的 vcl 文件的片段...

sub vcl_recv {
    # Cache rules above here...
    if (req.backend.healthy) {
        set req.grace = 30d;
    } else {
        set req.grace = 300d;
    }
}

sub vcl_fetch {
    # Fetch rules above here ...

    # If backend returns 500 error then boost the cache grace period...
    if (beresp.status == 500) {
        set beresp.grace = 10h;
        return (restart);
    }

    # How long carnish should keep the objects in cache..
    set beresp.grace = 1h;

    # Actual TTL of cache - If an object is older than this an update will be triggered to the backend server :)
    set beresp.ttl = 1m;
}

请注意,如果您想提供更长的后端响应宽限期(对于像我的配置中的 500 错误),您将需要设置后端探测...这是我的后端探测的副本。

backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .probe = { 
        .url = "/nginx-status";
        .timeout = 500 ms; 
        .interval = 3s; 
        .window = 10;
        .threshold = 4;
    }
}

答案3

在 Varnish 3 中,这是通过“Grace 模式”实现的。根据手册[1],你必须添加以下逻辑:

sub vcl_fetch {
  set beresp.grace = 30m;
} // Makes objects to be cached/stored 30 min beyond its max-age/ttl

sub vcl_recv {
  set req.grace = 60s;
} // Allows varnish to serve objects which expired within last minute.

[1]https://www.varnish-cache.org/docs/3.0/tutorial/handling_misbehaving_servers.html#grace-mode

相关内容