Как обновить приложение 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://docs.python.org/3/library/asyncio.html
- https://hackernoon.com/3x-faster-than-flask-8e89bfbe8e4f
- https://medium.com/super/how-we-optimized-service-performance-using-the-python-quart-asgi-framework-and-reduced-costs-by-1362dc365a0
- https://pgjones.dev/blog/fastapi-flask-quart-2022/
- https://github.com/pallets/quart
- https://pgjones.gitlab.io/hypercorn/tutorials/installation.html
- https://pgjones.gitlab.io/quart/tutorials/asyncio.html
- https://www.infoworld.com/article/3658336/asgi-explained-the-future-of-python-web-development.html
Первоначально опубликовано на https://www.vidavolta.io 5 июня 2023 г.
Дополнительные материалы на PlainEnglish.io.
Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .