问题描述
我正在开发一个多租户平台,我们的客户可以在其中创建在线商店。一家商店可能会同时以多种货币(例如欧元和美元)销售产品。
让它工作的最初部分相当容易,但我遇到了不希望的副作用。
sub vcl_recv {
if (req.http.cookie) {
cookie.parse(req.http.cookie);
// If the user has a `currency` cookie,set it as a header
set req.http.x-currency = cookie.get("currency");
}
...
}
sub vcl_hash {
hash_data(req.http.x-currency);
}
我的后端识别使用此标头或不使用此标头以呈现正确的输出,然后 Varnish 存储结果。如果请求没有这个标头,服务器会发送一个 Set-Cookie
标头,它会告诉浏览器使用商店的默认货币创建一个 cookie。
我的问题源于这样一个事实,即我无法事先知道商店的默认货币(在我们的例子中是欧元或美元),因为所有者可以动态添加/删除它们。
>因此,在对 /homepage
端点的第一个请求中,用户将没有 currency
cookie,因此其值为 '',这将在 {{1} } 方法。
服务器识别出客户端没有货币 cookie,因此设置了一个 hash_data
标头,当页面点击浏览器时,客户端就会拥有 cookie。
目前,我们有一个 Set-Cookie
的缓存条目,没有 /homepage
cookie。
客户点击刷新,请求到达 Varnish。
这次有一个currency
cookie,在currency
函数中使用,但是产生的key不同,所以再次命中后端,但是输出会完全一样(显然,Varnish 不知道这一点)。
hash_data
url 有两个关联的缓存条目,它们具有相同的内容。
我最初的思路是:
进入 /homepage
子例程并检查请求。如果请求缺少 cookie,则 vcl_backend_response
对象将在其 beresp
中包含商店的默认货币。我可以使用它来创建 2 个不同的缓存键,它们指向同一个缓存条目 - Set-Header
用于空 cookie 和 /homepage + ''
。
这在技术上是不可能的,但它说明了似乎正在解决我的问题。
为简单起见,我没有提供 100% 的 vcl 配置,但我使用默认 vcl 作为模板为 /homepage + 'EUR'
和 vcl_recv
创建自定义配置。我知道 cookie 意味着个性化内容,我不应该缓存它,但在这种情况下,个性化并不适合该用户,所以我破例了。
编辑
www.mystore.com/en/ - 默认货币为美元
vcl_backend_response
我们目前缓存中没有任何内容
- 用户第一次访问
sub vcl_hash { if(req.url !~ "^/(contact|sitemap)") { hash_data(req.http.x-currency); } }
,他没有任何 cookie - Varnish 基于
homepage
生成一个散列,它是空的。结果是未命中。 - Varnish 命中后端,后端发现缺少
http.x-currency
标头,因此设置了 Set-Cookie 标头,将 cookie 设置为 USD - Varnish 接收响应,将其缓存,然后将其发送回客户端
- 同一用户在同一页面上刷新
- Varnish 根据
x-currency
生成一个哈希值,现在是 USD(上次为空),这再次导致缓存未命中 - Varnish 进入后端,后端返回完全相同的响应,因为没有
http.x-currency
或x-currency
= USD 意味着后端相同 - Varnish 接收响应,缓存它并返回给客户端
主页路由现在已完全缓存。无论某人使用 x-currency
cookie 访问还是没有访问,他都会收到缓存的响应。但是,缓存寄存器中有两个条目,一个是空的 currency=USD
cookie,一个是 currency
cookie,两者的结果相同。
问题源于这样一个事实:当请求到达并且它没有 currency=USD
cookie 时,如果没有后端的帮助,我没有一种可预测的方法来计算它的值。
在我看来,我似乎只能忍受这个。
解决方法
据我所知,您只想为包含不同货币的页面创建缓存变体。根据您提供的信息,我假设主页在内容方面没有区别,也不需要变化。
网址匹配
您可以匹配某些不需要变体的网址格式并有条件地执行变体。
这是一个例子:
sub vcl_hash {
if(req.url !~ "^/(homepage|contact|sitemap)") {
hash_data(req.http.x-currency);
}
}
这是一个过于简化的示例,但我希望您理解可以有条件地进行变体。使用包含或排除模式。
发送 Vary 标头
您还可以通过为需要按货币变化的页面发送以下 Vary
响应标头来管理应用程序中的条件变化方面:
Vary: x-currency
通过这样做,您可以从应用程序内部管理变体,而不是依赖于 hash_data()
中的 sub vcl_hash
。
请记住,x-currency
请求标头需要发送到应用程序。这不是真正的问题,因为您已在 vcl_recv
中设置了此标头。
去掉 Vary
中 vcl_deliver
标头的这一部分很重要,因为客户端不知道这个请求标头。
首页问题的重复键
在您上面的(更新的)问题以及下面的评论中,您谈到了基于 /homepage
标头多次缓存的假设 x-currency
对象。
这对你来说应该不是问题。我注意到您了解 Varnish 中的散列是如何工作的。
我上面提到的 2 个解决方案(URL 匹配和变化)解释了如何有条件地扩展哈希。术语有条件在这里非常重要。
如果 /homepage
路由无论 x-currency
值如何都产生完全相同的结果,则意味着 x-currency
不应成为散列的一部分。这也意味着 URL 和 主机头 将构成散列的基础。
如果不在 x-currency
中或作为 hash_data()
标头的一部分添加 Vary
,/homepage
将只有一个版本。
您表示 Vary: x-currency
是您的首选解决方案。我建议您只为每种货币具有不同输出的页面设置它。
我的印象是您的问题可以通过有条件地改变货币来解决。
Set-Cookie 标头怎么样?
另一个重要的说明是关于在未设置货币的情况下返回 Set-Cookie
标头的页面。
Varnish 的标准行为将导致所谓的Hit-For-Miss。
这意味着该对象未存储在缓存中,因为您不想为每个人缓存 Set-Cookie
标头。
在接下来的 2 分钟内,对该资源的所有请求都将绕过缓存,除非下一个响应变为可缓存。
在您的情况下,对 /homepage
的第二个请求将变为可缓存的,因为响应将不再包含 Set-Cookie
。
就命中率而言,结果是每个用户的第一个请求都将是缓存未命中,因为 Set-Cookie
。
请记住这一点。
就我个人而言,我不会担心这一点,但如果您这样做,您实际上可以去除 beresp.http.Set-Cookie
以使页面可缓存并自己在 vcl_deliver
中设置 cookie。
当然,您需要访问决定 EUR 与 USD 的逻辑,以正确设置 cookie 值。