将 http abort/close 从 nginx 传播到 uwsgi / Django
Posted
技术标签:
【中文标题】将 http abort/close 从 nginx 传播到 uwsgi / Django【英文标题】:Propagate http abort/close from nginx to uwsgi / Django 【发布时间】:2017-02-08 09:30:58 【问题描述】:我有一个 Django 应用程序 web 应用程序,我想知道是否可以让 nginx 将中止/关闭传播到 uwsgi/Django。
基本上我知道 nginx 知道过早的中止/关闭,因为它默认为 uwsgi_ignore_client_abort
为“关闭”,并且在发送响应之前中止/关闭请求时,您的 nginx 日志中会出现 nginx 499 错误。一旦 uwsgi 完成对请求的处理,它会在将响应返回给 nginx 时抛出“IO 错误”。
将uwsgi_ignore_client_abort
设置为“on”只会使 nginx 不知道中止/关闭,并删除 uwsgi“IO 错误”,因为 uwsgi 仍然可以写回 nginx。
我的用例是我有一个应用程序,人们在其中非常快速地翻阅一些 ajax 结果,因此如果快速翻页我中止他们跳过的页面的未决 ajax 请求,这可以保持客户端的清洁和高效。但这对服务器端(uwsgi/Django)没有任何作用,因为即使没有任何东西在等待响应,它们仍然必须处理每个请求。
现在显然可能存在某些页面,我不希望请求因任何原因而过早中止。但我将 celery 用于可能属于该类别的长时间运行的请求。
那么这可能吗? uwsgi's
hariakari 设置让我觉得它在某种程度上......只是不知道该怎么做。
【问题讨论】:
只是好奇,uwsgi有一个is_connected函数。在您终止连接的情况下,此函数是否返回 False?听起来问题确实出在 uwsgi 方面,因为 Nginx 文档声明设置 proxy_ignore_client_abort 和 uwsgi_ignore_client_abort 都默认关闭。 【参考方案1】:我的用例是我有一个应用程序,人们在其中非常快速地翻阅一些 ajax 结果,因此如果快速翻页我中止他们跳过的页面的未决 ajax 请求,这可以保持客户端清洁和高效。
在客户端中止 AJAX 请求是通过XMLHttpRequest.abort()
完成的。如果调用abort()
时请求还没有发出,则请求不会发出。但是如果请求已经发送,服务器不会知道请求已经中止。连接不会关闭,不会有任何消息发送到服务器,什么都没有。如果您希望服务器知道不再需要某个请求,则基本上需要想出一种方法来识别请求,以便在发出初始请求时获得它的标识符。然后,通过另一个 AJAX 请求,您可以告诉服务器应该取消较早的请求。 (如果您搜索有关 abort()
like this one 的问题并搜索“服务器”,您会找到相同的解释。)
请注意,uwsgi_ignore_client_abort
是在 TCP 级别处理连接关闭的东西。这与中止 AJAX 请求不同。在 javascript 中,您通常无法采取任何要求关闭 TCP 连接的操作。浏览器优化连接的创建和销毁以满足其需求。刚才,我这样做了:
我使用lsof
检查是否有任何进程与example.com
有连接。没有。 (lsof
是一个 *nix 实用程序,允许列出打开的文件。网络连接是 *nix 中的“文件”。)
我在 Chrome 中打开了一个指向 example.com 的页面。 lsof
显示了连接和打开它的进程。
然后我关闭了页面。
我使用lsof
进行了轮询,以查看我之前确定的连接是否仍然打开。 在我关闭页面后它保持打开状态大约一分钟,即使没有真正需要保持连接打开。
再多的摆弄 uswgi 设置也不会让它知道通过 XMLHttpRequest.abort()
执行的中止操作
您提供的用例场景是用户快速翻阅某些结果的场景。我可以看到问题中给出的描述有两种可能性:
用户在进一步分页之前等待刷新。例如,Alice 正在查看按字母顺序为用户“Zeno”排序的用户名列表,每次显示新页面时,她都会看到该名称不存在并向下翻页。在这种情况下,没有什么可中止的,因为用户的操作取决于已处理的请求首先。 (用户必须先查看新页面才能做出决定。)
用户只是向下翻页而不等待刷新。 Alice 再次在寻找“Zeno”,但她认为它会出现在最后一页,所以点击、点击、点击,她走了。在这种情况下,您可以消除对服务器的请求。然后按下下一页按钮,增加应该向用户显示但不发送请求的页面的数量离开。相反,您在用户停止单击按钮后等待一小段延迟,然后发送带有 final 页码的请求,因此您发出一个请求而不是一打。 Here 是为 DataTables 搜索执行去抖动的示例。
【讨论】:
很有趣,因为在我的测试中,这在某种程度上确实有效......(我可以查看 uwsgi 日志并查看每个中止的 ajax 请求的错误)我想知道它是否与我的 keepalive 有关设置或我的实际连接路径是 haproxy -> nginx -> uwsgi -> django. 我知道的唯一可以使 XHR 中止与连接关闭相关的设置是在浏览器端。我记得看过有关设置的文档,这些设置可能会改变浏览器关闭连接的频率。据推测,可以将设置设置为最终浏览器在发出 XHR 中止时几乎立即关闭连接。请注意,这些是针对开发人员的设置,而不是人们常用的设置。我还可以看到代理如何比浏览器更频繁地关闭连接。【参考方案2】:现在显然可能存在某些页面,我不希望请求因任何原因而过早中止。
这正是采取这种或另一种方式背后的问题。
显然,您可能不希望继续花费系统资源来处理已中止的连接,例如昂贵的搜索操作。
但也许连接足够重要,即使客户端已断开连接,它仍然需要处理。
例如,同样昂贵的搜索操作,但实际上不是特定于客户端的,并且将由 nginx 为所有后续客户端缓存。
或者可能是修改应用程序状态的操作 - 您显然不希望您的应用程序具有不一致的状态!
如前所述,问题出在 uWSGI,而不是 NGINX。然而,你不能让 uWSGI 自动决定你的意图是什么,除非你自己向 uWSGI 透露这样的意图。
您将如何在代码中准确地揭示您的意图?一大堆编程语言并不真正支持多线程和/或异步编程模型,这使得取消操作变得非常重要。
因此,这里没有神奇的解决方案。 Even the concurrency-friendly programming languages like Golang are having issues around the WithCancel
context
— 你可能不得不在每个可能阻塞的函数调用中传递它,这使得代码非常难看。
您是否已经在 Django 中进行上述上下文传递?如果不是,那么解决方案很丑但很简单——任何时候你可以清楚地中止请求,检查客户端是否仍然连接到uwsgi.is_connected(uwsgi.connection_fd())
:
【讨论】:
以上是关于将 http abort/close 从 nginx 传播到 uwsgi / Django的主要内容,如果未能解决你的问题,请参考以下文章