在 nginx/gunicorn/django Web 架构中有效处理长时间运行的 HTTP 连接

Posted

技术标签:

【中文标题】在 nginx/gunicorn/django Web 架构中有效处理长时间运行的 HTTP 连接【英文标题】:Efficient handling of long running HTTP connections in an nginx/gunicorn/django web architecture 【发布时间】:2012-08-06 15:59:59 【问题描述】:

我正在开发基于nginx+gunicorn+django 的Web 服务。客户端是智能手机应用程序。应用程序需要对外部 API(Facebook、Amazon S3...)进行一些长时间运行的调用,因此服务器只需将作业排队到作业服务器(使用 Celery 而不是 Redis)。

只要有可能,一旦服务器将作业排队,它就会立即返回,并关闭 HTTP 连接。这可以正常工作,并允许服务器维持非常高的负载。

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<--------close----------|                        |
  .                        |                        |
  .                        |                        |

但在某些情况下,客户端需要在作业完成后立即获得结果。不幸的是,一旦 HTTP 连接关闭,服务器就无法联系客户端。一种解决方案是依靠客户端应用程序每隔几秒钟轮询一次服务器,直到作业完成。如果可能的话,我想避免这种解决方案,主要是因为它会阻碍服务的反应性,并且还会因为它会给服务器加载许多不必要的轮询请求。

简而言之,我想保持 HTTP 连接正常运行,什么也不做(除了可能每隔一段时间发送一个空格以保持 TCP 连接处于活动状态,只需 like Amazon S3 does),直到工作完成,然后服务器返回结果。

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<------keep-alive-------|                        |
  |         [...]          |                        |
  |<------keep-alive-------|                        |
  |                        |<--------result---------|
  |<----result + close-----|                        |
  .                        |                        |
  .                        |                        |

假设服务器处于非常高的负载下,我如何以有效的方式实现长时间运行的 HTTP 连接(目前还不是这样,但目标是能够承受最高可能的负载,数百还是每秒数千个请求)?

将实际作业卸载到其他服务器应确保服务器上的 CPU 使用率较低,但如何避免进程堆积并使用服务器的所有 RAM,或者由于打开的连接过多而丢弃传入请求?

这可能主要是正确配置 nginx 和 gunicorn 的问题。我已经阅读了一些关于async workers based on greenlets in gunicorn 的内容:文档说异步工作者被“应用程序进行长时间阻塞调用(即,外部 Web 服务)”使用,这听起来很完美。它还说“一般来说,应用程序应该能够使用这些工作类而无需更改”。这听起来很棒。对此有何反馈?

感谢您的建议。

【问题讨论】:

你研究过 django 的 AJAX 'long-polling' 解决方案吗?看起来基本上是一样的。 是的,你是对的,这可能是一回事。 AJAX 意味着客户端的 javascript,它更多的是关于 Web 浏览器,在我的情况下,客户端是一个软电话应用程序。但是服务器端大概也差不多了,好主意。 【参考方案1】:

我是answering my own question,也许有人有更好的解决方案。

进一步阅读gunicorn's documentation,并阅读更多关于eventlet 和gevent 的内容,我认为gunicorn 完美地回答了我的问题。 Gunicorn 有一个管理工人池的主进程。每个工作人员可以是同步的(单线程,一次处理一个请求)或异步的(每个工作人员实际上几乎同时处理多个请求)。

同步工作者非常容易理解和调试,如果工作者失败,只会丢失一个请求。但是如果一个工作人员被困在一个长时间运行的外部 API 调用中,它基本上是在睡觉。因此,在高负载的情况下,所有工作人员可能会在等待结果时进入休眠状态,请求最终会被丢弃。

所以解决办法是把默认的worker类型从同步改为异步(选择eventlet或者gevent,here's a comparison)。现在每个worker运行多个green threads,每个都非常轻量级。每当一个线程必须等待某个 I/O 时,另一个绿色线程就会恢复执行。这称为cooperative multitasking。它非常快,而且非常轻量级(如果他们正在等待 I/O,单个工作人员可以处理数千个并发请求)。正是我需要的。

我想知道如何更改现有代码,但显然标准 python 模块是 gunicorn 在启动时的monkey-patched(实际上是通过 eventlet 或 gevent),因此所有现有代码都可以在不更改的情况下运行,并且仍然可以很好地与其他线程一起运行.

gunicorn 中有很多参数可以调整,例如使用 gunicorn 的 worker_connections 参数的最大同时客户端数,使用 backlog 参数的最大挂起连接数等。

这太好了,我马上开始测试!

【讨论】:

测试后有什么反馈或更新吗?即将追随你的脚步 嗨 dsldsl。到目前为止,我对 gunicorn 和 gevent 非常满意。我做了一些简单的测试,结果令人满意,gunicorn 的行为与它所说的一样:我尝试运行 1 个单独的工作人员,然后打开(而不是关闭)一些连接,这并没有阻止下一个完整的请求通过。但到目前为止,我还没有时间进行真正的基准测试。享受吧!

以上是关于在 nginx/gunicorn/django Web 架构中有效处理长时间运行的 HTTP 连接的主要内容,如果未能解决你的问题,请参考以下文章

nginx + gunicorn + django 2.0 踩坑

部署了解nginx+gunicorn+django架构

部署了解nginx+gunicorn+django架构

部署了解nginx+gunicorn+django架构

在 nginx/gunicorn/django Web 架构中有效处理长时间运行的 HTTP 连接

使用Nginx + Gunicorn + Django 方式部署django程序