Серверы приложений (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, остальные разбираются в тренажёре.
- Gunicorn с sync-воркерами: за счёт чего он обслуживает запросы параллельно, несмотря на GIL?A)Воркеры делят общую память и один интерпретатор на всех сразуB)Воркеры — отдельные процессы, у каждого свой интерпретатор и GILC)Все запросы идут в одном процессе, но в разных корутинах циклаD)Gunicorn отключает GIL на время обработки каждого запроса
показать ответ и разбор
+B)Воркеры — отдельные процессы, у каждого свой интерпретатор и GIL// разбор: gunicorn поднимает несколько процессов-воркеров (флаг -w), у каждого свой интерпретатор и свой GIL — поэтому запросы обрабатываются реально параллельно на разных ядрах, обходя ограничение GIL внутри одного процесса. Число воркеров подбирают под ядра; память растёт с их количеством.
- Приложение на 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-приложение не обслужит.
- 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.
- Почему 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 на проде тем более не нужен.
- Как gunicorn с sync-воркерами обслуживает запросы параллельно, несмотря на GIL?A)Воркеры — отдельные процессы, у каждого свой интерпретатор и свой GILB)gunicorn выполняет запросы строго по одному, создавая лишь видимость параллелизмаC)gunicorn отключает GIL на время обработки каждого входящего запроса к приложениюD)Все запросы обрабатываются в одном процессе несколькими потоками, обходя GIL
показать ответ и разбор
+A)Воркеры — отдельные процессы, у каждого свой интерпретатор и свой GIL// разбор: gunicorn запускает несколько воркеров — отдельных процессов, у каждого собственный интерпретатор Python и свой GIL. Поэтому они обрабатывают запросы по-настоящему параллельно на разных ядрах, обходя ограничение GIL, действующее внутри одного процесса. Плата — память растёт с числом воркеров (каждый процесс несёт свою копию). Число воркеров подбирают под ядра и характер нагрузки.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.