缓存动态内容的反向代理
Caching reverse proxy for dynamic content
我本来想在 Software Recommendations 上询问,但后来我发现这可能是一个太奇怪的要求,需要先澄清一下。
我的观点是:
- 每个回复都包含一个
etag
- 这是内容的哈希值
- 并且是全局唯一的(有足够的概率)
- 内容(大部分)是动态的,可能随时更改(
expires
和 max-age
header 在这里没用)。
- 内容 部分 user-dependent,由权限给出(本身有时会更改)。
基本上,代理应该包含一个将 etag
映射到响应内容的缓存。 etag
从服务器获取,在最常见的情况下,服务器根本不处理响应内容。
应该是这样的:代理总是向服务器发送一个请求然后
- 1 服务器 return 只有
etag
并且代理基于它进行查找并且
- 1.1 缓存命中,
- 从缓存中读取响应数据
- 并向客户端发送响应
- 1.2 缓存未命中,
- 它再次询问服务器然后
- 服务器 return 的响应内容和
etag
,
- 代理将其存储在缓存中
- 并向客户端发送响应
- 2 或服务器 returns 响应内容和
etag
,
- 代理将数据存储在其缓存中
- 并向客户端发送响应
为简单起见,我省略了 if-none-match
header 的处理,这很明显。
我这样做的原因是最常见的情况 1.1 可以在服务器中非常有效地实现(使用其缓存映射请求到 etags
;内容不缓存在服务器中),因此大多数请求都可以在服务器不处理响应内容的情况下处理。这应该比首先从侧缓存中获取内容然后提供它更好。
在案例1.2中,有两次请求到服务器,这听起来很糟糕,但并不比服务器请求侧缓存并未命中更糟糕。
Q1:我想知道,如何将第一个请求映射到 HTTP。在情况 1 中,它就像一个 HEAD 请求。在情况 2 中,它就像 GET。两者之间的决定取决于服务器:如果它可以在不计算内容的情况下提供 etag
,则为情况 1,否则为情况 2。
Q2:有反向代理做这样的事情吗?我读过有关 nginx、HAProxy 和 Varnish 的文章,但似乎并非如此。这让我想到 Q3: 这是个坏主意吗?为什么?
Q4:如果不是,那么哪个现有代理最容易适应?
一个例子
来自用户 U1
的类似 /catalog/123/item/456
的 GET 请求提供了一些内容 C1
和 etag: 777777
。代理存储 C1
在密钥 777777
下。
现在同样的请求来自用户 U2
。代理转发它,服务器 returns 只是 etag: 777777
并且代理很幸运,在其缓存中找到 C1
(案例 1.1)并发送它到 U2
。 在这个例子中,客户端和代理都不知道预期的结果。
有趣的是服务器如何在不计算答案的情况下知道 etag
。例如,它可以有一条规则,说明这种形式的请求 return 对所有用户的结果相同,假设允许给定用户看到它。因此,当来自 U1
的请求到来时,它会计算 C1
并将 etag
存储在键 /catalog/123/item/456
下。当相同的请求来自 U2
时,它只是验证了 U2
被允许查看结果。
Q1: 是GET请求。服务器可以在没有 body.
的情况下回答“304 未修改”
Q2: openresty(nginx加上一些额外的模块)可以做到,但是你需要自己实现一些逻辑(见下面更详细的描述)。
Q3:根据您问题中的信息,这听起来像是一个合理的想法。只是一些思考的食物:
您也可以将页面拆分为 user-specific 和可以独立缓存的通用部分。
您不应该期望缓存永远保留计算的响应。因此,如果服务器 returns a 304 not modified
with etag: 777777
(根据您的示例),但缓存不知道它,您应该可以选择强制 re-building 答案,例如另一个带有自定义 header X-Force-Recalculate: true
.
的请求
不完全是你问题的一部分,但是:确保设置正确的 Vary
header 以防止缓存问题。
如果这只是关于权限,您也许还可以使用签名 cookie 中的权限信息。缓存可以在不询问服务器的情况下从cookie中获取权限,并且cookie由于签名是防篡改的。
Q4:我会为此使用 openresty,特别是 lua-resty-redis module。将缓存的内容放入 redis key-value-store 中,并以 etag
为键。您需要在 Lua 中编写查找逻辑代码,但不应超过几行。
我本来想在 Software Recommendations 上询问,但后来我发现这可能是一个太奇怪的要求,需要先澄清一下。
我的观点是:
- 每个回复都包含一个
etag
- 这是内容的哈希值
- 并且是全局唯一的(有足够的概率)
- 内容(大部分)是动态的,可能随时更改(
expires
和max-age
header 在这里没用)。 - 内容 部分 user-dependent,由权限给出(本身有时会更改)。
基本上,代理应该包含一个将 etag
映射到响应内容的缓存。 etag
从服务器获取,在最常见的情况下,服务器根本不处理响应内容。
应该是这样的:代理总是向服务器发送一个请求然后
- 1 服务器 return 只有
etag
并且代理基于它进行查找并且- 1.1 缓存命中,
- 从缓存中读取响应数据
- 并向客户端发送响应
- 1.2 缓存未命中,
- 它再次询问服务器然后
- 服务器 return 的响应内容和
etag
, - 代理将其存储在缓存中
- 并向客户端发送响应
- 1.1 缓存命中,
- 2 或服务器 returns 响应内容和
etag
,- 代理将数据存储在其缓存中
- 并向客户端发送响应
为简单起见,我省略了 if-none-match
header 的处理,这很明显。
我这样做的原因是最常见的情况 1.1 可以在服务器中非常有效地实现(使用其缓存映射请求到 etags
;内容不缓存在服务器中),因此大多数请求都可以在服务器不处理响应内容的情况下处理。这应该比首先从侧缓存中获取内容然后提供它更好。
在案例1.2中,有两次请求到服务器,这听起来很糟糕,但并不比服务器请求侧缓存并未命中更糟糕。
Q1:我想知道,如何将第一个请求映射到 HTTP。在情况 1 中,它就像一个 HEAD 请求。在情况 2 中,它就像 GET。两者之间的决定取决于服务器:如果它可以在不计算内容的情况下提供 etag
,则为情况 1,否则为情况 2。
Q2:有反向代理做这样的事情吗?我读过有关 nginx、HAProxy 和 Varnish 的文章,但似乎并非如此。这让我想到 Q3: 这是个坏主意吗?为什么?
Q4:如果不是,那么哪个现有代理最容易适应?
一个例子
来自用户 U1
的类似 /catalog/123/item/456
的 GET 请求提供了一些内容 C1
和 etag: 777777
。代理存储 C1
在密钥 777777
下。
现在同样的请求来自用户 U2
。代理转发它,服务器 returns 只是 etag: 777777
并且代理很幸运,在其缓存中找到 C1
(案例 1.1)并发送它到 U2
。 在这个例子中,客户端和代理都不知道预期的结果。
有趣的是服务器如何在不计算答案的情况下知道 etag
。例如,它可以有一条规则,说明这种形式的请求 return 对所有用户的结果相同,假设允许给定用户看到它。因此,当来自 U1
的请求到来时,它会计算 C1
并将 etag
存储在键 /catalog/123/item/456
下。当相同的请求来自 U2
时,它只是验证了 U2
被允许查看结果。
Q1: 是GET请求。服务器可以在没有 body.
的情况下回答“304 未修改”Q2: openresty(nginx加上一些额外的模块)可以做到,但是你需要自己实现一些逻辑(见下面更详细的描述)。
Q3:根据您问题中的信息,这听起来像是一个合理的想法。只是一些思考的食物:
您也可以将页面拆分为 user-specific 和可以独立缓存的通用部分。
您不应该期望缓存永远保留计算的响应。因此,如果服务器 returns a
304 not modified
withetag: 777777
(根据您的示例),但缓存不知道它,您应该可以选择强制 re-building 答案,例如另一个带有自定义 headerX-Force-Recalculate: true
. 的请求
不完全是你问题的一部分,但是:确保设置正确的
Vary
header 以防止缓存问题。如果这只是关于权限,您也许还可以使用签名 cookie 中的权限信息。缓存可以在不询问服务器的情况下从cookie中获取权限,并且cookie由于签名是防篡改的。
Q4:我会为此使用 openresty,特别是 lua-resty-redis module。将缓存的内容放入 redis key-value-store 中,并以 etag
为键。您需要在 Lua 中编写查找逻辑代码,但不应超过几行。