在 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 库,它似乎正是这样做的。