Как gunicorn распределяет запросы между синхронизаторами?

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

Предположим, что все запросы выполняются за одно и то же время.

Является ли задание случайным? По-круговой? На основе ресурсов?

Команда, которую я использую для запуска сервера:

gunicorn --workers 8 bind 0.0.0.0:8000 main:app

1 Я использую FastAPI, но считаю, что это не относится к данному вопросу.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
9
0
527
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В моем случае (также с FastAPI) я обнаружил, что он начинается с циклического перебора, а затем становится глупым, когда все рабочие процессы заполнены.

Пример:

  • вы отправляете 100 запросов одновременно
  • первые 8 распределены по 8 рабочим процессам синхронизации
  • остальные 92 затем будут назначены первому рабочему, который будет свободен из первых 8
  • только после того, как ВСЕ (или многие) воркеры снова будут свободны, новые запросы будут распределяться между ними более сбалансированным образом.

Я пытаюсь исправить это неэффективное поведение для 92 запросов, упомянутых выше. Пока безуспешно.

Надеюсь, кто-то еще может добавить свои идеи ??

Почему «неэффективное поведение» — назначать любого работника, который свободен?

aaron 25.12.2022 06:08

Это нормально, если 1-й запрос назначается бесплатному воркеру, но тогда этот воркер больше не должен считаться бесплатным. Однако в моем случае этому рабочему назначены также 2-й, ..., 91-й И 92-й.

tyrex 26.12.2022 12:51

SyncWorker принимает клиентское соединение только тогда, когда оно бесплатно. Запросы специально не назначаются возможно недоступному работнику в пуле (см. мой ответ). Если работник обрабатывает все эти запросы, то каждый из этих запросов поступает только после обработки предыдущего запроса. Скорее всего не равное распределение, но рабочий точно бесплатный. Я не уверен, почему вы думаете, что это не должно считаться бесплатным. Чтение «балансировки нагрузки», связанное с моим ответом, показывает попытку достичь «равного распределения», но есть явные потери производительности из-за значительных накладных расходов.

aaron 26.12.2022 13:30

Спасибо @aaron за предоставление этих деталей. Я провожу эксперименты и вижу поведение, которое я описываю, т. е. рабочий получает все эти запросы ДО того, как он обработает первые. Поскольку вы упомянули, что этого не должно происходить (правильно?), то, возможно, происходит что-то еще (возможно, ошибка в моем коде). Будет исследовать снова.

tyrex 26.12.2022 22:12

Я должен спросить, вы уверены, что используете SyncWorker?

aaron 27.12.2022 01:14

Я использую рабочий класс, специфичный для FastAPI, следующим образом: gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80. Я добавлю минимальный воспроизводимый пример, но может пройти некоторое время, прежде чем я смогу его создать и загрузить.

tyrex 28.12.2022 01:23

Ну, это не SyncWorker, о котором этот вопрос изначально. UvicornWorker выполняет loop.create_server() , который вызывает sock.accept() на каждом такте цикла событий (по задумке). На данный момент нет «исправления» для этого — запрос функции Поддерживать потолок параллелизма без возврата 503, например worker_connections от gunicorn (encode/uvicorn#865) был отклонен в декабре 2020 года.

aaron 28.12.2022 12:20
Ответ принят как подходящий

Gunicorn не раздает запросы.

Каждый рабочий создается с одним и тем же LISTENERS (например, gunicorn.sock.TCPSocket) в Arbiter.spawn_worker() и вызывает listener.accept() самостоятельно.

Присвоение в блокирующих вызовах ОС методу сокета accept() — т. е. любому рабочему процессу, который позже будет разбужен ядром ОС и предоставлен client соединению, — это деталь реализации ОС, которая эмпирически не является ни циклической, ни ресурсной.

Ссылка из документов

Из https://docs.gunicorn.org/en/stable/design.html:

Gunicorn основан на модели pre-fork worker. ... Мастер никогда ничего не знает об отдельных клиентах. Все запросы и ответы полностью обрабатываются рабочими процессами.

Gunicorn полагается на операционную систему для обеспечения всей балансировки нагрузки при обработке запросов.

Другое чтение

Другие вопросы по теме