Я использую gunicorn для запуска простого HTTP-сервера1, используя, например. 8 синхронизирующих воркеров (процессов). Из практических соображений мне интересно узнать, как gunicorn распределяет входящие запросы между этими воркерами.
Предположим, что все запросы выполняются за одно и то же время.
Является ли задание случайным? По-круговой? На основе ресурсов?
Команда, которую я использую для запуска сервера:
gunicorn --workers 8 bind 0.0.0.0:8000 main:app
1 Я использую FastAPI, но считаю, что это не относится к данному вопросу.
В моем случае (также с FastAPI) я обнаружил, что он начинается с циклического перебора, а затем становится глупым, когда все рабочие процессы заполнены.
Пример:
Я пытаюсь исправить это неэффективное поведение для 92 запросов, упомянутых выше. Пока безуспешно.
Надеюсь, кто-то еще может добавить свои идеи ??
Это нормально, если 1-й запрос назначается бесплатному воркеру, но тогда этот воркер больше не должен считаться бесплатным. Однако в моем случае этому рабочему назначены также 2-й, ..., 91-й И 92-й.
SyncWorker принимает клиентское соединение только тогда, когда оно бесплатно. Запросы специально не назначаются возможно недоступному работнику в пуле (см. мой ответ). Если работник обрабатывает все эти запросы, то каждый из этих запросов поступает только после обработки предыдущего запроса. Скорее всего не равное распределение, но рабочий точно бесплатный. Я не уверен, почему вы думаете, что это не должно считаться бесплатным. Чтение «балансировки нагрузки», связанное с моим ответом, показывает попытку достичь «равного распределения», но есть явные потери производительности из-за значительных накладных расходов.
Спасибо @aaron за предоставление этих деталей. Я провожу эксперименты и вижу поведение, которое я описываю, т. е. рабочий получает все эти запросы ДО того, как он обработает первые. Поскольку вы упомянули, что этого не должно происходить (правильно?), то, возможно, происходит что-то еще (возможно, ошибка в моем коде). Будет исследовать снова.
Я должен спросить, вы уверены, что используете SyncWorker?
Я использую рабочий класс, специфичный для FastAPI, следующим образом: gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80
. Я добавлю минимальный воспроизводимый пример, но может пройти некоторое время, прежде чем я смогу его создать и загрузить.
Ну, это не SyncWorker, о котором этот вопрос изначально. UvicornWorker выполняет loop.create_server() , который вызывает sock.accept()
на каждом такте цикла событий (по задумке). На данный момент нет «исправления» для этого — запрос функции Поддерживать потолок параллелизма без возврата 503, например worker_connections от gunicorn (encode/uvicorn#865) был отклонен в декабре 2020 года.
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 полагается на операционную систему для обеспечения всей балансировки нагрузки при обработке запросов.
Почему «неэффективное поведение» — назначать любого работника, который свободен?