сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Docker и Kubernetes

Серверы приложений (gunicorn/uvicorn)

Прод-серверы: gunicorn, uvicorn, воркеры

Запустить Python-приложение в прод - не flask run: собес проверяет, знаешь ли ты, чем WSGI-сервер отличается от ASGI, как воркеры дают конкурентность и почему FastAPI не поднять обычным gunicorn. Это граница между dev и prod, на которой спотыкаются многие.

Типовые формулировки: «чем запускать Flask/FastAPI в прод?», «как gunicorn обходит GIL?», «что за uvicorn-воркер».

Dev-сервер против прод, воркеры и GIL

Dev-серверы - flask run, uvicorn --reload это для отладки, не для нагрузки: они не держат конкурентный трафик и отказы. В прод ставят gunicorn (WSGI - web server gateway interface) или uvicorn (ASGI - asynchronous server gateway interface) за реверс-прокси nginx, который берёт TLS, статику и балансировку.

gunicorn с sync-воркерами обслуживает запросы параллельно, ОБХОДЯ GIL (global interpreter lock): воркеры это отдельные процессы, у каждого свой интерпретатор и свой GIL, поэтому они реально считают на разных ядрах. Число воркеров подбирают под ядра, память растёт с их числом.

// Отсюда классический промах - app.run() или --reload в проде: dev-сервер просто не рассчитан на нагрузку и устойчивость к сбоям.

gunicorn
WSGI-сервер и менеджер воркеров-процессов
reverse proxy
nginx перед приложением: TLS, статика, балансировка

ASGI-запуск и выбор типа воркера

FastAPI - ASGI, его запускают uvicorn, часто под gunicorn с классом воркера uvicorn.workers.UvicornWorker: так получаешь и управление процессами от gunicorn, и async event loop от uvicorn. Обычный sync-gunicorn ASGI-приложение НЕ обслужит - нужен именно uvicorn-воркер.

Выбор типа воркера это трейдоф. Async-воркер (uvicorn) держит тысячи I/O-соединений в одном цикле дёшево - НО только если код не блокирует loop. Sync-воркеры проще и прощают блокирующий код: заблокировался один процесс - остальные работают. Если в async-воркере есть блокирующий вызов, честнее взять sync.

// То есть async выгоден на массовом I/O при неблокирующем коде; там, где есть тяжёлые sync-вызовы, sync-воркеры надёжнее.

uvicorn
ASGI-сервер на event loop
воркер
процесс/поток, обрабатывающий запросы

Как отвечать: «Как gunicorn даёт конкурентность, если есть GIL?»

Процессами. Sync-воркеры gunicorn это отдельные процессы, а не потоки, и у каждого свой интерпретатор со своим GIL. Поэтому GIL, который мешает потокам считать параллельно, здесь не помеха: разные воркеры-процессы реально работают на разных ядрах одновременно. Число воркеров я подбираю под количество ядер, помня, что память растёт с их числом - каждый процесс несёт свою копию. Для FastAPI на ASGI беру uvicorn-воркеры под тем же gunicorn, чтобы получить и управление процессами, и async-цикл. А перед всем этим ставлю nginx как реверс-прокси под TLS и статику.

Названа суть (процессы, а не потоки, обходят GIL), связано с ядрами и памятью, добавлен ASGI-случай и роль nginx - цельная картина прод-развёртывания.

