我想使用HTTP代理(例如nginx)来缓存大量/昂贵的请求。这些资源对于任何授权的用户都是相同的,但是每个请求的后端都需要检查其身份验证/授权。
听起来像Cache-Control: public, max-age=0
以及nginx指令proxy_cache_revalidate on;
就是这样做的。代理可以缓存请求,但是每个后续请求都需要对后端执行条件GET,以确保在返回缓存资源之前对其进行授权。如果用户未经授权,则后端发送403;如果用户被授权且缓存资源不是陈旧,则发送304;如果新资源已过期,则后端发送200.
在nginx中,如果设置了max-age=0
,则根本不会缓存请求。如果设置了max-age=1
,那么如果我在初始请求之后等待1秒,那么nginx会执行条件GET请求,但是在1秒之前它直接从缓存中提供它,这对于需要的资源来说显然非常糟糕。认证
有没有办法让nginx缓存请求但是需要立即重新验证?
请注意, 在Apache中正常工作。以下是nginx和Apache的示例,前两个有max-age = 5,最后两个有max-age = 0:
# Apache with `Cache-Control: public, max-age=5`
$ while true; do curl -v http://localhost:4001/ >/dev/null 2>&1 | grep X-Cache; sleep 1; done
< X-Cache: MISS from 172.x.x.x
< X-Cache: HIT from 172.x.x.x
< X-Cache: HIT from 172.x.x.x
< X-Cache: HIT from 172.x.x.x
< X-Cache: HIT from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
< X-Cache: HIT from 172.x.x.x
# nginx with `Cache-Control: public, max-age=5`
$ while true; do curl -v http://localhost:4000/ >/dev/null 2>&1 | grep X-Cache; sleep 1; done
< X-Cached: MISS
< X-Cached: HIT
< X-Cached: HIT
< X-Cached: HIT
< X-Cached: HIT
< X-Cached: HIT
< X-Cached: REVALIDATED
< X-Cached: HIT
< X-Cached: HIT
# Apache with `Cache-Control: public, max-age=0`
# THIS IS WHAT I WANT
$ while true; do curl -v http://localhost:4001/ >/dev/null 2>&1 | grep X-Cache; sleep 1; done
< X-Cache: MISS from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
< X-Cache: REVALIDATE from 172.x.x.x
# nginx with `Cache-Control: public, max-age=0`
$ while true; do curl -v http://localhost:4000/ >/dev/null 2>&1 | grep X-Cache; sleep 1; done
< X-Cached: MISS
< X-Cached: MISS
< X-Cached: MISS
< X-Cached: MISS
< X-Cached: MISS
< X-Cached: MISS
正如您在前两个示例中所看到的,Apache和nginx都可以缓存请求,并且Apache正确缓存了max-age = 0请求,但nginx没有。
答案 0 :(得分:2)
我想解决在对话期间出现的其他问题/疑虑,因为我原来的回答是简单地使用X-Accel-Redirect
(并且,如果需要Apache兼容性,分别为X-Sendfile
)
您寻求“最佳”(没有X-Accel-Redirect
)的解决方案不正确,原因不止一个:
只需要未经身份验证的用户请求清理缓存。
如果所有其他请求都来自未经身份验证的用户,则实际上根本没有任何缓存。
任何人都可以向资源的公共网址发出请求,以确保您的缓存始终保持干净。
如果所提供的文件实际上是静态的,那么你就会浪费额外的内存,时间,光盘和虚拟机/缓存空间来保存每个文件的多个副本。
如果投放的内容是动态的:
执行身份验证与资源生成的成本是否相同?那么当总是需要重新验证时,通过缓存它实际获得了什么?恒定系数小于2倍?你也可以不打扰缓存只是勾选一个复选标记,因为真实世界的改进可以忽略不计。
生成视图的成本是否比执行身份验证要贵得多?听起来像是一个好主意,然后在高峰时间将其提供给成千上万的请求!但为了成功实现这一点,你最好不要让任何未经验证的用户潜伏(因为即使是一对夫妇也可能导致不得不重新生成视图的重大且不可预测的费用。)
各种边缘情况下缓存会发生什么?如果用户被拒绝访问,而开发人员没有使用适当的代码,然后缓存了该怎么办?如果下一位管理员决定调整一两个设置,例如proxy_cache_use_stale
,该怎么办?突然间,您有未经身份验证的用户收到了密码信息。通过不必要地将应用程序的独立部分连接在一起,您将遗留各种缓存中毒攻击向量。
对于需要身份验证的网页,我不认为返回Cache-Control: public, max-age=0
在技术上是正确的。我认为正确答案可能是must-revalidate
或private
代替public
。
由于缺乏对max-age=0
的立即重新验证支持的nginx“缺陷”是设计的(类似于缺乏对.htaccess
的支持)。
根据以上几点,立即要求重新验证给定资源毫无意义,而且这只是一种无法扩展的方法,特别是当你每秒都有“荒谬”的请求数量必须满足时使用最少的资源并且没有不确定的条款。
如果您需要由“委员会”设计的Web服务器,并且每个厨房水槽应用程序和任何RFC的每个可疑部分都具有向后兼容性,那么nginx根本就不是正确的解决方案。
另一方面,X-Accel-Redirect
非常简单,万无一失,事实上是标准的。它允许您以非常简洁的方式将内容与访问控制分开。这很简单。它实际上确保您的内容将被缓存,而不是您的缓存无缘无故地被清除。它是值得追求的 正确的解决方案。试图在窥视时间内每10K服务避免一次“额外”请求,以第一个地方不需要缓存时只有“一个”请求的价格,并且当10K请求到来时实际上没有缓存,不是正确的设计可扩展架构的方法。
答案 1 :(得分:0)
我认为您最好的选择是在enter_data()
的支持下修改您的后端。
默认情况下启用其功能,并在proxy_ignore_headers
的文档中进行了描述:
“X-Accel-Redirect”执行内部重定向到指定的URI;
然后,您将缓存所述内部资源,并自动将其返回给已经过身份验证的任何用户。
由于重定向必须是internal
,因此没有任何其他方式可以访问它(例如,没有某种内部重定向),因此,根据您的要求,未经授权的用户赢了&# 39;能够访问它,但它仍然可以像任何其他location
一样进行缓存。
答案 2 :(得分:0)
如果您无法按照建议修改后端应用程序,或者如果身份验证很简单,例如auth basic,则另一种方法是在Nginx中执行身份验证。
实现此身份验证过程并定义缓存有效期将是您需要做的全部工作,Nginx将按照以下流程进行处理
Nginx流程为伪代码:
If (user = unauthorised) then
Nginx declines request;
else
if (cache = stale) then
Nginx gets resource from backend;
Nginx caches resource;
Nginx serves resource;
else
Nginx gets resource from cache;
Nginx serves resource;
end if
end if
Con是取决于您拥有的auth类型,您可能需要类似Nginx Lua模块来处理逻辑。
查看所提供的其他讨论和信息。现在,不完全了解后端应用程序的工作方式,但查看用户anki-code
在GitHub上给出的HERE评论的示例配置,下面的配置将避免您提出的后端应用程序身份验证问题/授权检查未针对先前缓存的资源运行。
我假设后端应用程序为未经身份验证的用户返回HTTP 403代码。我还假设您已经安装了Nginx Lua模块,因为GitHub配置依赖于此,但我注意到您测试的部分不需要该模块。
配置:
server {
listen 80;
listen [::]:80;
server_name 127.0.0.1;
location / {
proxy_pass http://127.0.0.1:3000; # Metabase here
}
location ~ /api/card((?!/42/|/41/)/[0-9]*/)query {
access_by_lua_block {
-- HEAD request to a location excluded from caching to authenticate
res = ngx.location.capture( "/api/card/42/query", { method = ngx.HTTP_HEAD } )
if res.status = 403 then
return ngx.exit(ngx.HTTP_FORBIDDEN)
else
ngx.exec("@metabase")
end if
}
}
location @metabase {
# cache all cards data without card 42 and card 41 (they have realtime data)
if ($http_referer !~ /dash/){
#cache only cards on dashboard
set $no_cache 1;
}
proxy_no_cache $no_cache;
proxy_cache_bypass $no_cache;
proxy_pass http://127.0.0.1:3000;
proxy_cache_methods POST;
proxy_cache_valid 8h;
proxy_ignore_headers Cache-Control Expires;
proxy_cache cache_all;
proxy_cache_key "$request_uri|$request_body";
proxy_buffers 8 32k;
proxy_buffer_size 64k;
add_header X-MBCache $upstream_cache_status;
}
location ~ /api/card/\d+ {
proxy_pass http://127.0.0.1:3000;
if ($request_method ~ PUT) {
# when the card was edited reset the cache for this card
access_by_lua 'os.execute("find /var/cache/nginx -type f -exec grep -q \\"".. ngx.var.request_uri .."/\\" {} \\\; -delete ")';
add_header X-MBCache REMOVED;
}
}
}
有了这个,我希望 $ curl 'http://localhost:3001/api/card/1/query'
的测试运行如下:
location ~ /api/card((?!/42/|/41/)/[0-9]*/)query
/api/card/42/query
发出“HEAD”子请求。在给定的配置中,此位置不包括在缓存中。@metabase
命名的位置块发出子请求,该位置块处理实际请求并将内容返回给用户。location ~ /api/card((?!/42/|/41/)/[0-9]*/)query
/api/card/42/query
的后端发出“HEAD”子请求。而不是 /api/card/42/query
,如果资源密集,您可以创建一个简单的卡片查询,仅用于执行身份验证。
似乎是一种直截了当的方式。后端保持原样,不会弄乱它,你可以在Nginx中配置你的缓存细节。