Между браузером и вашей вьюхой стоит несколько слоёв: nginx, сервер приложений (gunicorn, uvicorn), интерфейс WSGI или ASGI и фреймворк. Вопрос «что такое WSGI» задают и джунам, а «почему FastAPI на uvicorn, а Django на gunicorn» уже middle-уровень.
Путь запроса
клиент -> nginx (TLS, статика, буферизация) -> gunicorn/uvicorn (процессы-воркеры)
-> WSGI/ASGI-приложение (Django, Flask, FastAPI) -> ваш код
- nginx (обратный прокси) принимает соединения, завершает TLS, отдаёт статику, буферизует медленных клиентов и защищает воркеры.
- Сервер приложений держит пул воркеров и переводит HTTP в вызов Python-приложения по стандартному интерфейсу.
- Фреймворк реализует этот интерфейс: маршрутизация, middleware, вьюхи.
WSGI
WSGI (PEP 3333) это синхронный интерфейс: приложение это вызываемый объект, который получает словарь environ с данными запроса и функцию start_response, а возвращает итерируемое тело ответа.
def app(environ, start_response):
path = environ['PATH_INFO']
body = f'Привет, {path}'.encode()
start_response('200 OK', [('Content-Type', 'text/plain; charset=utf-8')])
return [body]
Один запрос занимает воркер (процесс или поток) от начала до конца. WebSocket и долгие соединения в WSGI не укладываются. На WSGI работают Django (классически) и Flask, серверы: gunicorn, uWSGI.
ASGI
ASGI это асинхронный преемник: приложение это корутина app(scope, receive, send).
async def app(scope, receive, send):
assert scope['type'] == 'http'
await send({'type': 'http.response.start', 'status': 200,
'headers': [(b'content-type', b'text/plain')]})
await send({'type': 'http.response.body', 'body': b'ok'})
scopeописывает соединение (типhttp,websocket,lifespan),receiveиsendобмениваются событиями.- Поддерживает WebSocket, стриминг, server-sent events и тысячи одновременных соединений в одном процессе.
- Серверы: uvicorn, hypercorn, granian, daphne. Фреймворки: FastAPI и Starlette, Django (через
asgi.py), Litestar.
Воркеры и модели конкурентности
- gunicorn sync-воркеры: процесс обрабатывает один запрос за раз. Пропускная способность = число воркеров. Медленный внешний вызов блокирует воркер.
- gthread: несколько потоков в процессе, помогает при I/O.
- uvicorn (или gunicorn с uvicorn-воркерами): цикл событий в каждом процессе, тысячи одновременных ожиданий, но блокирующий код в
async defостанавливает весь процесс. - Процессов обычно делают по числу ядер или около
2 * CPU + 1для sync-модели, потом подбирают нагрузочным тестом.
Django: WSGI или ASGI
В проекте Django есть оба файла: wsgi.py и asgi.py. Асинхронные вьюхи Django работают и под WSGI, но тогда каждый запрос всё равно занимает воркер, и выгоды нет. Чтобы получить пользу от async def, Django запускают под ASGI-сервером. ORM Django при этом в основном синхронный внутри: async-методы (aget, acount и т.п.) выполняют запросы через поток.
Частые ошибки
- Запуск через
manage.py runserverилиuvicorn --reloadв проде. - Отсутствие таймаутов: воркер висит на медленном клиенте или внешнем API.
- Синхронные блокирующие вызовы внутри async-обработчиков.
- Мысль, что ASGI сам по себе ускоряет CPU-bound код.
Что спросят на собесе
- Что такое WSGI, как выглядит минимальное WSGI-приложение.
- Чем ASGI отличается от WSGI, зачем он появился.
- Зачем nginx перед gunicorn или uvicorn.
- Как работают воркеры gunicorn, сколько их ставить.
- Почему FastAPI запускают на uvicorn, а Django может работать и так, и так.