Nginx 反向代理到 Django,在从上游读取响应 header 时接收“上游过早关闭连接”

Nginx reverse proxying to Django receiving `upstream prematurely closed connection while reading response header from upstream`

TL;DR

当尝试通过 HTTP 反向代理到本地 Django 实例(没有 WSGI 中间件)时,Nginx 记录 upstream prematurely closed connection while reading response header from upstream 时发生了什么 http/tcp 现象?

长版

冒着激怒社区的风险,我不会包含任何配置,因为虽然我确定它是相关的,但我正在尝试理解这种现象背后的理论。

我和一些队友维护着供内部使用的网络服务器。在 the/our 内部工具的世界里,事情永远不会被产品化。我们通常会做任何必要的事情来为我们的 co-workers 提供一些价值。风险和可用资源都很低。

因此,我们犯了一个大错,那就是自己建立一个 Python 2 Django 服务器。没有 WSGI 中间件,没有额外的进程。我看到了告诫,但我们已经做了我们已经做的。

我最近在这个可憎的东西面前建立了一个 Nginx 实例,使我们能够 "hot-swap" 我们的 Web 应用程序实例零停机时间。我仍然没有在两者之间插入任何东西。 Nginx 只是 reverse-proxying,通过本地主机 http 连接到侦听本地主机 non-standard 端口的 Django 实例。

在此更改之后,我们开始看到来自 Nginx 的 502 的爆发。有几个页面是 "live",因为它们会进行一些轮询以检查事物的更新。因此,我们拥有的用户数量有 "a lot" 流量。

我实际上认为无论什么问题在 Nginx 引入之前就已经存在,但是由于浏览器直接收到错误,它只是简单地重试并且用户看不到打嗝,而现在他们得到一个丑陋的 502 错误消息.

现在开始提问: 如果我在 Nginx 中看到 error.log upstream prematurely closed connection while reading response header from upstream 那究竟是什么意思?我在这个网站上看到了很多关于配置更改建议的帖子,none 其中似乎适用于我,但我正在寻找的是理论。

这个错误是什么意思? Nginx 在尝试将请求代理到 Django 时到底经历了什么? Django 是否拒绝连接? Django 是否在完成之前关闭连接?

如果 Django 正在做这些事情,为什么?是否内存不足,线程,是否有某些原因会限制线程数等?

作为临时的盲目尝试,over-the-weekend 修复我建立了应用程序的第二个实例并将 Nginx 配置为 round-robin 负载平衡它们。它似乎奏效了,但我要等到星期一早上出现峰值负载时才能确定。

第二个实例在同一个盒子上,所以不可能有任何额外的系统资源。 Python 解释器实例中是否有一些资源 运行 可用,以至于创建第二个实例可以给我 "twice" 容量?

除了"throw more resources at it!"

,我真的想在这里学习一些有价值的东西

如有任何帮助,我们将不胜感激。提前谢谢!

更新

Philipp,非常感谢您的详尽回答!一个简短的问题来锁定我的理解...

如果我的上游 Python 服务器 "cannot handle enough requests in parallel and blocks" 可能是什么原因造成的。我认为这是一个单一的过程,因此可以简化问题。什么资源会 运行 出来?服务器是否可能只是以它可以容纳的任何速度读取套接字?什么 system/server 配置会决定它一次可以处理的 in-flight 请求的数量?我仔细查看了一下,找不到任何会人为限制其响应能力的显式 Django(Python 服务器库)配置选项。我当然可以支持额外的资源,但如果它更多的是系统限制,那么我不希望同一个盒子上的另一个实例做任何事情(这是我现在期望的,因为第二个实例开始在周末)。我想在这里一劳永逸地做出深思熟虑的决定。

再次感谢您(或其他人)的帮助!

更新 2

潜在的问题(正如 Linux kernel-savvy co-worker 我星期一早上进来时向我描述的那样)是 LISTEN QUEUE DEPTH。

正是这个构造体的能力受到了限制。当一个进程侦听端口并且新的连接尝试进入(在建立连接之前)时,LISTEN QUEUE 如果进程建立连接的速度比它们进入的速度慢,则会建立。

因此,这与内存或 CPU 无关(除非这些资源的短缺是连接建立缓慢的原因),而是对进程连接容量的限制。

我绝不是任何这方面的专家,但正是这种结构让我知道为什么给定的进程突然决定(或 OS 决定)它将接受没有更多的连接。

更多可以阅读here

再次感谢 Philipp 带领我走上正确的道路!

upstream prematurely closed connection while reading response header from upstream

该错误肯定在上游,这意味着在您的情况下,与您的 Python 服务器的连接。 502 表示从 Nginx 到其上游服务器之一的 TCP 连接已关闭(从 python 进程主动关闭,或由系统超时关闭)。

根据您的描述,可能是 Python 服务器无法并行处理足够多的请求并阻塞。只要你前面没有 Nginx,你就不会注意到,也许只是请求很慢。有了 Nginx,它就发生了变化,因为 Nginx 可以轻松处理大量请求,并且可能会接受比其上游服务器(即您的 Python 服务器)可以跟上的更多请求。在那种情况下,上游服务器没有响应,最终套接字被关闭,这迫使 Nginx 失败并返回 502(错误的网关)。

为了检验该理论,您比较了向 Nginx 或直接向 Python 服务器发出多个请求时发生的情况。当您直接访问 Python 服务器时,它的请求被阻止并且服务速度变慢(但没有错误),但是当您访问 Nginx 时,所有请求都会立即被接受(但有些失败并返回 502),这可能是这种情况我描述的。

在这种情况下,您可以尝试一些操作:

  • 确保 keep-alive 在 Nginx 上有效(无论如何这是一个好主意,并且应该限制对上游的并行请求的数量)。有关详细信息,请参阅 this answer
  • (如果可能)更改 Python 服务器,以便它们可以处理更多并行请求
  • 确保您没有 运行 服务器上的文件句柄不足,并监控系统上的 TPC 套接字数量(例如,使用 sudo netstat -tulpan)。

我可能是错的,因为我在回答中做了很多猜测。不过,我希望它能为您解决请求关闭(或超时)的原因提供一些思路。

可能你已经检查过了,但我昨天遇到了类似的问题,问题是: 我是 运行 uwsgi--http-socket :5000。 我已更改为 --socket :5000 并且效果很好。

希望对大家有所帮助。