На чём валят

  • app.run()/--reload в проде: dev-сервер не держит нагрузку и отказы.
  • Запускать FastAPI обычным sync-gunicorn: ASGI нужен uvicorn-воркер (UvicornWorker).
  • Блокирующий вызов в async-воркере замораживает весь цикл - тогда честнее sync-воркеры.
  • Задирать число воркеров без учёта памяти: каждый процесс несёт свою копию, упрёшься в RAM.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.

  1. #app_servers1 / 5
    Gunicorn с sync-воркерами: за счёт чего он обслуживает запросы параллельно, несмотря на GIL?
    A)Воркеры делят общую память и один интерпретатор на всех сразу
    B)Воркеры — отдельные процессы, у каждого свой интерпретатор и GIL
    C)Все запросы идут в одном процессе, но в разных корутинах цикла
    D)Gunicorn отключает GIL на время обработки каждого запроса
    показать ответ и разбор
    +B)Воркеры — отдельные процессы, у каждого свой интерпретатор и GIL

    // разбор: gunicorn поднимает несколько процессов-воркеров (флаг -w), у каждого свой интерпретатор и свой GIL — поэтому запросы обрабатываются реально параллельно на разных ядрах, обходя ограничение GIL внутри одного процесса. Число воркеров подбирают под ядра; память растёт с их количеством.

  2. #app_servers2 / 5
    Приложение на FastAPI (ASGI). Чем его запускать в проде?
    A)Через flask run, ведь под капотом фреймворки взаимозаменяемы
    B)Обычным gunicorn с sync-воркерами, как WSGI-приложение
    C)uvicorn, часто под gunicorn с uvicorn-воркерами
    D)Только встроенным uvicorn --reload, как и на локальной машине
    показать ответ и разбор
    +C)uvicorn, часто под gunicorn с uvicorn-воркерами

    // разбор: FastAPI — ASGI, ему нужен ASGI-сервер: uvicorn (или hypercorn). В проде часто гоняют gunicorn как менеджер процессов с классом воркера uvicorn.workers.UvicornWorker — получаешь и управление воркерами gunicorn, и async-цикл uvicorn. Голый sync-gunicorn ASGI-приложение не обслужит.

  3. #app_servers3 / 5
    Sync-воркеры (gthread) против async-воркера (uvicorn) — когда что?
    A)Async — на массовый I/O-конкаренси, sync-потоки — проще и под смешанное
    B)Выбор ни на что не влияет — оба обслуживают запросы одинаково
    C)Async-воркер строго лучше, sync-модель устарела
    D)Sync-воркеры быстрее за счёт простоты модели
    показать ответ и разбор
    +A)Async — на массовый I/O-конкаренси, sync-потоки — проще и под смешанное

    // разбор: Async-воркер (uvicorn) держит тысячи одновременных I/O-соединений в одном цикле дёшево — если код действительно async и не блокирует цикл. Sync-воркеры (процессы/потоки gunicorn) проще, прощают блокирующий код и хороши под умеренную или смешанную нагрузку. Блокирующий вызов в async всё портит — тогда честнее sync.

  4. #app_servers4 / 5
    Почему flask run / uvicorn --reload не годятся для прода?
    A)Они требуют платной лицензии для использования в продакшен-окружении
    B)Они несовместимы с Docker, поэтому в контейнере их не запустить
    C)Они работают только на локальной машине и физически недоступны из внешней сети
    D)Это dev-серверы для отладки — не рассчитаны на нагрузку и отказы
    показать ответ и разбор
    +D)Это dev-серверы для отладки — не рассчитаны на нагрузку и отказы

    // разбор: Встроенные dev-серверы (flask run, uvicorn --reload, app.run()) предназначены для разработки: автоперезагрузка, подробные ошибки, но они не держат нагрузку, плохо переживают отказы и не управляют пулом воркеров. В прод берут полноценный сервер приложений: gunicorn для WSGI, uvicorn (часто под gunicorn) для ASGI, за реверс-прокси. Флаг --reload на проде тем более не нужен.

  5. #app_servers5 / 5
    Как gunicorn с sync-воркерами обслуживает запросы параллельно, несмотря на GIL?
    A)Воркеры — отдельные процессы, у каждого свой интерпретатор и свой GIL
    B)gunicorn выполняет запросы строго по одному, создавая лишь видимость параллелизма
    C)gunicorn отключает GIL на время обработки каждого входящего запроса к приложению
    D)Все запросы обрабатываются в одном процессе несколькими потоками, обходя GIL
    показать ответ и разбор
    +A)Воркеры — отдельные процессы, у каждого свой интерпретатор и свой GIL

    // разбор: gunicorn запускает несколько воркеров — отдельных процессов, у каждого собственный интерпретатор Python и свой GIL. Поэтому они обрабатывают запросы по-настоящему параллельно на разных ядрах, обходя ограничение GIL, действующее внутри одного процесса. Плата — память растёт с числом воркеров (каждый процесс несёт свою копию). Число воркеров подбирают под ядра и характер нагрузки.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.