在 nginx 中分离原始请求和镜像请求的问题
Problem Segregating Original Request and Mirrored Request in nginx
我有 2 个环境(envA、envB)。 envA 需要将其请求镜像到 envB 以及对 envB 进行另外 2 次调用,其中包含来自 envA 响应的信息。 envA 对 envB 的响应不感兴趣,它本质上是一个火了就不管的情况。 objective 是为了确保 envA 的操作和性能绝不会受到对 envB 的调用的影响。我们选择使用 nginx 作为我们的代理并让它做镜像。我们还编写了一个 lua 脚本来处理我上面描述的逻辑。
问题在于,即使来自 envA 服务的响应很快返回,nginx 也会保留 return 对调用者的 envA 响应,直到完成对 envB 的其他 3 个调用。我想以某种方式摆脱这种堵塞。
我们的团队中没有任何人对 lua 或 nginx 有经验,所以我确信我们所拥有的不是 best/right 的方法...但是到目前为止,我们一直在做的是调整连接和读取超时,以确保我们将任何阻塞减少到最短时间。但这并没有让我们到达我们想去的地方。
经过一些研究,我发现 https://github.com/openresty/lua-nginx-module#ngxtimerat 其中;据我了解;与在 java 中创建一个 ScheduledThreadPoolExecutor 相同,只是将一个作业排入队列并将其与原始请求流隔离,从而消除阻塞。但是我不太了解范围如何变化以确保我没有把事情搞砸 data/variable 明智而且我也不确定使用什么库来调用 envB 因为我们已经到目前为止使用 ngx.location.capture,根据上面 link 中的文档,在使用 ngx.timer.at 时不是一个选项。因此,对于如何正确使用 ngx.timer.at 或实现此目标的替代方法的任何见解,我将不胜感激。
这是我们正在使用的 lua 代码。我把它弄糊涂了很多
但我们所拥有的骨架在那里,主要部分是 content_by_lua_block 部分
http {
upstream envA {
server {{getenv "ENVA_URL"}};
}
upstream envB {
server {{getenv "ENVB_URL"}};
}
server {
underscores_in_headers on;
aio threads=one;
listen 443 ssl;
ssl_certificate {{getenv "CERT"}};
ssl_certificate_key {{getenv "KEY"}};
location /{{getenv "ENDPOINT"}}/ {
content_by_lua_block {
ngx.req.set_header("x-original-uri", ngx.var.uri)
ngx.req.set_header("x-request-method", ngx.var.echo_request_method)
resp = ""
ngx.req.read_body()
if (ngx.var.echo_request_method == 'POST') then
local request = ngx.req.get_body_data()
resp = ngx.location.capture("/envA" .. ngx.var.request_uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB" .. ngx.var.uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB/req2" .. "/envB/req2", { method = ngx.HTTP_POST })
ngx.status = resp.status
ngx.header["Content-Type"] = 'application/json'
ngx.header["x-original-method"] = ngx.var.echo_request_method
ngx.header["x-original-uri"] = ngx.var.uri
ngx.print(resp.body)
ngx.location.capture("/mirror/envB/req3" .. "/envB/req3", { method = ngx.HTTP_POST, body = resp.body })
end
}
}
location /envA {
rewrite /envA(.*) break;
proxy_pass https://envAUrl;
proxy_ssl_certificate {{getenv "CERT"}};
proxy_ssl_certificate_key {{getenv "KEY"}};
}
###############################
# ENV B URLS
###############################
location /envB/req1 {
rewrite /envB/req1(.*) break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req2 {
rewrite (.*) /envB/req2 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req3 {
rewrite (.*) /envB/req3 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
}
}
就我们所看到的问题而言...我们看到在通过此代理时访问 envA 与我们不使用它时相比,响应时间(秒)有所增加。
编辑:我很快就找到了更好的解决方案(见我的另一个答案),但我也放弃了这个解决方案,因为至少从技术上了解它可能有一些价值工作(你可能不应该这样做)
我能想到的最快的事情就是这个
content_by_lua_block {
ngx.say 'Hello World!'
local start = os.time()
ngx.flush()
ngx.req.socket:close()
while os.difftime(os.time(), start) < 4 do
end
}
首先使用 ngx.flush()
将实际输出刷新到客户端,然后使用 ngx.req.socket:close()
关闭连接。很确定这不是最干净的选择,但在大多数情况下它是有效的。如果我能找到更好的解决方案,我会 post 另一个答案:)
发出第一个答案差不多五分钟后,我想起了进行这种清理的正确方法 activity。
函数 ngx.timer.at 允许您在一定时间后将函数安排到 运行,包括 0
在当前处理程序完成后立即执行。您可以使用它来安排您的清理职责和其他操作,以便在响应已返回给客户端并且请求以干净的方式结束后执行。
这是一个例子:
content_by_lua_block {
ngx.say 'Hello World!'
ngx.timer.at(0, function(_, time)
local start = os.time()
while os.difftime(os.time(), start) < time do
end
os.execute('DISPLAY=:0 zenity --info --width 300 --height 100 --title "Openresty" --text "Done processing stuff :)"')
end, 3)
}
请注意,我使用 zenity
显示带有消息的弹出窗口 window,因为我没有任何设置来检查它是否真的被调用。
编辑:我应该提一下,要在预定事件中发送 HTTP 请求,您需要使用 cosocket API,它不支持开箱即用的 HTTP 请求,但可以快速 google 搜索会显示 this 库,它似乎正是这样做的。
我有 2 个环境(envA、envB)。 envA 需要将其请求镜像到 envB 以及对 envB 进行另外 2 次调用,其中包含来自 envA 响应的信息。 envA 对 envB 的响应不感兴趣,它本质上是一个火了就不管的情况。 objective 是为了确保 envA 的操作和性能绝不会受到对 envB 的调用的影响。我们选择使用 nginx 作为我们的代理并让它做镜像。我们还编写了一个 lua 脚本来处理我上面描述的逻辑。
问题在于,即使来自 envA 服务的响应很快返回,nginx 也会保留 return 对调用者的 envA 响应,直到完成对 envB 的其他 3 个调用。我想以某种方式摆脱这种堵塞。
我们的团队中没有任何人对 lua 或 nginx 有经验,所以我确信我们所拥有的不是 best/right 的方法...但是到目前为止,我们一直在做的是调整连接和读取超时,以确保我们将任何阻塞减少到最短时间。但这并没有让我们到达我们想去的地方。
经过一些研究,我发现 https://github.com/openresty/lua-nginx-module#ngxtimerat 其中;据我了解;与在 java 中创建一个 ScheduledThreadPoolExecutor 相同,只是将一个作业排入队列并将其与原始请求流隔离,从而消除阻塞。但是我不太了解范围如何变化以确保我没有把事情搞砸 data/variable 明智而且我也不确定使用什么库来调用 envB 因为我们已经到目前为止使用 ngx.location.capture,根据上面 link 中的文档,在使用 ngx.timer.at 时不是一个选项。因此,对于如何正确使用 ngx.timer.at 或实现此目标的替代方法的任何见解,我将不胜感激。
这是我们正在使用的 lua 代码。我把它弄糊涂了很多 但我们所拥有的骨架在那里,主要部分是 content_by_lua_block 部分
http {
upstream envA {
server {{getenv "ENVA_URL"}};
}
upstream envB {
server {{getenv "ENVB_URL"}};
}
server {
underscores_in_headers on;
aio threads=one;
listen 443 ssl;
ssl_certificate {{getenv "CERT"}};
ssl_certificate_key {{getenv "KEY"}};
location /{{getenv "ENDPOINT"}}/ {
content_by_lua_block {
ngx.req.set_header("x-original-uri", ngx.var.uri)
ngx.req.set_header("x-request-method", ngx.var.echo_request_method)
resp = ""
ngx.req.read_body()
if (ngx.var.echo_request_method == 'POST') then
local request = ngx.req.get_body_data()
resp = ngx.location.capture("/envA" .. ngx.var.request_uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB" .. ngx.var.uri, { method = ngx.HTTP_POST })
ngx.location.capture("/mirror/envB/req2" .. "/envB/req2", { method = ngx.HTTP_POST })
ngx.status = resp.status
ngx.header["Content-Type"] = 'application/json'
ngx.header["x-original-method"] = ngx.var.echo_request_method
ngx.header["x-original-uri"] = ngx.var.uri
ngx.print(resp.body)
ngx.location.capture("/mirror/envB/req3" .. "/envB/req3", { method = ngx.HTTP_POST, body = resp.body })
end
}
}
location /envA {
rewrite /envA(.*) break;
proxy_pass https://envAUrl;
proxy_ssl_certificate {{getenv "CERT"}};
proxy_ssl_certificate_key {{getenv "KEY"}};
}
###############################
# ENV B URLS
###############################
location /envB/req1 {
rewrite /envB/req1(.*) break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req2 {
rewrite (.*) /envB/req2 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
location /envB/req3 {
rewrite (.*) /envB/req3 break;
proxy_pass https://envB;
proxy_connect_timeout 30;
}
}
}
就我们所看到的问题而言...我们看到在通过此代理时访问 envA 与我们不使用它时相比,响应时间(秒)有所增加。
编辑:我很快就找到了更好的解决方案(见我的另一个答案),但我也放弃了这个解决方案,因为至少从技术上了解它可能有一些价值工作(你可能不应该这样做)
我能想到的最快的事情就是这个
content_by_lua_block {
ngx.say 'Hello World!'
local start = os.time()
ngx.flush()
ngx.req.socket:close()
while os.difftime(os.time(), start) < 4 do
end
}
首先使用 ngx.flush()
将实际输出刷新到客户端,然后使用 ngx.req.socket:close()
关闭连接。很确定这不是最干净的选择,但在大多数情况下它是有效的。如果我能找到更好的解决方案,我会 post 另一个答案:)
发出第一个答案差不多五分钟后,我想起了进行这种清理的正确方法 activity。
函数 ngx.timer.at 允许您在一定时间后将函数安排到 运行,包括 0
在当前处理程序完成后立即执行。您可以使用它来安排您的清理职责和其他操作,以便在响应已返回给客户端并且请求以干净的方式结束后执行。
这是一个例子:
content_by_lua_block {
ngx.say 'Hello World!'
ngx.timer.at(0, function(_, time)
local start = os.time()
while os.difftime(os.time(), start) < time do
end
os.execute('DISPLAY=:0 zenity --info --width 300 --height 100 --title "Openresty" --text "Done processing stuff :)"')
end, 3)
}
请注意,我使用 zenity
显示带有消息的弹出窗口 window,因为我没有任何设置来检查它是否真的被调用。
编辑:我应该提一下,要在预定事件中发送 HTTP 请求,您需要使用 cosocket API,它不支持开箱即用的 HTTP 请求,但可以快速 google 搜索会显示 this 库,它似乎正是这样做的。