Как обновить приложение Flask с помощью асинхронности

Скорее всего, вы не получаете всего от своего веб-сервера Python.

Задолго до Python 3, ChatGPT и TikTok бедным разработчикам приходилось писать свои веб-серверы, используя *вздрагивая* родные функции Python 2. Это означало отсутствие асинхронных операций. Фреймворки, возникшие в эту эпоху, получили широкое распространение и стали краеугольным камнем веб-разработки. Прямо сейчас может быть даже синхронный веб-сервер Python, показывающий вам этот текст. Но все меняется, и благодаря встроенной поддержке асинхронных операций в Python 3 появился новый стандарт — Асинхронный интерфейс серверного шлюза (ASGI).

В настоящее время существуют новые фреймворки, реализующие этот стандарт, такие как quart и FastAPI, но многие разработчики по-прежнему по умолчанию используют свои знакомые синхронные предшественники. Цель этой статьи — продемонстрировать преимущества асинхронных веб-серверов для производительности вашего приложения.

В этой статье мы будем загружать тестовые веб-серверы на Python. Дополнительную информацию о нагрузочном тестировании с помощью Locust см. в моей предыдущей статье.

Кто должен это прочитать?

Любой, кто хочет создать веб-сервер Python (или кто поддерживает существующий), привязанный либо к сети, либо к вводу-выводу. Или просто любой, кто хочет узнать о парадигмах синхронной и асинхронной веб-инфраструктуры!

Перво-наперво — синхронный подход

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

Веб-сервер, по сути, балансирует нагрузку на входящие запросы к рабочим процессам, которые обычно существуют в отдельных потоках. Для поддержки случая, когда все рабочие процессы заняты, веб-сервер может иметь внутреннюю очередь для хранения запросов в порядке FIFO.

В этом дизайне количество потоков по своей сути ограничивает объем параллелизма, который вы можете обеспечить. Если работа связана с процессором — тогда это прекрасная настройка, но если работа связана с вводом-выводом или с привязкой к процессору — тогда ограничение вашего параллелизма равнозначно ограничению вашей максимальной пропускной способности. Позвольте мне продемонстрировать на простом примере.

Давайте сначала создадим простой синхронный веб-сервер, который имитирует рабочую нагрузку, связанную с сетью / вводом-выводом, для этого мы будем использовать фреймворк Flask, обслуживаемый официанткой.

Наше приложение вернется через от 100 до 1000 миллисекунд — довольно реалистичный сценарий, связанный с сетью.

Просто запустите с «waitress-serve — port=8000 flask_app_network_bound:app», чтобы запустить сервер.

Теперь давайте увеличим нагрузку с помощью Locust. См. мою предыдущую статью для более подробной информации о том, как приступить к работе с нагрузочными тестами Python.

На данный момент мы будем увеличивать нагрузку примерно на 10 TPS каждые 30 секунд.

С конфигурацией сервера по умолчанию (4 потока) мы можем получить только около 7 TPS, а задержка увеличивается линейно по мере того, как все больше и больше запросов ожидают в очереди доступного работника.

С 16 потоками мы можем предотвратить неизбежное до предсказуемых ~30 TPS.

Итак, мы можем давать серверу все больше и больше потоков и достигать большей пропускной способности. Это ожидаемо, но поскольку у нас есть приложение, связанное с сетью, нам действительно не нужно держать поток занятым, пока мы ждем сети, должен быть лучший способ.

Введите асинхронный

Асинхронные веб-серверы

Асинхронные веб-серверы принципиально отличаются тем, как они обрабатывают запросы и параллелизм. Они будут запускать основной процесс — по сути, диспетчер задач, который планирует выполнение запросов. Ключевое отличие здесь в том, что задачи возвращают управление диспетчеру задач, когда им нужно дождаться асинхронной работы — как в случае дорогого сетевого вызова. Это позволяет диспетчеру задач достигать очень высокого уровня параллелизма — поскольку, когда задача возвращает управление, он может планировать дополнительные задачи. При такой архитектуре одновременно могут существовать сотни или даже тысячи одновременных задач. Эта архитектура может выглядеть примерно так:

Итак, как мы реализуем это на практике? Quart — это быстрый асинхронный веб-фреймворк на Python и хорошая отправная точка — с семантикой, намеренно похожей на Flask. Наше простое синхронное приложение теперь реализовано следующим образом:

Обратите внимание, что наш обработчик запросов теперь асинхронный, а наш связанный с сетью вызов снабжен аннотацией ожидание — эти ключевые слова позволяют нашему асинхронному серверу творить магию, о которой я говорил ранее. Чтобы узнать больше об асинхронности Python, ознакомьтесь с документацией asyncio.

Давайте запустим тот же тест, что и раньше, с конфигурацией веб-сервера hypercorn по умолчанию — hypercorn quart-app-network-bound:app:

Мы получаем до 100 TPS, не вспотев. Это свидетельствует о том, насколько эффективными могут быть асинхронные серверы с рабочими нагрузками, сильно привязанными к сети/оператору ввода-вывода.

Однако становится лучше, помните, как я упоминал, что диспетчер задач работает в одном процессе? Что ж, мы можем воспользоваться преимуществами наших нескольких ядер ЦП, создав несколько параллельных рабочих процессов.

Теперь у нас есть 4 отдельных идентификатора процесса, и наша схема архитектуры выглядит примерно так:

Мы объединяем лучшее из обоих миров — с процессами, способными выполнять все те же асинхронные методы, которые мы обсуждали ранее.

Все, что осталось, это проверить это с помощью другого нагрузочного теста:

Неторопливые 250 TPS без дыма от нашего веб-сервера — (пара всплесков задержки, вероятно, когда моя система загружала веб-страницу или что-то в этом роде).

Заключение

В этой статье мы рассмотрели синхронные и асинхронные веб-серверы Python и показали, что для рабочих нагрузок, сильно связанных с вводом-выводом и сетью, (относительно) более новые веб-серверы, совместимые с ASGI, могут быть гораздо более производительными, чем серверы WSGI. ваш профиль приложений и можете ли вы извлечь выгоду из async.

Чтобы увидеть код, использованный в этой статье, посетите репозиторий Github.

Если вам понравилась эта статья, вам может понравиться другой контент с Веб-сайта VidaVolta и Medium page, который содержит статьи на темы, похожие на эту.

Рекомендации

Первоначально опубликовано на https://www.vidavolta.io 5 июня 2023 г.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .