Varnish 位于负载均衡后端的缓存清理

假设有如下的架构:
LB proxy pass to Varnish
我们有时清理缓存会遇到问题:虽然 Varnish 上面的缓存清理机制配置好了,但是使用客户端访问到的那个 URL 却无法清理缓存!具体一点来说是比如客户端使用”http://www.example.com/some/url” 访问到最前端的负载均衡(比如是使用 Nginx),那么当我们使用 PURGE 方法在 Varnish 服务器上面清除”http://www.example.com/some/url”这个 URL 的时候,会发现尽管 ban 规则添加了,但是客户端访问到的仍然是旧的资源;不过使用 Ctrl+f5 却能清除缓存,但这种方法在有多个 Varnish 缓存服务器的情况下不是一个好的清除缓存的方法。
出现这种情况的原因可能是负载均衡上面把 URL 做了修改了,比如 URL 重写、Host 头替换等。这个时候客户端发送过来的”http://www.example.com/some/url”到了 Varnish 服务器可能就变成了访问的是”http://www.example.com/some/other/url”或者”http://other.example.com/some/url”。这个时候自然是无法再使用客户端可见的 URL 来清理缓存,因为缓存服务器上面存在的是另外一个 URL 的缓存!
不过这个问题还是比较容易解决的。思路是是一定要把客户端访问的那个 URL 通过某种方式传递给缓存服务器,然后缓存服务器可以识别到那个前端 URL,并且使用那个客户端 URL 作为清除缓存的标志。
假设最前端是 Nginx 在做调度,我们可以通过以下步骤实现:
1. Nginx proxy pass 到后端的时候添加两个 HTTP 头,比如 X-Forwarded-Host 和 X-Forwarded-Url,分别保存用户请求的原始 Host 头和 URL,这两个会在 Nginx 请求后端的时候被发送。

proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Url  $request_uri;

(注意:proxy_set_header 指令如果在低级别位置定义,则不会继承高级别位置已经定义的。也就是说如果你在 http 这个配置段加了那两条配置指令,但是 location 配置段有其它的 proxy_set_header 指令存在了,那么 location 段不会继承到这两个 HTTP 头的配置,必须要单独在这里再配置一次。坑爹的 Nginx!)
2. Varnish 上面读取 X-Forwarded-Host 和 X-Forwarded-Url 这两个 HTTP 头,并且把它们作为清除缓存的标志。

sub vcl_backend_response {
        if (bereq.http.X-Forwarded-Host) {
                set beresp.http.X-PURGE-Host = bereq.http.X-Forwarded-Host;
        } else {
                set beresp.http.X-PURGE-Host = bereq.http.Host;
        }

        if (bereq.http.X-Forwarded-Url) {
                set beresp.http.X-PURGE-Url = bereq.http.X-Forwarded-Url;
        } else {
                set beresp.http.X-PURGE-Url = bereq.url;
        }
}

清理缓存的是这条命令:

ban ("obj.http.X-PURGE-Host == " + req.http.Host + " && obj.http.X-PURGE-Url ~ ^" + req.url + "$");

最终我们就都可以用和客户端一样的 URL 去清理缓存了。