Эффективная обработка длительных HTTP-соединений в веб-архитектуре nginx/gunicorn/django - программирование

Эффективная обработка длительных HTTP-соединений в веб-архитектуре nginx/gunicorn/django

Я работаю над веб-сервисом, реализованным поверх nginx + gunicorn + django. Клиенты - это приложения для смартфонов. Приложение должно выполнить несколько длительных вызовов внешних API (Facebook, Amazon S3...), поэтому сервер просто ставит очередь на задание на сервер заданий (используя Celery над Redis).

Когда это возможно, как только сервер ставит в очередь задание, он сразу возвращается, а HTTP-соединение закрывается. Это отлично работает и позволяет серверу поддерживать очень высокую нагрузку.

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

Но в некоторых случаях клиент должен получить результат, как только работа будет завершена. К сожалению, сервер не может связаться с клиентом после закрытия HTTP-соединения. Одним из решений было бы полагаться на клиентское приложение, опросившее сервер каждые несколько секунд, пока работа не будет завершена. Я хотел бы избежать этого решения, если это возможно, главным образом потому, что это будет препятствовать реактивности службы, а также потому, что оно загрузит сервер со множеством ненужных запросов опроса.

Короче говоря, я хотел бы поддерживать HTTP-соединение и работать, ничего не делая (за исключением, возможно, посылая пробел каждый раз, чтобы поддерживать соединение TCP, просто как Amazon S3 делает), пока работа не будет выполнена, и сервер вернет результат.

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

Как я могу эффективно реализовать длительные HTTP-соединения эффективным образом, предполагая, что сервер находится под очень высокой нагрузкой (это еще не так, но цель - поддерживать максимально возможную нагрузку, с сотнями или тысячи запросов в секунду)

Разгрузка фактических заданий на другие серверы должна обеспечивать низкое использование ЦП на сервере, но как я могу избежать нагромождения процессов и использования всей ОЗУ сервера или отбрасываемых входящих запросов из-за слишком большого количества открытых соединений?

Это, вероятно, в основном касается правильной настройки nginx и gunicorn. Я немного прочитал о асинхронных рабочих, основанных на озерах в gunicorn: в документации говорится, что асинхронные рабочие используются "Приложения, делающие длинные блокирующие вызовы (Т.е., внешние веб-сервисы)", это звучит прекрасно. В нем также говорится: "В общем, приложение должно иметь возможность использовать эти рабочие классы без изменений". Это звучит здорово. Любые отзывы об этом?

Спасибо за ваши советы.

4b9b3361

Ответ 1

Я отвечая на мой вопрос, возможно, у кого-то есть лучшее решение.

Чтение документации по пулемету немного дальше и немного больше читаем о eventlet и gevent, я думаю, что gunicorn отлично отвечает на мой вопрос. Гуникорн имеет мастер-процесс, который управляет пулом рабочих. Каждый рабочий может быть либо синхронным (однопоточный, обрабатывающий один запрос за раз), либо асинхронным (каждый рабочий фактически обрабатывает несколько запросов почти одновременно).

Синхронные рабочие очень просты в понимании и отладке, и если работник терпит неудачу, теряется только один запрос. Но если работник застрял в длительном внешнем вызове API, он в основном спал. Поэтому в случае высокой нагрузки все работники могут спать во время ожидания результатов, а запросы в конечном итоге будут удалены.

Итак, решение состоит в том, чтобы изменить рабочий класс по умолчанию от синхронного до асинхронного (выбор eventlet или gevent, здесь сравнение). Теперь каждый рабочий запускает несколько зеленых потоков, каждый из которых чрезвычайно легкий. Всякий раз, когда один поток должен ждать некоторых операций ввода-вывода, другой зеленый поток возобновляет выполнение. Это называется совместная многозадачность. Он очень быстрый и очень легкий (один рабочий может обрабатывать тысячи одновременных запросов, если они ждут ввода-вывода). То, что мне нужно.

Мне было интересно, как мне изменить существующий код, но, по-видимому, стандартные модули python обезглавленные с помощью gunicorn при запуске (на самом деле через eventlet или gevent), поэтому весь существующий код может работать без изменений и по-прежнему вести себя хорошо с другими потоками.

Есть несколько параметров, которые можно настроить в пушких, например максимальное количество одновременных клиентов с использованием параметра gunicorn worker_connections, максимальное количество ожидающих соединений с использованием параметра backlog и т.д.

Это просто здорово, я сразу же начну тестировать!